import Dexie, { type Table } from 'dexie'
import type { Process, Activity, GatewayConfig, Resource, Simulation, ProcessGroup, AppSettings } from '@/domain/types'

export class ProcessCostDb extends Dexie {
  processes!: Table<Process>
  activities!: Table<Activity>
  gateways!: Table<GatewayConfig>
  resources!: Table<Resource>
  simulations!: Table<Simulation>
  groups!: Table<ProcessGroup>    // 🆕 Sprint 3
  settings!: Table<AppSettings>   // 🆕 Sprint 3

  constructor() {
    super('ProcessCostPlatform')

    this.version(1).stores({
      processes: 'id, name, updatedAt',
      activities: 'id, processId, bpmnElementId',
      gateways: 'id, processId, bpmnElementId',
      resources: 'id, processId, name',
      simulations: 'id, processId, executedAt',
    })

    // Sprint 1: agrega campos de automatización a Activity y volumetría a Process.
    // El upgrade popula datos existentes con defaults para garantizar invariante de tipo.
    this.version(2)
      .stores({
        processes: 'id, name, updatedAt',
        activities: 'id, processId, bpmnElementId',
        gateways: 'id, processId, bpmnElementId',
        resources: 'id, processId, name',
        simulations: 'id, processId, executedAt',
      })
      .upgrade(async (tx) => {
        await tx.table('activities').toCollection().modify((act) => {
          if (act.automatable === undefined)         act.automatable = false
          if (act.automatedCostFixed === undefined)  act.automatedCostFixed = 0
          if (act.automatedTimeMinutes === undefined) act.automatedTimeMinutes = 0
        })
        await tx.table('processes').toCollection().modify((proc) => {
          if (proc.annualFrequency === undefined)      proc.annualFrequency = 1000
          if (proc.analysisHorizonYears === undefined) proc.analysisHorizonYears = 3
          if (proc.automationInvestment === undefined) proc.automationInvestment = 0
        })
      })

    // Sprint 2: migra ActivityResourceAssignment.utilizationPercent → utilizationMinutes + units.
    // Los stores no cambian — assignments están embebidos en Activity, no son tabla propia.
    this.version(3)
      .stores({
        processes: 'id, name, updatedAt',
        activities: 'id, processId, bpmnElementId',
        gateways: 'id, processId, bpmnElementId',
        resources: 'id, processId, name',
        simulations: 'id, processId, executedAt',
      })
      .upgrade(async (tx) => {
        await tx.table('activities').toCollection().modify((act) => {
          if (!Array.isArray(act.assignedResources)) {
            act.assignedResources = []
            return
          }
          act.assignedResources = act.assignedResources.map((assignment: Record<string, unknown>) => {
            if ('utilizationPercent' in assignment) {
              // Heurística: minutos = round(executionTimeMinutes × utilizationPercent)
              // Si executionTimeMinutes no está disponible o es 0, usar 0 (conservador)
              const execMinutes = typeof act.executionTimeMinutes === 'number' ? act.executionTimeMinutes : 0
              const utilizationMinutes = Math.round(execMinutes * (assignment.utilizationPercent as number))
              return { resourceId: assignment.resourceId, utilizationMinutes, units: 1 }
            }
            if ('utilizationMinutes' in assignment) {
              // Ya está en nuevo formato (idempotencia)
              return {
                resourceId: assignment.resourceId,
                utilizationMinutes: assignment.utilizationMinutes ?? 0,
                units: assignment.units ?? 1,
              }
            }
            // Fallback defensivo
            return { resourceId: assignment.resourceId, utilizationMinutes: 0, units: 1 }
          })
        })
      })

    // Sprint 3: agrega tabla de grupos (Process Library), tabla de settings,
    // campos groupId/tags en Process, y automatedAssignedResources en Activity.
    this.version(4)
      .stores({
        processes:   'id, name, updatedAt, groupId',  // groupId indexado para queries por grupo
        activities:  'id, processId, bpmnElementId',
        gateways:    'id, processId, bpmnElementId',
        resources:   'id, processId, name',
        simulations: 'id, processId, executedAt',
        groups:      'id, name, updatedAt',           // nueva tabla
        settings:    'id',                            // nueva tabla (singleton)
      })
      .upgrade(async (tx) => {
        // Process: agregar groupId (null) y tags ([])
        await tx.table('processes').toCollection().modify((proc) => {
          if (proc.groupId === undefined) proc.groupId = null
          if (!Array.isArray(proc.tags))  proc.tags = []
        })
        // Activity: agregar automatedAssignedResources ([])
        await tx.table('activities').toCollection().modify((act) => {
          if (!Array.isArray(act.automatedAssignedResources)) {
            act.automatedAssignedResources = []
          }
        })
        // Settings: crear singleton con defaults (put es idempotente por clave primaria)
        await tx.table('settings').put({ id: 'singleton', groupLabel: 'Cliente' })
      })

    // Belt-and-suspenders: garantizar singleton en fresh installs y entornos de test.
    // El upgrade también lo crea; este handler cubre el caso en que la tabla sea nueva
    // pero el upgrade no haya persistido el put (conocido con fake-indexeddb).
    this.on('ready', async () => {
      const existing = await this.settings.get('singleton')
      if (!existing) {
        await this.settings.put({ id: 'singleton', groupLabel: 'Cliente' })
      }
    })
  }
}

export const db = new ProcessCostDb()
