import type {
  ProcessGraph,
  ProcessGraphNode,
  ProcessGraphFlow,
  BpmnNodeType,
  BpmnElementId,
} from './types'

// Mapeo de tags XML BPMN → nuestro tipo normalizado
const TAG_TO_TYPE: Record<string, BpmnNodeType> = {
  startevent: 'startEvent',
  endevent: 'endEvent',
  task: 'task',
  usertask: 'userTask',
  servicetask: 'serviceTask',
  scripttask: 'scriptTask',
  manualtask: 'manualTask',
  businessruletask: 'businessRuleTask',
  subprocess: 'subprocess',
  exclusivegateway: 'exclusiveGateway',
  parallelgateway: 'parallelGateway',
  inclusivegateway: 'inclusiveGateway',
  eventbasedgateway: 'eventBasedGateway',
  intermediatethrowevent: 'intermediateThrowEvent',
  intermediatecatchevent: 'intermediateCatchEvent',
}

function normalizeLocalName(tagName: string): string {
  // Eliminar namespace prefix (bpmn:Task → task)
  const local = tagName.includes(':') ? tagName.split(':')[1] : tagName
  return local.toLowerCase()
}

function getAttr(el: Element, name: string): string {
  return el.getAttribute(name) ?? ''
}

function parseFlowRefs(el: Element, tagName: string): string[] {
  return Array.from(el.getElementsByTagNameNS('*', tagName)).map((ref) => ref.textContent?.trim() ?? '')
}

export class BpmnParseError extends Error {
  constructor(message: string) {
    super(message)
    this.name = 'BpmnParseError'
  }
}

export function parseBpmnXml(xml: string): ProcessGraph {
  let doc: Document
  try {
    const parser = new DOMParser()
    doc = parser.parseFromString(xml, 'application/xml')
  } catch {
    throw new BpmnParseError('El archivo no es un XML válido')
  }

  const parserError = doc.querySelector('parsererror')
  if (parserError) {
    throw new BpmnParseError('XML malformado: ' + parserError.textContent?.slice(0, 200))
  }

  const nodes = new Map<BpmnElementId, ProcessGraphNode>()
  const flows = new Map<string, ProcessGraphFlow>()
  const startEventIds: BpmnElementId[] = []

  // Buscar TODOS los procesos del documento. Las colaboraciones tienen múltiples
  // <bpmn:process> como hijos directos de <definitions>, uno por pool.
  const definitions = doc.documentElement
  const processEls = Array.from(definitions.children).filter(
    (el) => normalizeLocalName(el.tagName) === 'process'
  )

  if (processEls.length === 0) {
    throw new BpmnParseError('No se encontró un elemento <process> en el BPMN')
  }

  // Parsear cada proceso y acumular nodos y flujos en mapas compartidos
  for (const processEl of processEls) {
    for (const child of Array.from(processEl.children)) {
      const local = normalizeLocalName(child.tagName)
      const type: BpmnNodeType = TAG_TO_TYPE[local] ?? 'unknown'

      if (local === 'sequenceflow') {
        const flow: ProcessGraphFlow = {
          id: getAttr(child, 'id'),
          sourceRef: getAttr(child, 'sourceRef'),
          targetRef: getAttr(child, 'targetRef'),
          name: getAttr(child, 'name') || undefined,
        }
        if (flow.id) flows.set(flow.id, flow)
        continue
      }

      if (type === 'unknown') continue

      const id = getAttr(child, 'id')
      if (!id) continue

      const node: ProcessGraphNode = {
        id,
        name: getAttr(child, 'name'),
        type,
        outgoing: parseFlowRefs(child, 'outgoing'),
        incoming: parseFlowRefs(child, 'incoming'),
      }

      nodes.set(id, node)
      if (type === 'startEvent') startEventIds.push(id)
    }
  }

  if (nodes.size === 0) {
    throw new BpmnParseError('El proceso no contiene elementos reconocibles')
  }

  // Pools de soporte (SAP, bandeja manual, etc.) pueden no tener StartEvent propio —
  // exigimos al menos uno entre todos los pools del diagrama.
  if (startEventIds.length === 0) {
    throw new BpmnParseError('El proceso no tiene evento de inicio (StartEvent)')
  }

  return { nodes, flows, startEventIds }
}

// Extrae solo los IDs y nombres de los task/subprocess — util para inicializar actividades
export function extractActivityElements(
  graph: ProcessGraph
): Array<{ bpmnElementId: BpmnElementId; name: string; type: 'task' | 'subprocess' }> {
  const result = []
  for (const node of graph.nodes.values()) {
    if (
      ['task', 'userTask', 'serviceTask', 'scriptTask', 'manualTask', 'businessRuleTask'].includes(node.type)
    ) {
      result.push({ bpmnElementId: node.id, name: node.name || node.id, type: 'task' as const })
    } else if (node.type === 'subprocess') {
      result.push({ bpmnElementId: node.id, name: node.name || node.id, type: 'subprocess' as const })
    }
  }
  return result
}

// Extrae gateways divergentes (los que tienen >1 outgoing)
export function extractGatewayElements(
  graph: ProcessGraph
): Array<{ bpmnElementId: BpmnElementId; name: string; gatewayType: string; outgoing: string[] }> {
  const result = []
  for (const node of graph.nodes.values()) {
    if (
      ['exclusiveGateway', 'parallelGateway', 'inclusiveGateway', 'eventBasedGateway'].includes(node.type) &&
      node.outgoing.length > 1
    ) {
      result.push({
        bpmnElementId: node.id,
        name: node.name || node.id,
        gatewayType: node.type,
        outgoing: node.outgoing,
      })
    }
  }
  return result
}
