<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue'
import { marked } from 'marked'
import { api, getToken, setToken, type DocMeta } from './lib/api'
import { AsrSession } from './lib/asr'

const docs = ref<DocMeta[]>([])
const activeId = ref<number | null>(null)
const title = ref('')
const content = ref('')
const inputText = ref('')
const status = ref('')
const statusErr = ref(false)
const busy = ref(false)
const recording = ref(false)

onMounted(() => applyLayoutVars())

// ====== 拖拽布局 ======
// sidebarW: 侧栏宽(px) | editorRatio: 源码/预览比例(0-1) | inputH: 输入框高(px)
const sidebarW = ref(parseInt(localStorage.getItem('write.sidebarW') || '260'))
const editorRatio = ref(parseFloat(localStorage.getItem('write.editorRatio') || '0.5'))
const inputH = ref(parseInt(localStorage.getItem('write.inputH') || '140'))

function applyLayoutVars(): void {
  const r = document.documentElement
  r.style.setProperty('--sidebar-w', sidebarW.value + 'px')
  r.style.setProperty('--editor-fr', editorRatio.value + 'fr')
  r.style.setProperty('--preview-fr', (1 - editorRatio.value) + 'fr')
  r.style.setProperty('--input-h', inputH.value + 'px')
}

type DragKind = 'sidebar' | 'editor' | 'input'
let dragKind: DragKind | null = null
let dragStartPos = 0
let dragStartVal = 0
let dragPaneW = 0

function startDrag(e: MouseEvent, kind: DragKind): void {
  dragKind = kind
  dragStartPos = kind === 'input' ? e.clientY : e.clientX
  if (kind === 'sidebar') {
    dragStartVal = sidebarW.value
  } else if (kind === 'editor') {
    dragStartVal = editorRatio.value
    const pane = (e.currentTarget as HTMLElement).parentElement
    dragPaneW = pane ? pane.offsetWidth : window.innerWidth
  } else {
    dragStartVal = inputH.value
  }
  document.body.style.cursor = kind === 'input' ? 'row-resize' : 'col-resize'
  document.body.style.userSelect = 'none'
  window.addEventListener('mousemove', onDrag)
  window.addEventListener('mouseup', endDrag)
  e.preventDefault()
}
function onDrag(e: MouseEvent): void {
  if (!dragKind) return
  if (dragKind === 'input') {
    const dy = dragStartPos - e.clientY  // 向上拖 = 高度变大
    inputH.value = Math.max(60, Math.min(window.innerHeight - 200, dragStartVal + dy))
  } else {
    const dx = e.clientX - dragStartPos
    if (dragKind === 'sidebar') {
      sidebarW.value = Math.max(180, Math.min(500, dragStartVal + dx))
    } else {
      const newR = dragStartVal + dx / Math.max(1, dragPaneW)
      editorRatio.value = Math.max(0.15, Math.min(0.85, newR))
    }
  }
  applyLayoutVars()
}
function endDrag(): void {
  if (!dragKind) return
  localStorage.setItem('write.sidebarW', String(sidebarW.value))
  localStorage.setItem('write.editorRatio', String(editorRatio.value))
  localStorage.setItem('write.inputH', String(inputH.value))
  dragKind = null
  document.body.style.cursor = ''
  document.body.style.userSelect = ''
  window.removeEventListener('mousemove', onDrag)
  window.removeEventListener('mouseup', endDrag)
}

const needToken = ref(!getToken())
const tokenInput = ref('')

// mobile UI state
const drawerOpen = ref(false)
const mobilePane = ref<'editor' | 'preview'>('preview')

let saveTimer: ReturnType<typeof setTimeout> | null = null
let asr: AsrSession | null = null
let voiceBase = '' // committed transcript so partials don't clobber it

function setStatus(msg: string, err = false): void {
  status.value = msg
  statusErr.value = err
  if (msg && !err) {
    setTimeout(() => {
      if (status.value === msg) status.value = ''
    }, 3000)
  }
}

