import { computed, ref } from 'vue'

/**
 * 编辑器命令栈：所有反射 API 写操作都包装为 `EditCommand`，做 / 撤销 双向。
 *
 * 设计要点：
 * - `apply` / `revert` 都是异步，让命令内部 await 反射 API。
 * - `apply` 内部可以记录新建对象的 id（如 createSite 返回 id），在 closure 里保存供 `revert` 使用。
 * - 栈深度默认 20（撤销最多 20 步），覆盖创建站点 / 配置站点字段 / 创建路径 / 删除 / 对齐 /
 *   字段复制 / 批量生成 / AI 落地 等所有走 `run()` 的写操作；超出旧命令直接丢弃。
 * - 批处理（如「矩阵生成」一次调用产生 N 条 create）会被包装为单个 `CompositeCommand`，
 *   一次撤销整体回退。
 *
 * 注意：本栈只跟踪通过 `do(...)` 提交的命令；如果直接调反射 API（不通过这里），栈不感知。
 * 推荐所有编辑器写操作通过此 composable 暴露的命令工厂触发。
 */

export interface EditCommand {
  /** 命令的人类可读描述（用于撤销提示气泡）。 */
  label: string
  /** 正向执行。可能产生副作用（创建/删除/修改对象，调反射 API）。 */
  apply: () => Promise<void> | void
  /** 反向撤销。要保证幂等：多次 revert 不应崩。 */
  revert: () => Promise<void> | void
}

export interface UseHistoryOptions {
  maxDepth?: number
  onChange?: () => void
}

export function useHistory(opts: UseHistoryOptions = {}) {
  const maxDepth = opts.maxDepth ?? 20

  const undoStack = ref<EditCommand[]>([])
  const redoStack = ref<EditCommand[]>([])
  const busy = ref(false)

  const canUndo = computed(() => undoStack.value.length > 0 && !busy.value)
  const canRedo = computed(() => redoStack.value.length > 0 && !busy.value)

  async function run(cmd: EditCommand) {
    if (busy.value) return
    busy.value = true
    try {
      await cmd.apply()
      undoStack.value = [...undoStack.value, cmd].slice(-maxDepth)
      redoStack.value = []
      opts.onChange?.()
    } finally {
      busy.value = false
    }
  }

  async function undo() {
    if (!canUndo.value) return
    const cmd = undoStack.value[undoStack.value.length - 1]!
    busy.value = true
    try {
      await cmd.revert()
      undoStack.value = undoStack.value.slice(0, -1)
      redoStack.value = [...redoStack.value, cmd].slice(-maxDepth)
      opts.onChange?.()
    } finally {
      busy.value = false
    }
  }

  async function redo() {
    if (!canRedo.value) return
    const cmd = redoStack.value[redoStack.value.length - 1]!
    busy.value = true
    try {
      await cmd.apply()
      redoStack.value = redoStack.value.slice(0, -1)
      undoStack.value = [...undoStack.value, cmd].slice(-maxDepth)
      opts.onChange?.()
    } finally {
      busy.value = false
    }
  }

  function clear() {
    undoStack.value = []
    redoStack.value = []
    opts.onChange?.()
  }

  /** 组合命令：把一组 sub-commands 当作整体撤销 / 重做。 */
  function composite(label: string, subs: EditCommand[]): EditCommand {
    return {
      label,
      apply: async () => { for (const s of subs) await s.apply() },
      revert: async () => {
        // 反向逐条 revert，确保依赖序无误（例如先删 track 再删 site）
        for (let i = subs.length - 1; i >= 0; i--) await subs[i]!.revert()
      }
    }
  }

  return {
    undoStack,
    redoStack,
    busy,
    canUndo,
    canRedo,
    maxDepth,
    undoCount: computed(() => undoStack.value.length),
    redoCount: computed(() => redoStack.value.length),
    run,
    undo,
    redo,
    clear,
    composite
  }
}
