import type { Node, Edge } from '@xyflow/react'
import type { NodeData, EdgeData, EdgeType } from '@/types'
import type { YamlNode, YamlNodeConnection } from '@/types/yaml'
import { SIDES, sideDefault, sideHandleCount } from '@/utils/handleUtils'
import yaml from 'js-yaml'

/** Build a map of node id → label for edge resolution */
function buildIdToLabel(nodes: Node<NodeData>[]): Map<string, string> {
  const m = new Map<string, string>()
  for (const n of nodes) m.set(n.id, n.data.label)
  return m
}

function makeConnection(
  targetLabel: string,
  edgeType: EdgeType,
  edgeLabel: string | undefined,
  edge?: Edge<EdgeData>,
): YamlNodeConnection {
  const conn: YamlNodeConnection = {
    label: targetLabel,
    linkType: edgeType,
    linkLabel: edgeLabel ?? '',
  }
  // Preserve the exact connection points so the layout round-trips.
  if (edge?.sourceHandle) conn.sourceHandle = edge.sourceHandle
  if (edge?.targetHandle) conn.targetHandle = edge.targetHandle
  return conn
}

/** Write each side's handle count onto the entry when it exceeds the side default. */
function attachHandleCounts(entry: YamlNode, data: NodeData): void {
  for (const side of SIDES) {
    const count = sideHandleCount(data, side)
    if (count > sideDefault(side)) {
      entry[`${side}Handles` as 'topHandles' | 'bottomHandles' | 'leftHandles' | 'rightHandles'] =
        count
    }
  }
}

/**
 * Serialize React Flow canvas state to a YAML string.
 * Each node becomes one entry; edges are embedded as parent/clusterR/clusterL sub-objects.
 * Edge deduplication: each edge is written on exactly one side (source as clusterR, target as clusterL)
 * unless the edge type is 'virtual' or there is a parentId relationship, in which case
 * it becomes the 'parent' field of the child node.
 */
export function exportCanvasToYaml(nodes: Node<NodeData>[], edges: Edge<EdgeData>[]): string {
  const idToLabel = buildIdToLabel(nodes)

  // Build per-node edge maps (id → connections)
  // We use a Set to track already-serialized edge ids (deduplication).
  const serializedEdges = new Set<string>()

  // Index edges by source and target for quick lookup
  const edgesBySource = new Map<string, Edge<EdgeData>[]>()
  const edgesByTarget = new Map<string, Edge<EdgeData>[]>()
  for (const e of edges) {
    if (!edgesBySource.has(e.source)) edgesBySource.set(e.source, [])
    edgesBySource.get(e.source)!.push(e)
    if (!edgesByTarget.has(e.target)) edgesByTarget.set(e.target, [])
    edgesByTarget.get(e.target)!.push(e)
  }

  const yamlNodes: YamlNode[] = []

  for (const node of nodes) {
    const d = node.data

    // Skip groupRect nodes — they are canvas decoration only
    if (d.type === 'groupRect') continue

    const entry: YamlNode = {
      nodeType: d.type,
      label: d.label,
    }

    if (d.custom_icon) entry.nodeIcon = d.custom_icon
    if (d.hostname) entry.hostname = d.hostname
    if (d.ip) entry.ipAddress = d.ip
    if (d.check_method && d.check_method !== 'none') entry.checkMethod = d.check_method
    if (d.check_target) entry.checkTarget = d.check_target
    if (d.notes) entry.notes = d.notes

    // Hardware specs — omit zero values
    if (d.cpu_model) entry.cpuModel = d.cpu_model
    if (d.cpu_count && d.cpu_count > 0) entry.cpuCore = d.cpu_count
    if (d.ram_gb && d.ram_gb > 0) entry.ram = d.ram_gb
    if (d.disk_gb && d.disk_gb > 0) entry.disk = d.disk_gb

    // Custom connection-point counts, so imported edges land on real slots.
    attachHandleCounts(entry, d)

    // Parent relationship: if this node has a parentId in React Flow,
    // encode it as a 'parent' connection using any virtual edge between them.
    if (node.parentId) {
      const parentLabel = idToLabel.get(node.parentId) ?? node.parentId
      // Find an edge between parent and this node (either direction)
      const parentEdges = [
        ...(edgesBySource.get(node.parentId) ?? []).filter((e) => e.target === node.id),
        ...(edgesByTarget.get(node.parentId) ?? []).filter((e) => e.source === node.id),
      ]
      const pEdge = parentEdges[0]
      const linkType: EdgeType = (pEdge?.data?.type as EdgeType) ?? 'virtual'
      const linkLabel = pEdge?.data?.label ?? ''
      entry.parent = { label: parentLabel, linkType, linkLabel: linkLabel as string }
      // Import always rebuilds this edge parent→child, so orient the handles the
      // same way (swap when the stored edge runs child→parent).
      if (pEdge) {
        const parentToChild = pEdge.source === node.parentId
        const srcH = parentToChild ? pEdge.sourceHandle : pEdge.targetHandle
        const tgtH = parentToChild ? pEdge.targetHandle : pEdge.sourceHandle
        if (srcH) entry.parent.sourceHandle = srcH
        if (tgtH) entry.parent.targetHandle = tgtH
        serializedEdges.add(pEdge.id)
      }
    }

    // Outgoing edges (this node is the source):
    // - cluster type → clusterR (Proxmox cluster link, directional)
    // - everything else → links array (supports multiple connections)
    const outgoingEdges = (edgesBySource.get(node.id) ?? []).filter(
      (e) => !serializedEdges.has(e.id) && e.target !== node.parentId,
    )
    for (const e of outgoingEdges) {
      const targetLabel = idToLabel.get(e.target)
      if (!targetLabel) continue
      const edgeType: EdgeType = (e.data?.type as EdgeType) ?? 'ethernet'
      const edgeLabel = e.data?.label as string | undefined
      const conn = makeConnection(targetLabel, edgeType, edgeLabel, e)
      if (edgeType === 'cluster') {
        if (!entry.clusterR) entry.clusterR = conn
      } else {
        entry.links = [...(entry.links ?? []), conn]
      }
      serializedEdges.add(e.id)
    }

    // Incoming cluster edges not yet serialized → clusterL
    const incomingClusterEdges = (edgesByTarget.get(node.id) ?? []).filter(
      (e) => !serializedEdges.has(e.id) && (e.data?.type as EdgeType) === 'cluster',
    )
    for (const e of incomingClusterEdges) {
      const sourceLabel = idToLabel.get(e.source)
      if (!sourceLabel) continue
      const edgeLabel = e.data?.label as string | undefined
      if (!entry.clusterL) entry.clusterL = makeConnection(sourceLabel, 'cluster', edgeLabel, e)
      serializedEdges.add(e.id)
    }

    yamlNodes.push(entry)
  }

  return yaml.dump(yamlNodes, { lineWidth: -1, noRefs: true })
}

/** Trigger a browser file download with the given YAML content */
export function downloadYaml(content: string, filename = 'homelable-export.yaml'): void {
  const blob = new Blob([content], { type: 'text/yaml;charset=utf-8' })
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = filename
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  URL.revokeObjectURL(url)
}