async function loadDocs(): Promise<void> {
  try {
    docs.value = await api.listDocs()
    if (docs.value.length && activeId.value === null) {
      await selectDoc(docs.value[0].id)
    }
  } catch (e: any) {
    if (String(e).includes('401')) {
      needToken.value = true
    } else {
      setStatus(String(e), true)
    }
  }
}

async function selectDoc(id: number): Promise<void> {
  try {
    const d = await api.getDoc(id)
    activeId.value = d.id
    title.value = d.title
    content.value = d.content
    drawerOpen.value = false  // close drawer on mobile after pick
  } catch (e: any) {
    setStatus(String(e), true)
  }
}

async function newDoc(): Promise<void> {
  try {
    const d = await api.createDoc('未命名文档')
    await loadDocs()
    await selectDoc(d.id)
  } catch (e: any) {
    setStatus(String(e), true)
  }
}

async function deleteDoc(id: number): Promise<void> {
  if (!confirm('删除这个文档？')) return
  await api.deleteDoc(id)
  if (activeId.value === id) activeId.value = null
  await loadDocs()
}

function scheduleSave(): void {
  if (saveTimer) clearTimeout(saveTimer)
  saveTimer = setTimeout(saveNow, 800)
}

async function saveNow(): Promise<void> {
  if (activeId.value === null) return
  try {
    await api.saveContent(activeId.value, content.value, title.value)
    // refresh doc list ordering
    docs.value = await api.listDocs()
  } catch (e: any) {
    setStatus(String(e), true)
  }
}

watch(content, scheduleSave)
watch(title, scheduleSave)

async function sendMessage(): Promise<void> {
  if (!inputText.value.trim() || busy.value || activeId.value === null) return
  const text = inputText.value.trim()
  busy.value = true
  setStatus('思考中…')
  inputText.value = ''
  try {
    // Ensure latest edits are saved before claude touches the file
    if (saveTimer) { clearTimeout(saveTimer); saveTimer = null }
    await api.saveContent(activeId.value, content.value, title.value)
    const r = await api.chat(activeId.value, text)
    content.value = r.content
    const cost = r.cost_usd?.toFixed(4) ?? '?'
    const turns = r.turns ?? '?'
    setStatus(`${r.reply || '完成'} • cost=$${cost} turns=${turns}`)
  } catch (e: any) {
    setStatus(String(e), true)
  } finally {
    busy.value = false
    docs.value = await api.listDocs()
  }
}

function onInputKeydown(e: KeyboardEvent): void {
  if (e.key === 'Enter' && !e.shiftKey && !e.isComposing) {
    e.preventDefault()
    sendMessage()
  }
}

async function toggleMic(): Promise<void> {
  if (recording.value) {
    // stop
    recording.value = false
    await asr?.stop()
    asr = null
  } else {
    voiceBase = inputText.value
    if (voiceBase && !voiceBase.endsWith(' ')) voiceBase += ' '
    asr = new AsrSession((ev) => {
      if (ev.kind === 'error') {
        setStatus(`ASR: ${ev.message}`, true)
        return
      }
      // The Qwen3-ASR server returns cumulative text per partial.
      inputText.value = voiceBase + ev.text
      if (ev.kind === 'final') {
        voiceBase = inputText.value
        if (voiceBase && !voiceBase.endsWith(' ')) voiceBase += ' '
      }
    })
    try {
      await asr.start()
      recording.value = true
      setStatus('🎙 录音中…再点一次停止')
    } catch (e: any) {
      recording.value = false
      asr = null
      setStatus(String(e), true)
    }
  }
}

function saveToken(): void {
  if (!tokenInput.value.trim()) return
  setToken(tokenInput.value.trim())
  needToken.value = false
  tokenInput.value = ''
  loadDocs()
}

const renderedPreview = computed(() => {
  return marked.parse(content.value || '*（空文档）*', { breaks: true, gfm: true }) as string
})

