import type { WorkflowDefinition, WorkflowNodeDef } from '@/types/workflow'
import { nextWorkflowId } from '@/workflow/workflowLibrary'
import { normalizeWorkflow } from '@/workflow/workflowUtils'

export type WorkflowImportMode = 'single' | 'catalog'

export interface WorkflowImportResult {
  mode: WorkflowImportMode
  workflows: WorkflowDefinition[]
}

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

/** 解析 JSON 文本，支持单流程、流程库合集、旧版 draft 包裹格式 */
export function parseWorkflowJsonText(text: string): WorkflowImportResult {
  let parsed: unknown
  try {
    parsed = JSON.parse(text)
  } catch {
    throw new WorkflowImportError('JSON 格式无效，请检查文件内容')
  }

  if (!parsed || typeof parsed !== 'object') {
    throw new WorkflowImportError('文件内容不是有效的流程 JSON 对象')
  }

  const root = parsed as Record<string, unknown>

  if (Array.isArray(root.workflows)) {
    const workflows = root.workflows
      .map((item, index) => coerceWorkflowDefinition(item, index))
      .filter((w): w is WorkflowDefinition => w !== null)
    if (workflows.length === 0) {
      throw new WorkflowImportError('流程库文件中没有可导入的流程')
    }
    return { mode: 'catalog', workflows }
  }

  if (root.workflow && typeof root.workflow === 'object') {
    const one = coerceWorkflowDefinition(root.workflow, 0)
    if (!one) throw new WorkflowImportError('draft 格式中的 workflow 字段无效')
    return { mode: 'single', workflows: [one] }
  }

  const single = coerceWorkflowDefinition(parsed, 0)
  if (!single) {
    throw new WorkflowImportError('无法识别文件结构：需包含 nodes、edges 字段，或 workflows 数组')
  }
  return { mode: 'single', workflows: [single] }
}

function coerceWorkflowDefinition(raw: unknown, index: number): WorkflowDefinition | null {
  if (!raw || typeof raw !== 'object') return null
  const row = raw as Record<string, unknown>
  if (!Array.isArray(row.nodes)) return null

  const id =
    typeof row.id === 'string' && row.id.trim()
      ? row.id.trim()
      : nextWorkflowId()
  const name =
    typeof row.name === 'string' && row.name.trim()
      ? row.name.trim()
      : `导入流程 ${index + 1}`
  const version = typeof row.version === 'number' ? row.version : 1
  const edges = Array.isArray(row.edges) ? sanitizeEdges(row.edges) : []

  const viewport = sanitizeViewport(row.viewport)

  const nodes = (row.nodes as unknown[]).map((n, nodeIndex) => sanitizeNode(n, nodeIndex))

  return prepareImportedWorkflow({
    id,
    name,
    version,
    nodes,
    edges,
    ...(viewport ? { viewport } : {})
  })
}

function sanitizeViewport(raw: unknown): WorkflowDefinition['viewport'] | undefined {
  if (!raw || typeof raw !== 'object') return undefined
  const v = raw as Record<string, unknown>
  const x = Number(v.x)
  const y = Number(v.y)
  const zoom = Number(v.zoom)
  if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(zoom)) return undefined
  return { x, y, zoom }
}

function sanitizeNode(raw: unknown, index: number): WorkflowNodeDef {
  const row = (raw && typeof raw === 'object' ? raw : {}) as Record<string, unknown>
  const id = typeof row.id === 'string' && row.id.trim() ? row.id.trim() : `n-import-${index + 1}`
  const type = typeof row.type === 'string' && row.type.trim() ? row.type.trim() : 'start'
  const label = typeof row.label === 'string' && row.label.trim() ? row.label.trim() : type
  const position =
    row.position && typeof row.position === 'object'
      ? {
          x: Number((row.position as Record<string, unknown>).x) || 0,
          y: Number((row.position as Record<string, unknown>).y) || 0
        }
      : { x: 80 + index * 40, y: 120 }

  const params =
    row.params && typeof row.params === 'object' && !Array.isArray(row.params)
      ? { ...(row.params as Record<string, unknown>) }
      : {}

  return { id, type, label, position, params }
}

function sanitizeEdges(raw: unknown[]): WorkflowDefinition['edges'] {
  return raw
    .map((item, index) => {
      if (!item || typeof item !== 'object') return null
      const row = item as Record<string, unknown>
      const source = typeof row.source === 'string' ? row.source : ''
      const target = typeof row.target === 'string' ? row.target : ''
      if (!source || !target) return null
      return {
        id: typeof row.id === 'string' && row.id.trim() ? row.id.trim() : `e-import-${index + 1}`,
        source,
        target,
        ...(typeof row.sourceHandle === 'string' ? { sourceHandle: row.sourceHandle } : {}),
        ...(typeof row.label === 'string' ? { label: row.label } : {})
      }
    })
    .filter((e): e is NonNullable<typeof e> => e !== null)
}

/** 规范化并移除仅用于导出的跳转字段（画布以 edges 为准） */
export function prepareImportedWorkflow(def: WorkflowDefinition): WorkflowDefinition {
  const cleaned: WorkflowDefinition = {
    ...def,
    nodes: def.nodes.map((n) => {
      const { nextId, failNextId, timeoutNextId, ...rest } = n
      void nextId
      void failNextId
      void timeoutNextId
      return rest
    })
  }
  return normalizeWorkflow(cleaned)
}

/** 为与库内已有 id 冲突的流程分配新 id */
export function resolveWorkflowIdConflicts(
  workflows: WorkflowDefinition[],
  existingIds: Set<string>
): WorkflowDefinition[] {
  const used = new Set(existingIds)
  return workflows.map((w) => {
    let id = w.id
    if (used.has(id)) {
      id = nextWorkflowId()
    }
    used.add(id)
    return id === w.id ? w : { ...w, id }
  })
}

export function pickJsonFile(): Promise<File | null> {
  return new Promise((resolve) => {
    const input = document.createElement('input')
    input.type = 'file'
    input.accept = '.json,application/json'
    input.style.display = 'none'
    input.onchange = () => {
      const file = input.files?.[0] ?? null
      input.remove()
      resolve(file)
    }
    input.oncancel = () => {
      input.remove()
      resolve(null)
    }
    document.body.appendChild(input)
    input.click()
  })
}

export async function readFileAsText(file: File): Promise<string> {
  return file.text()
}

export function importResultSummary(result: WorkflowImportResult): string {
  if (result.mode === 'catalog') {
    return `流程库文件，共 ${result.workflows.length} 个流程`
  }
  const name = result.workflows[0]?.name ?? '未命名'
  return `单个流程「${name}」`
}
