<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'
import type { GameData, SceneNode } from '@engine/types'
import { useGraphEditor } from './composables/useGraphEditor'
import SceneGraph from './components/SceneGraph.vue'
import NodeEditor from './components/NodeEditor.vue'
import PreviewPanel from './components/PreviewPanel.vue'

const editor = useGraphEditor()
const dirty = ref(false)
const fileInputRef = ref<HTMLInputElement | null>(null)

const sceneNodes = computed(() => {
  return Object.values(editor.gameData.value.scenes).map((s) => ({
    id: s.id,
    label: s.id,
  }))
})

const sceneEdges = computed(() => {
  const edges: { id: string; source: string; target: string; label?: string }[] = []
  for (const [id, scene] of Object.entries(editor.gameData.value.scenes)) {
    if (scene.choices) {
      let ci = 0
      for (const c of scene.choices) {
        if (c.targetScene) {
          edges.push({
            id: `${id}_choice_${ci}`,
            source: id,
            target: c.targetScene,
            label: c.text.slice(0, 10),
          })
        }
        ci++
      }
    }
    if (scene.nextScene) {
      edges.push({
        id: `${id}_next`,
        source: id,
        target: scene.nextScene,
        label: '→ 默认',
      })
    }
    if (scene.qte) {
      if (scene.qte.successScene) {
        edges.push({
          id: `${id}_qte_s`,
          source: id,
          target: scene.qte.successScene,
          label: 'QTE成功',
        })
      }
      if (scene.qte.failScene) {
        edges.push({
          id: `${id}_qte_f`,
          source: id,
          target: scene.qte.failScene,
          label: 'QTE失败',
        })
      }
    }
  }
  return edges
})

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

const sceneList = computed(() => editor.sceneList.value)

const previewVideoUrl = computed(() => {
  if (!selectedScene.value?.videoUrl) return null
  const url = selectedScene.value.videoUrl
  if (url.startsWith('/')) return url
  return '/' + url
})

function newNode() {
  const id = editor.addScene()
  editor.selectedNodeId.value = id
  dirty.value = true
}

function delNode(id: string) {
  editor.deleteScene(id)
  dirty.value = true
}

function onUpdateScene(id: string, partial: any) {
  editor.updateScene(id, partial)
  dirty.value = true
}

function onAddChoice(sourceId: string) {
  editor.addChoice(sourceId)
  dirty.value = true
}

function onUpdateChoice(sourceId: string, index: number, partial: any) {
  editor.updateChoice(sourceId, index, partial)
  dirty.value = true
}

function onDeleteChoice(sourceId: string, index: number) {
  editor.deleteChoice(sourceId, index)
  dirty.value = true
}

function onAddEdge(source: string, target: string) {
  const scene = editor.gameData.value.scenes[source]
  if (!scene) return
  if (!scene.choices) scene.choices = []
  scene.choices.push({
    text: `${source} → ${target}`,
    targetScene: target,
  })
  editor.gameData.value.scenes = { ...editor.gameData.value.scenes }
  dirty.value = true
}

async function exportJSON() {
  const data = editor.exportJSON()
  const json = JSON.stringify(data, null, 2)
  const blob = new Blob([json], { type: 'application/json' })
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = 'scenes.json'
  a.click()
  URL.revokeObjectURL(url)
  dirty.value = false
}

function importJSON() {
  fileInputRef.value?.click()
}

async function onFileSelected(e: Event) {
  const file = (e.target as HTMLInputElement).files?.[0]
  if (!file) return
  const text = await file.text()
  try {
    const data = JSON.parse(text) as GameData
    editor.loadJSON(data)
    dirty.value = false
  } catch (err) {
    alert('JSON 解析失败：' + (err as Error).message)
  }
}

async function loadDemo() {
  const resp = await fetch('/scenes/demo.json')
  const data = await resp.json()
  editor.loadJSON(data)
  dirty.value = false
}

onMounted(() => {
  loadDemo()
})
</script>

<template>
  <div class="editor-layout">
    <div class="toolbar">
      <span class="toolbar-title">剧情编辑器</span>
      <div class="toolbar-actions">
        <button @click="newNode">+ 新场景</button>
        <button @click="importJSON" class="secondary">导入 JSON</button>
        <button @click="exportJSON" class="secondary">导出 JSON</button>
        <button @click="loadDemo" class="secondary">加载示例</button>
        <span v-if="dirty" class="dirty-indicator">● 未保存</span>
      </div>
      <span class="toolbar-start">
        起始场景:
        <select
          :value="editor.startSceneId.value"
          @change="editor.startSceneId.value = ($event.target as HTMLSelectElement).value; dirty = true"
          class="start-select"
        >
          <option value="">-- 选择 --</option>
          <option v-for="n in sceneNodes" :key="n.id" :value="n.id">{{ n.label }}</option>
        </select>
      </span>
    </div>

    <div class="editor-main">
      <SceneGraph
        class="graph-area"
        :scene-nodes="sceneNodes"
        :scene-edges="sceneEdges"
        :start-scene="editor.startSceneId.value"
        :selected-node-id="editor.selectedNodeId.value"
        @select-node="editor.selectedNodeId.value = $event"
        @add-edge="onAddEdge"
      />

      <NodeEditor
        :scene="selectedScene"
        :scene-list="sceneList"
        @update="onUpdateScene"
        @add-choice="onAddChoice"
        @update-choice="onUpdateChoice"
        @delete-choice="onDeleteChoice"
        @delete-scene="delNode"
        @close="editor.selectedNodeId.value = null"
      />

      <PreviewPanel :video-url="previewVideoUrl" />
    </div>

    <input
      ref="fileInputRef"
      type="file"
      accept=".json"
      style="display:none"
      @change="onFileSelected"
    />
  </div>
</template>

<style>
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html, body {
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: #0a0a16;
  color: #ccc;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

#editor-app {
  width: 100%;
  height: 100%;
}
</style>

<style scoped>
.editor-layout {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
}

.toolbar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 16px;
  background: #111122;
  border-bottom: 1px solid rgba(255,255,255,0.06);
  flex-shrink: 0;
}

.toolbar-title {
  font-size: 15px;
  font-weight: 600;
  color: #ddd;
  letter-spacing: 1px;
  margin-right: 16px;
}

.toolbar-actions {
  display: flex;
  gap: 8px;
}

.toolbar-actions button {
  padding: 6px 14px;
  font-size: 12px;
  color: #ddd;
  background: rgba(255,255,255,0.08);
  border: 1px solid rgba(255,255,255,0.15);
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.15s;
}

.toolbar-actions button:hover {
  background: rgba(255,255,255,0.14);
}

.toolbar-actions button.secondary {
  color: #8cf;
  border-color: rgba(100,200,255,0.2);
  background: rgba(100,200,255,0.06);
}

.toolbar-start {
  margin-left: auto;
  font-size: 12px;
  color: #777;
  display: flex;
  align-items: center;
  gap: 6px;
}

.start-select {
  padding: 4px 8px;
  font-size: 12px;
  background: rgba(255,255,255,0.05);
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 3px;
  color: #ccc;
}

.dirty-indicator {
  font-size: 11px;
  color: #ff9800;
}

.editor-main {
  flex: 1;
  display: flex;
  overflow: hidden;
}

.graph-area {
  flex: 1;
  position: relative;
  min-height: 0;
}
</style>