onMounted(() => {
  if (!needToken.value) loadDocs()
})
</script>

<template>
  <div v-if="needToken" class="modal-backdrop">
    <div class="modal">
      <h2>请输入 passphrase</h2>
      <input
        v-model="tokenInput"
        type="password"
        placeholder="WRITE_PASSPHRASE"
        @keydown.enter="saveToken"
      />
      <div class="modal-actions">
        <button class="btn primary" @click="saveToken">进入</button>
      </div>
    </div>
  </div>

  <div
    class="app"
    :class="{ 'drawer-open': drawerOpen, 'pane-editor': mobilePane === 'editor', 'pane-preview': mobilePane === 'preview' }"
    v-show="!needToken"
  >
    <div class="mobile-bar">
      <button class="hamburger" @click="drawerOpen = true" aria-label="open sidebar">☰</button>
      <div class="doc-title">
        <input
          v-if="activeId !== null"
          v-model="title"
          placeholder="文档标题"
          style="border:none;outline:none;background:transparent;font:inherit;font-weight:600;width:100%;"
        />
        <span v-else style="color:#888;">没选中文档</span>
      </div>
      <div class="pane-tabs">
        <button :class="{ active: mobilePane === 'editor' }" @click="mobilePane = 'editor'" title="源码">✏</button>
        <button :class="{ active: mobilePane === 'preview' }" @click="mobilePane = 'preview'" title="预览">👁</button>
      </div>
    </div>

    <div class="sidebar-backdrop" @click="drawerOpen = false"></div>

    <aside class="sidebar">
      <div class="sidebar-header">
        <h1>✍ write</h1>
        <button class="new-btn" @click="newDoc">+ 新建</button>
      </div>
      <div class="doc-list">
        <div
          v-for="d in docs"
          :key="d.id"
          class="doc-item"
          :class="{ active: d.id === activeId }"
          @click="selectDoc(d.id)"
        >
          <div class="title">{{ d.title || '未命名' }}</div>
          <button class="del" @click.stop="deleteDoc(d.id)">删</button>
        </div>
        <div v-if="!docs.length" style="padding: 12px; color: #888; font-size: 12px;">
          还没有文档，点 + 新建一个。
        </div>
      </div>
    </aside>

    <div class="splitter splitter-main" @mousedown="startDrag($event, 'sidebar')" title="拖动调整侧栏宽度"></div>

    <main class="workspace">
      <div class="editor-pane">
        <div class="title-row">
          <input
            v-model="title"
            placeholder="文档标题"
            :disabled="activeId === null"
          />
        </div>
        <div class="editor">
          <textarea
            v-model="content"
            placeholder="markdown 源码（此处可手动编辑，也可以让 AI 改）"
            :disabled="activeId === null"
          ></textarea>
        </div>
        <div class="splitter splitter-editor" @mousedown="startDrag($event, 'editor')" title="拖动调整源码/预览比例"></div>
        <div class="preview" v-html="renderedPreview"></div>
      </div>

      <div class="splitter splitter-input" @mousedown="startDrag($event, 'input')" title="拖动调整输入框高度"></div>

      <div class="input-bar">
        <div class="input-row">
          <textarea
            v-model="inputText"
            placeholder="对话框（Enter 发送 / Shift+Enter 换行）"
            :disabled="busy || activeId === null"
            @keydown="onInputKeydown"
          ></textarea>
          <div class="btn-tray">
            <button
              class="btn mic"
              :class="{ on: recording }"
              :disabled="busy || activeId === null"
              @click="toggleMic"
              :title="recording ? '停止录音' : '开始语音输入'"
            >{{ recording ? '◼' : '🎙' }}</button>
            <button
              class="btn primary"
              :disabled="busy || !inputText.trim() || activeId === null"
              @click="sendMessage"
            >发送</button>
          </div>
        </div>
        <div class="status" :class="{ err: statusErr }">{{ status }}</div>
      </div>
    </main>
  </div>
</template>
