import { defineStore } from 'pinia'
import { shallowRef, ref, computed, triggerRef } from 'vue'
import type { GameData, SceneNode, Choice } from '@engine/types'
import { putVersion, getVersions } from '../db/editorDB'

export interface AIDiff {
  added: string[]
  modified: string[]
  deleted: string[]
  globalFields: string[]
}

interface EditorVersion {
  id?: number
  sourcePath: string
  timestamp: number
  label: string
  gameData: GameData
}

export const useEditorStore = defineStore('editor', () => {
  const gameData = shallowRef<GameData>({ scenes: {}, startScene: '', variables: {} })
  const selectedNodeId = ref<string | null>(null)
  const startSceneId = ref('')
  const dirty = ref(false)
  const sourcePath = ref('/scenes/demo.json')
  const deepseekKey = ref(localStorage.getItem('deepseek_key') || '')
  const showAIPanel = ref(false)
  const aiCollapsed = ref(true)
  const aiSessionId = ref('')
  const aiChanges = ref<AIDiff | null>(null)
  const versions = ref<EditorVersion[]>([])

  const selectedScene = computed(() => {
    if (!selectedNodeId.value) return null
    return gameData.value.scenes[selectedNodeId.value] ?? null
  })

  function markDirty() { dirty.value = true }

  function loadJSON(json: GameData) {
    gameData.value = JSON.parse(JSON.stringify(json))
    triggerRef(gameData)
    startSceneId.value = json.startScene || ''
    selectedNodeId.value = null
    dirty.value = false
  }

  function exportJSON(): GameData {
    return JSON.parse(JSON.stringify({ ...gameData.value, startScene: startSceneId.value }))
  }

  function generateId(): string {
    let i = Object.keys(gameData.value.scenes).length + 1
    while (gameData.value.scenes[`scene_${i}`]) i++
    return `scene_${i}`
  }

  function addScene(): string {
    const id = generateId()
    gameData.value = {
      ...gameData.value,
      scenes: {
        ...gameData.value.scenes,
        [id]: { id, videoUrl: '', choices: [], nextScene: '', subtitleUrl: '', onEnter: [] },
      },
    }
    triggerRef(gameData)
    dirty.value = true
    autoSave()
    return id
  }

  function deleteScene(id: string) {
    if (startSceneId.value === id) return
    const nextScenes = { ...gameData.value.scenes }
    delete nextScenes[id]
    for (const key of Object.keys(nextScenes)) {
      const s = nextScenes[key]
      if (s.choices) nextScenes[key] = { ...s, choices: s.choices.filter((c) => c.targetScene !== id) }
      if (Array.isArray(s.nextScene)) {
        nextScenes[key] = { ...s, nextScene: s.nextScene.filter((r) => r.targetScene !== id) }
      } else if (s.nextScene === id) {
        nextScenes[key] = { ...nextScenes[key], nextScene: '' }
      }
      if (s.qte) {
        const qte = { ...s.qte }
        let changed = false
        if (qte.successScene === id) { qte.successScene = ''; changed = true }
        if (qte.failScene === id) { qte.failScene = ''; changed = true }
        if (changed) nextScenes[key] = { ...nextScenes[key], qte }
      }
    }
    gameData.value = { ...gameData.value, scenes: nextScenes }
    triggerRef(gameData)
    dirty.value = true
    if (selectedNodeId.value === id) selectedNodeId.value = null
    autoSave()
  }

  function updateScene(id: string, partial: Partial<SceneNode>) {
    const scene = gameData.value.scenes[id]
    if (!scene) return
    gameData.value = {
      ...gameData.value,
      scenes: { ...gameData.value.scenes, [id]: { ...scene, ...partial } },
    }
    triggerRef(gameData)
    dirty.value = true
    autoSave()
  }

  function addChoice(sourceId: string) {
    const scene = gameData.value.scenes[sourceId]
    if (!scene) return
    gameData.value = {
      ...gameData.value,
      scenes: {
        ...gameData.value.scenes,
        [sourceId]: { ...scene, choices: [...(scene.choices || []), { text: '\u65b0\u9009\u9879', targetScene: '' }] },
      },
    }
    triggerRef(gameData)
    dirty.value = true
    autoSave()
  }

  function updateChoice(sourceId: string, index: number, partial: Partial<Choice>) {
    const scene = gameData.value.scenes[sourceId]
    if (!scene?.choices) return
    const newChoices = scene.choices.map((c, i) => (i === index ? { ...c, ...partial } : c))
    gameData.value = {
      ...gameData.value,
      scenes: { ...gameData.value.scenes, [sourceId]: { ...scene, choices: newChoices } },
    }
    triggerRef(gameData)
    dirty.value = true
    autoSave()
  }

  function deleteChoice(sourceId: string, index: number) {
    const scene = gameData.value.scenes[sourceId]
    if (!scene?.choices) return
    gameData.value = {
      ...gameData.value,
      scenes: {
        ...gameData.value.scenes,
        [sourceId]: { ...scene, choices: scene.choices.filter((_, i) => i !== index) },
      },
    }
    triggerRef(gameData)
    dirty.value = true
  }

  function setSourcePath(p: string) { sourcePath.value = p; localStorage.setItem('editor_last_source', p) }

  function setDeepseekKey(k: string) { deepseekKey.value = k; localStorage.setItem('deepseek_key', k) }

  function setAISessionId(id: string) { aiSessionId.value = id; localStorage.setItem('editor_ai_session', id) }

  function clearAISession() { aiSessionId.value = ''; localStorage.removeItem('editor_ai_session') }

  async function autoSave() {
    try {
      await fetch('/api/save', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ path: sourcePath.value, data: gameData.value }),
      })
    } catch { /* dev server not running */ }
  }

  async function saveVersion(label: string) {
    try {
      await putVersion({
        sourcePath: sourcePath.value,
        timestamp: Date.now(),
        label,
        gameData: JSON.parse(JSON.stringify(gameData.value)),
      })
      await loadVersions()
    } catch {}
  }

  async function restoreVersion(idx: number) {
    const v = versions.value[idx]
    if (!v) return
    gameData.value = v.gameData
    startSceneId.value = v.gameData.startScene || ''
    selectedNodeId.value = null
    aiChanges.value = null
    dirty.value = false
    autoSave()
  }

  async function loadVersions() {
    try {
      versions.value = await getVersions(sourcePath.value)
    } catch {
      versions.value = []
    }
  }

  function clearAIMarkers() {
    aiChanges.value = null
  }

  async function reloadFromDisk(oldGameData?: GameData) {
    try {
      const resp = await fetch(sourcePath.value)
      const newData = await resp.json()
      if (oldGameData) {
        const { scenes: newScenes, ...newGlobal } = newData
        const { scenes: oldScenes, ...oldGlobal } = oldGameData
        const diff: AIDiff = { added: [], modified: [], deleted: [], globalFields: [] }
        for (const id of Object.keys(newScenes)) {
          if (!oldScenes[id]) diff.added.push(id)
          else if (JSON.stringify(oldScenes[id]) !== JSON.stringify(newScenes[id])) diff.modified.push(id)
        }
        for (const id of Object.keys(oldScenes)) {
          if (!newScenes[id]) diff.deleted.push(id)
        }
        for (const key of Object.keys({ ...(oldGlobal as any), ...(newGlobal as any) })) {
          if (JSON.stringify((oldGlobal as any)[key]) !== JSON.stringify((newGlobal as any)[key])) {
            diff.globalFields.push(key)
          }
        }
        aiChanges.value = diff
      }
      gameData.value = newData
      selectedNodeId.value = null
      clearAISession()
    } catch { /* failed to reload */ }
  }

  return {
    gameData, selectedNodeId, selectedScene, startSceneId, dirty, sourcePath,
    deepseekKey, showAIPanel, aiSessionId, aiCollapsed, aiChanges, versions,
    markDirty, loadJSON, exportJSON, addScene, deleteScene,
    updateScene, addChoice, updateChoice, deleteChoice, generateId,
    setSourcePath, setDeepseekKey, setAISessionId, clearAISession, autoSave, reloadFromDisk,
    saveVersion, restoreVersion, loadVersions, clearAIMarkers,
  }
})
