import { createGenerationNode, removeNodes, upsertNode } from '../model/graphOps'
import type { GenerationCanvasNode } from '../model/generationCanvasTypes'
import { CLIPBOARD_OFFSET, createClipboardNodeId, createNodeId } from './canvasIds'
import { bumpPersistRevision, isCategoryId, shouldPersistCanvasMutation } from './canvasGuards'
import { getHistoryFlags, pushUndoSnapshot } from '../events/canvasUndoJournal'
import { emitCanvasGesture } from '../events/canvasEventEmitter'
import type { CanvasNodeActions, CanvasSliceCreator } from './canvasStoreTypes'

export const createCanvasNodeActions: CanvasSliceCreator<CanvasNodeActions> = (set, get) => ({
  addNode: (input) => {
    const currentState = get()
    const existingCount = currentState.nodes.filter((node) => node.kind === input.kind).length
    const categoryId = isCategoryId(input.categoryId) ? input.categoryId : undefined
    const baseNode = createGenerationNode({
      id: createNodeId(input.kind),
      kind: input.kind,
      title: input.title,
      prompt: input.prompt,
      x: input.position?.x ?? 120 + existingCount * 34,
      y: input.position?.y ?? 360 + existingCount * 30,
    })
    const nextNode = categoryId ? { ...baseNode, categoryId } : baseNode
    pushUndoSnapshot(currentState)
    set((state) => {
      state.nodes = upsertNode(state.nodes, nextNode)
      state.selectedNodeIds = input.select === false ? state.selectedNodeIds : [nextNode.id]
      state.pendingConnectionSourceId = ''
      bumpPersistRevision(state)
      Object.assign(state, getHistoryFlags())
    })
    // S5-a 影子日志:nextNode 是 immer 外构造的 plain 对象,emit 内部再深拷贝一层
    emitCanvasGesture([{ type: 'canvas.node.added', payload: { node: nextNode } }])
    return nextNode
  },
  commitPersistedChange: () => {
    set((state) => {
      bumpPersistRevision(state)
    })
  },
  updateNode: (nodeId, patch, options) => {
    if (!get().nodes.some((candidate) => candidate.id === nodeId)) return
    set((state) => {
      const node = state.nodes.find((candidate) => candidate.id === nodeId)
      if (!node) return
      Object.assign(node, patch)
      if (shouldPersistCanvasMutation(options)) bumpPersistRevision(state)
    })
    emitCanvasGesture([{ type: 'canvas.node.updated', payload: { nodeId, patch } }])
  },
  updateNodePrompt: (nodeId, prompt) => {
    if (!get().nodes.some((candidate) => candidate.id === nodeId)) return
    set((state) => {
      const node = state.nodes.find((candidate) => candidate.id === nodeId)
      if (!node) return
      node.prompt = prompt
      bumpPersistRevision(state)
    })
    emitCanvasGesture([{ type: 'canvas.node.prompt-changed', payload: { nodeId, prompt } }])
  },
  setNodeLocked: (nodeId, locked) => {
    const existing = get().nodes.find((candidate) => candidate.id === nodeId)
    if (!existing || Boolean(existing.locked) === locked) return
    set((state) => {
      const node = state.nodes.find((candidate) => candidate.id === nodeId)
      if (!node) return
      node.locked = locked
      bumpPersistRevision(state)
    })
    // 专用事件(非 node.updated):锁是审计要点(谁锁的/何时锁的),日志里必须一眼可查。
    // title 随事件携带:S9 记忆提炼器增量扫描拿不到旧事件里的标题,事件自含可读。
    emitCanvasGesture([{ type: locked ? 'canvas.node.locked' : 'canvas.node.unlocked', payload: { nodeId, title: existing.title } }])
  },
  moveNode: (nodeId, position, options) => {
    // 守卫上移到 set 外(影子日志要与真实变更同真值;语义与原内嵌守卫等价)
    const existing = get().nodes.find((candidate) => candidate.id === nodeId)
    if (!existing || (existing.position.x === position.x && existing.position.y === position.y)) return
    set((state) => {
      const node = state.nodes.find((candidate) => candidate.id === nodeId)
      if (!node) return
      node.position = position
      if (shouldPersistCanvasMutation(options)) bumpPersistRevision(state)
    })
    emitCanvasGesture([{ type: 'canvas.node.moved', payload: { nodeId, position } }])
  },
  moveSelectedNodes: (delta, options) => {
    set((state) => {
      const selected = new Set(state.selectedNodeIds)
      if (!selected.size || (delta.x === 0 && delta.y === 0)) return
      let moved = false
      for (const node of state.nodes) {
        if (!selected.has(node.id)) continue
        node.position = {
          x: Math.round(node.position.x + delta.x),
          y: Math.round(node.position.y + delta.y),
        }
        moved = true
      }
      if (moved && shouldPersistCanvasMutation(options)) bumpPersistRevision(state)
    })
    // 后态读取:每个被移动节点一条 moved,共享一个手势 txn
    const selected = new Set(get().selectedNodeIds)
    if (selected.size && (delta.x !== 0 || delta.y !== 0)) {
      emitCanvasGesture(
        get().nodes
          .filter((node) => selected.has(node.id))
          .map((node) => ({ type: 'canvas.node.moved', payload: { nodeId: node.id, position: node.position } })),
      )
    }
  },
  deleteSelectedNodes: () => {
    const currentState = get()
    if (!currentState.selectedNodeIds.length) return
    const removedIds = [...currentState.selectedNodeIds]
    pushUndoSnapshot(currentState)
    set((state) => {
      const next = removeNodes(state.nodes, state.edges, state.selectedNodeIds)
      state.nodes = next.nodes
      state.edges = next.edges
      state.selectedNodeIds = []
      bumpPersistRevision(state)
      Object.assign(state, getHistoryFlags())
    })
    emitCanvasGesture(removedIds.map((nodeId) => ({ type: 'canvas.node.removed', payload: { nodeId } })))
  },
  selectNode: (nodeId, additive = false) => {
    set((state) => {
      if (!additive) {
        state.selectedNodeIds = [nodeId]
        return
      }
      const nextIds = state.selectedNodeIds.includes(nodeId)
        ? state.selectedNodeIds.filter((id) => id !== nodeId)
        : [...state.selectedNodeIds, nodeId]
      state.selectedNodeIds = nextIds
    })
  },
  clearSelection: () => {
    set({ selectedNodeIds: [], pendingConnectionSourceId: '' })
  },
  // v0.7.5: 全选当前分类的所有节点（如果传 categoryId 则限定，否则全选画布所有节点）
  selectAllNodes: (categoryId?: string) => {
    set((state) => {
      const ids = state.nodes
        .filter((n) => !categoryId || (n.categoryId || 'shots') === categoryId)
        .map((n) => n.id)
      state.selectedNodeIds = ids
    })
  },
  duplicateNodeForRegeneration: (nodeId) => {
    const state = get()
    const node = state.nodes.find((candidate) => candidate.id === nodeId)
    if (!node) return null
    const nextNode = createGenerationNode({
      id: createNodeId(node.kind),
      kind: node.kind,
      title: node.title,
      prompt: node.prompt,
      x: node.position.x + 40,
      y: node.position.y + 40,
    })
    const history = node.history ? [...node.history] : []
    const result = node.result
    if (result && !history.some((entry) => entry.id === result.id)) {
      history.unshift(result)
    }
    const copiedNode: GenerationCanvasNode = {
      ...nextNode,
      history,
      references: node.references ? [...node.references] : [],
      meta: node.meta ? { ...node.meta } : {},
      size: node.size ? { ...node.size } : nextNode.size,
      prompt: node.prompt || '',
      categoryId: node.categoryId,
      groupId: node.groupId,
      derivedFrom: node.id,
    }
    pushUndoSnapshot(state)
    set((current) => {
      const original = current.nodes.find((candidate) => candidate.id === nodeId)
      if (original && history.length) original.history = history
      current.nodes.push(copiedNode)
      if (copiedNode.groupId) {
        const group = current.groups.find((candidate) => candidate.id === copiedNode.groupId)
        if (group && !group.nodeIds.includes(copiedNode.id)) {
          group.nodeIds.push(copiedNode.id)
          group.updatedAt = Date.now()
        }
      }
      current.selectedNodeIds = [copiedNode.id]
      bumpPersistRevision(current)
      Object.assign(current, getHistoryFlags())
    })
    // 一笔手势三件事如实记账:原节点补 history、新节点诞生、组成员变化(后态)
    const touchedGroup = copiedNode.groupId ? get().groups.find((group) => group.id === copiedNode.groupId) : undefined
    emitCanvasGesture([
      ...(history.length ? [{ type: 'canvas.node.updated', payload: { nodeId, patch: { history } } }] : []),
      { type: 'canvas.node.added', payload: { node: copiedNode } },
      ...(touchedGroup ? [{ type: 'canvas.group.updated', payload: { group: touchedGroup } }] : []),
    ])
    return copiedNode
  },
  reassignNodeCategory: (nodeId, categoryId) => {
    const id = String(categoryId || '').trim()
    if (!isCategoryId(id)) return
    const existing = get().nodes.find((candidate) => candidate.id === nodeId)
    if (!existing || existing.categoryId === id) return
    set((state) => {
      const node = state.nodes.find((candidate) => candidate.id === nodeId)
      if (!node) return
      if (node.categoryId === id) return
      node.categoryId = id
      bumpPersistRevision(state)
    })
    emitCanvasGesture([{ type: 'canvas.node.updated', payload: { nodeId, patch: { categoryId: id } } }])
  },
  copyNodeToCategory: (nodeId, categoryId) => {
    const id = String(categoryId || '').trim()
    if (!isCategoryId(id)) return null
    const source = get().nodes.find((candidate) => candidate.id === nodeId)
    if (!source) return null
    const { id: _sourceId, categoryId: _sourceCategoryId, groupId: _sourceGroupId, ...rest } = source
    const copiedNode: GenerationCanvasNode = {
      ...rest,
      id: createClipboardNodeId(source.id),
      title: source.title ? `${source.title} 副本` : source.title,
      position: {
        x: source.position.x + CLIPBOARD_OFFSET,
        y: source.position.y + CLIPBOARD_OFFSET,
      },
      categoryId: id,
      derivedFrom: source.id,
      references: source.references ? [...source.references] : undefined,
      history: source.history ? [...source.history] : undefined,
      runs: source.runs ? [...source.runs] : undefined,
      meta: source.meta ? { ...source.meta } : undefined,
      size: source.size ? { ...source.size } : source.size,
    }
    pushUndoSnapshot(get())
    set((state) => {
      state.nodes.push(copiedNode)
      state.selectedNodeIds = [copiedNode.id]
      bumpPersistRevision(state)
      Object.assign(state, getHistoryFlags())
    })
    emitCanvasGesture([{ type: 'canvas.node.added', payload: { node: copiedNode } }])
    return copiedNode
  },
  deleteNode: (nodeId) => {
    const current = get()
    if (!current.nodes.some((candidate) => candidate.id === nodeId)) return
    pushUndoSnapshot(current)
    set((state) => {
      const next = removeNodes(state.nodes, state.edges, [nodeId])
      state.nodes = next.nodes
      state.edges = next.edges
      state.groups = state.groups.map((group) => ({
        ...group,
        nodeIds: group.nodeIds.filter((candidateNodeId) => candidateNodeId !== nodeId),
      }))
      state.selectedNodeIds = state.selectedNodeIds.filter((candidateNodeId) => candidateNodeId !== nodeId)
      bumpPersistRevision(state)
      Object.assign(state, getHistoryFlags())
    })
    // node.removed 只表达"删节点+其边"(deleteSelectedNodes 不清组,语义须分开);
    // 本 action 还清理了组成员 → 发受影响组的后态(影子期不改 store 行为,只如实记账)。
    const touchedGroups = get().groups.filter((group) => current.groups.some((before) => before.id === group.id && before.nodeIds.includes(nodeId)))
    emitCanvasGesture([
      { type: 'canvas.node.removed', payload: { nodeId } },
      ...touchedGroups.map((group) => ({ type: 'canvas.group.updated', payload: { group } })),
    ])
  },
})
