<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { invoke } from '@tauri-apps/api/core'
import { open, save } from '@tauri-apps/plugin-dialog'
import { listen, type UnlistenFn } from '@tauri-apps/api/event'
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
import TaskQueue from './components/TaskQueue.vue'
import SubtitleEditor from './components/SubtitleEditor.vue'
import { useTaskStore } from './stores/tasks'
import type { DefaultModelPaths, OutputMode, TargetLanguage, TranslationConfig } from './lib/types'

const { t, locale } = useI18n()
const SOURCE_LANGUAGE_OPTIONS = [
  { value: 'zh', label: '中文' },
  { value: 'en', label: 'English' },
  { value: 'ja', label: '日本語' },
  { value: 'ko', label: '한국어' },
  { value: 'fr', label: 'Français' },
  { value: 'de', label: 'Deutsch' },
  { value: 'es', label: 'Español' },
  { value: 'ru', label: 'Русский' },
  { value: 'it', label: 'Italiano' },
  { value: 'pt', label: 'Português' },
  { value: 'ar', label: 'العربية' },
  { value: 'tr', label: 'Türkçe' },
  { value: 'vi', label: 'Tiếng Việt' },
  { value: 'id', label: 'Bahasa Indonesia' },
  { value: 'th', label: 'ไทย' },
  { value: 'hi', label: 'हिन्दी' },
  { value: 'uk', label: 'Українська' },
  { value: 'pl', label: 'Polski' },
  { value: 'nl', label: 'Nederlands' },
]
const MEDIA_EXTENSIONS = [
  'mp3',
  'wav',
  'm4a',
  'flac',
  'aac',
  'ogg',
  'opus',
  'mp4',
  'm4v',
  'mkv',
  'flv',
  'mov',
  'avi',
  'wmv',
  'webm',
  'ts',
  'm2ts',
  'mpeg',
  'mpg',
] as const
const FREE_API_KEY_URL = 'https://docs.bigmodel.cn/cn/guide/models/free/glm-4.7-flash'

const taskStore = useTaskStore()
const targetLang = ref<TargetLanguage>('zh')
const outputMode = ref<OutputMode>('translate')
const sourceLang = ref('auto')
const bilingualOutput = ref(true)
const whisperModelPath = ref('')
const vadModelPath = ref('')
const defaultModelPaths = ref<DefaultModelPaths | null>(null)
const translationConfig = ref<TranslationConfig>({
  apiBase: localStorage.getItem('llm.apiBase') ?? 'https://open.bigmodel.cn/api/paas/v4',
  apiKey: localStorage.getItem('llm.apiKey') ?? '',
  model: localStorage.getItem('llm.model') ?? 'GLM-4.7-Flash',
  batchSize: Number(localStorage.getItem('llm.batchSize') ?? '12'),
  contextSize: Number(localStorage.getItem('llm.contextSize') ?? '5'),
})
const pending = ref(false)
const feedback = ref('')
const feedbackTone = ref<'normal' | 'error'>('normal')
const showAdvanced = ref(false)
const isDragging = ref(false)
const showApiKeyDialog = ref(false)
const apiKeyUrlCopied = ref(false)
const authorUrlCopied = ref(false)
let unlistenMenuAction: UnlistenFn | null = null
let dragDropUnlistenFn: UnlistenFn | null = null
let authorCopyTimer: ReturnType<typeof setTimeout> | null = null

const selectedTask = computed(() => taskStore.selectedTask)
const hasTranslationKey = computed(() => translationConfig.value.apiKey.trim().length > 0)

onMounted(() => {
  taskStore.initialize()
  void loadDefaultModelPaths()
  void bindMenuActions()
  void initDragDrop()
})

onUnmounted(() => {
  if (unlistenMenuAction) {
    unlistenMenuAction()
    unlistenMenuAction = null
  }
  if (dragDropUnlistenFn) {
    dragDropUnlistenFn()
    dragDropUnlistenFn = null
  }
  if (authorCopyTimer) {
    clearTimeout(authorCopyTimer)
    authorCopyTimer = null
  }
})

watch(locale, (newLocale) => {
  localStorage.setItem('locale', newLocale)
  if (outputMode.value === 'translate' && !hasTranslationKey.value) {
    showMissingApiKeyFeedback()
  }
})

watch([outputMode, hasTranslationKey], ([mode, hasKey]) => {
  if (mode === 'translate' && !hasKey) {
    showMissingApiKeyFeedback()
    return
  }

  if (feedbackTone.value === 'error' && feedback.value === t('app.feedback.noApiKey')) {
    feedbackTone.value = 'normal'
    feedback.value = ''
  }
})

function persistTranslationConfig() {
  localStorage.setItem('llm.apiBase', translationConfig.value.apiBase)
  localStorage.setItem('llm.apiKey', translationConfig.value.apiKey)
  localStorage.setItem('llm.model', translationConfig.value.model)
  localStorage.setItem('llm.batchSize', String(translationConfig.value.batchSize))
  localStorage.setItem('llm.contextSize', String(translationConfig.value.contextSize))
}

function resetModelPaths() {
  whisperModelPath.value = defaultModelPaths.value?.whisperModelPath ?? ''
  vadModelPath.value = defaultModelPaths.value?.vadModelPath ?? ''
  feedbackTone.value = 'normal'
  feedback.value = t('app.feedback.restoredDefaults')
}

function showMissingApiKeyFeedback() {
  feedbackTone.value = 'error'
  feedback.value = t('app.feedback.noApiKey')
}

function freeApiKeyHint() {
  if (locale.value === 'zh-CN') {
    return '开始翻译任务前需要先填写 LLM API Key。你可以选择下面任意一种方式。'
  }

  return 'An LLM API Key is required before starting a translation task. Choose any of the options below.'
}

function openApiKeyDialog() {
  showMissingApiKeyFeedback()
  apiKeyUrlCopied.value = false
  showApiKeyDialog.value = true
}

async function copyFreeApiKeyUrl() {
  await navigator.clipboard.writeText(FREE_API_KEY_URL)
  apiKeyUrlCopied.value = true
}

async function copyAuthorUrl() {
  await navigator.clipboard.writeText('https://kuraa.cc')
  authorUrlCopied.value = true
  if (authorCopyTimer) {
    clearTimeout(authorCopyTimer)
  }
  authorCopyTimer = setTimeout(() => {
    authorUrlCopied.value = false
    authorCopyTimer = null
  }, 1400)
}

function acknowledgeApiKeyDialog() {
  showApiKeyDialog.value = false
  outputMode.value = 'source'
}

async function loadDefaultModelPaths() {
  try {
    const paths = await invoke<DefaultModelPaths>('get_default_model_paths')
    defaultModelPaths.value = paths
    if (!whisperModelPath.value) {
      whisperModelPath.value = paths.whisperModelPath
    }
    if (!vadModelPath.value) {
      vadModelPath.value = paths.vadModelPath
    }
  } catch (error) {
    feedback.value =
      error instanceof Error ? `${t('app.feedback.loadModelError')}${error.message}` : t('app.feedback.loadModelErrorFallback')
  }
}

async function bindMenuActions() {
  unlistenMenuAction = await listen<string>('menu:action', async ({ payload }) => {
    switch (payload) {
      case 'pick-files':
        await handlePickFiles()
        break
      case 'export-srt':
        await handleExport('srt')
        break
      case 'export-vtt':
        await handleExport('vtt')
        break
      case 'export-ass':
        await handleExport('ass')
        break
      case 'toggle-advanced':
        showAdvanced.value = !showAdvanced.value
        break
      case 'toggle-bilingual':
        bilingualOutput.value = !bilingualOutput.value
        feedback.value = bilingualOutput.value ? t('app.feedback.bilingualOn') : t('app.feedback.bilingualOff')
        break
      case 'reset-models':
        resetModelPaths()
        break
      case 'set-lang-zh-CN':
        locale.value = 'zh-CN'
        break
      case 'set-lang-en':
        locale.value = 'en'
        break
      default:
        break
    }
  })
}

async function submitFiles(filePaths: string[]) {
  if (outputMode.value === 'translate' && !hasTranslationKey.value) {
    openApiKeyDialog()
    return
  }

  pending.value = true
  feedbackTone.value = 'normal'
  feedback.value = ''

  try {
    for (const filePath of filePaths) {
      await taskStore.startTask({
        filePath,
        sourceLang: sourceLang.value === 'auto' ? null : sourceLang.value,
        targetLang: targetLang.value,
        outputMode: outputMode.value,
        bilingualOutput: bilingualOutput.value,
        translationConfig: outputMode.value === 'translate' ? translationConfig.value : null,
        whisperModelPath: whisperModelPath.value || null,
        vadModelPath: vadModelPath.value || null,
      })
    }
    feedback.value = t('app.feedback.submitted', { count: filePaths.length })
  } catch (error) {
    feedback.value = error instanceof Error ? error.message : t('app.feedback.submitFailed')
  } finally {
    pending.value = false
  }
}

async function handlePickFiles() {
  try {
    feedbackTone.value = 'normal'
    feedback.value = ''
    persistTranslationConfig()
    const selected = await open({
      multiple: true,
      directory: false,
      title: t('app.pickFiles'),
      filters: [
        {
          name: t('app.mediaFiles'),
          extensions: [...MEDIA_EXTENSIONS],
        },
      ],
    })

    if (!selected) return

    const filePaths = Array.isArray(selected) ? selected : [selected]
    if (filePaths.length === 0) return

    await submitFiles(filePaths)
  } catch (error) {
    feedback.value = error instanceof Error ? `${t('app.feedback.dialogError')}${error.message}` : t('app.feedback.dialogErrorFallback')
  }
}

function getFileExtension(filePath: string): string {
  const lastDot = filePath.lastIndexOf('.')
  if (lastDot === -1) return ''
  return filePath.slice(lastDot + 1).toLowerCase()
}

function isMediaFile(filePath: string): boolean {
  const ext = getFileExtension(filePath)
  return MEDIA_EXTENSIONS.includes(ext as typeof MEDIA_EXTENSIONS[number])
}

async function initDragDrop() {
  try {
    dragDropUnlistenFn = await getCurrentWebviewWindow().onDragDropEvent((event) => {
      if (event.payload.type === 'enter' || event.payload.type === 'over') {
        isDragging.value = true
      } else if (event.payload.type === 'leave') {
        isDragging.value = false
      } else if (event.payload.type === 'drop') {
        isDragging.value = false
        const mediaFiles = event.payload.paths.filter(isMediaFile)
        if (mediaFiles.length === 0) {
          feedback.value = t('app.feedback.noMediaFiles')
          return
        }
        if (mediaFiles.length !== event.payload.paths.length) {
          const skipped = event.payload.paths.length - mediaFiles.length
          feedback.value = t('app.feedback.someFilesSkipped', { count: skipped })
        }
        void submitFiles(mediaFiles)
      }
    })
  } catch (error) {
    console.error('Failed to initialize drag-drop:', error)
  }
}

async function handleFiles(event: Event) {
  const input = event.target as HTMLInputElement
  const files = Array.from(input.files ?? [])
  const filePaths = files
    .map((file) => (file as File & { path?: string }).path)
    .filter((path): path is string => Boolean(path))

  if (filePaths.length === 0) {
    await handlePickFiles()
    input.value = ''
    return
  }

  await submitFiles(filePaths)
  input.value = ''
}

function getDefaultExportPath(filePath: string, format: 'srt' | 'vtt' | 'ass') {
  const separatorIndex = Math.max(filePath.lastIndexOf('/'), filePath.lastIndexOf('\\'))
  const directory = separatorIndex >= 0 ? filePath.slice(0, separatorIndex + 1) : ''
  const fileName = separatorIndex >= 0 ? filePath.slice(separatorIndex + 1) : filePath
  const stem = fileName.replace(/\.[^./\\]*$/, '') || 'subtitle'
  return `${directory}${stem}.${format}`
}

async function handleExport(format: 'srt' | 'vtt' | 'ass') {
  if (!selectedTask.value) return
  const outputPath = await save({
    title: t('app.exportSubtitles'),
    defaultPath: getDefaultExportPath(selectedTask.value.filePath, format),
    filters: [
      {
        name: format.toUpperCase(),
        extensions: [format],
      },
    ],
  })

  if (!outputPath) return

  const output = await taskStore.exportTask(selectedTask.value.id, format, outputPath)
  feedback.value = output
}

async function handleRetryTranslate(taskId: string) {
  persistTranslationConfig()
  if (!translationConfig.value.apiKey.trim()) {
    feedbackTone.value = 'error'
    feedback.value = t('app.feedback.noApiKey')
    return
  }
  try {
    await taskStore.retryTranslation(taskId, translationConfig.value)
    feedbackTone.value = 'normal'
    feedback.value = t('app.feedback.translationStarted')
  } catch (error) {
    feedback.value = error instanceof Error ? error.message : t('app.feedback.translationFailed')
  }
}

async function handleTranslateFromEditor() {
  if (!selectedTask.value) return
  await handleRetryTranslate(selectedTask.value.id)
}
</script>

<template>
  <main class="app-shell">
    <Transition name="drag">
      <div v-if="isDragging" class="drag-overlay">
        <div class="drag-overlay-content">
          <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
            <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
            <polyline points="17 8 12 3 7 8"/>
            <line x1="12" y1="3" x2="12" y2="15"/>
          </svg>
          <span>{{ $t('app.feedback.dropHint') }}</span>
        </div>
      </div>
    </Transition>
    <Transition name="dialog">
      <div v-if="showApiKeyDialog" class="dialog-overlay" @click.self="showApiKeyDialog = false">
        <section class="api-key-dialog" role="dialog" aria-modal="true" :aria-label="$t('app.llm.apiKey')">
          <div class="dialog-header">
            <div>
              <strong>{{ $t('app.llm.apiKey') }}</strong>
              <p>{{ freeApiKeyHint() }}</p>
              <ol class="api-key-options">
                <li>
                  <span class="option-icon self-host"></span>
                  <span>{{ locale === 'zh-CN' ? '自建大模型服务，填入兼容 OpenAI 的 API 地址和 Key。' : 'Host your own model service with an OpenAI-compatible API URL and key.' }}</span>
                </li>
                <li>
                  <span class="option-icon provider"></span>
                  <span>{{ locale === 'zh-CN' ? '购买成熟服务，DeepSeek、ChatGPT、Gemini 都可以。' : 'Use a mature paid service such as DeepSeek, ChatGPT, or Gemini.' }}</span>
                </li>
                <li>
                  <span class="option-icon free"></span>
                  <span>
                    {{ locale === 'zh-CN' ? '推荐免费的' : 'Recommended free option:' }}
                    <button class="api-key-link" type="button" @click="copyFreeApiKeyUrl">GLM-4.7-Flash</button>
                    <span class="copy-hint" :class="{ copied: apiKeyUrlCopied }">
                      {{ apiKeyUrlCopied ? (locale === 'zh-CN' ? '已复制链接' : 'Link copied') : (locale === 'zh-CN' ? '点击复制申请链接' : 'click to copy link') }}
                    </span>
                  </span>
                </li>
              </ol>
            </div>
            <button class="dialog-close" type="button" aria-label="Close" @click="showApiKeyDialog = false">×</button>
          </div>
          <div class="dialog-actions">
            <button class="button" type="button" @click="acknowledgeApiKeyDialog">
              {{ locale === 'zh-CN' ? '明白了，先转原文' : 'Got it, use source mode' }}
            </button>
          </div>
        </section>
      </div>
    </Transition>
    <section class="topbar panel">
      <div class="toolbar-main">
        <div class="toolbar-title">
          <strong>CrossSubtitle</strong>
          <span class="credit-line">
            by <button class="author-link" type="button" @click="copyAuthorUrl">kuraa</button>
            <span v-if="authorUrlCopied" class="author-copy-hint">{{ $t('app.feedback.copied') }}</span>
          </span>
        </div>
        <div class="toolbar-actions">
          <button class="button secondary toggle-button" type="button" @click="showAdvanced = !showAdvanced">
            {{ showAdvanced ? $t('app.collapse') : $t('app.settings') }}
          </button>
          <button class="button" type="button" :disabled="pending" @click="handlePickFiles">
            {{ pending ? $t('app.submitting') : $t('app.addTask') }}
          </button>
        </div>
      </div>

      <div class="workspace-toolbar">
        <div class="toolbar-group">
          <div class="form-grid">
            <label class="field">
              <span>{{ $t('app.mode') }}</span>
              <select v-model="outputMode">
                <option value="source">{{ $t('app.source') }}</option>
                <option value="translate">{{ $t('app.translate') }}</option>
              </select>
            </label>
            <label v-if="outputMode === 'translate'" class="field">
              <span>{{ $t('app.targetLang') }}</span>
              <select v-model="targetLang">
                <option value="zh">{{ $t('app.chinese') }}</option>
                <option value="en">{{ $t('app.english') }}</option>
              </select>
            </label>
            <label class="field">
              <span>{{ $t('app.sourceLang') }}</span>
              <select v-model="sourceLang">
                <option value="auto">{{ $t('app.autoDetect') }}</option>
                <option
                  v-for="option in SOURCE_LANGUAGE_OPTIONS"
                  :key="option.value"
                  :value="option.value"
                >
                  {{ option.label }}
                </option>
              </select>
            </label>
            <label class="check desktop-check">
              <input v-model="bilingualOutput" type="checkbox" />
              <span>{{ $t('app.bilingualExport') }}</span>
            </label>
          </div>
        </div>

        <p v-if="feedback" class="feedback status-text" :class="{ error: feedbackTone === 'error' }">{{ feedback }}</p>
      </div>

      <div v-if="showAdvanced" class="advanced-shell">
        <span class="group-title">{{ $t('app.advanced') }}</span>
        <div class="advanced-grid">
          <template v-if="outputMode === 'translate'">
            <label class="field wide">
              <span>{{ $t('app.llm.apiBase') }}</span>
              <input v-model="translationConfig.apiBase" placeholder="https://api.openai.com/v1" />
            </label>
            <label class="field wide">
              <span>{{ $t('app.llm.apiKey') }}</span>
              <input v-model="translationConfig.apiKey" type="password" placeholder="sk-..." />
            </label>
            <label class="field">
              <span>{{ $t('app.llm.model') }}</span>
              <input v-model="translationConfig.model" placeholder="GLM-4-Flash-250414" />
            </label>
            <label class="field">
              <span>{{ $t('app.llm.batchSize') }}</span>
              <input v-model.number="translationConfig.batchSize" type="number" min="10" max="15" />
            </label>
            <label class="field">
              <span>{{ $t('app.llm.contextSize') }}</span>
              <input v-model.number="translationConfig.contextSize" type="number" min="0" max="5" />
            </label>
          </template>
          <label class="field wide">
            <span>{{ $t('app.model.whisper') }}</span>
            <input
              v-model="whisperModelPath"
              :placeholder="defaultModelPaths?.whisperModelPath || $t('app.model.defaultWhisper')"
            />
          </label>
          <label class="field wide">
            <span>{{ $t('app.model.vad') }}</span>
            <input
              v-model="vadModelPath"
              :placeholder="defaultModelPaths?.vadModelPath || $t('app.model.defaultVad')"
            />
          </label>
        </div>
      </div>
    </section>

    <section class="content-grid">
      <TaskQueue
        :tasks="taskStore.tasks"
        :selected-task-id="taskStore.selectedTaskId"
        @select="taskStore.selectTask"
        @retry="taskStore.retryTask"
        @retry-translate="handleRetryTranslate"
        @delete="taskStore.deleteTask"
      />
      <SubtitleEditor
        :task="selectedTask"
        :logs="taskStore.selectedTaskLogs"
        @save="taskStore.updateSegment"
        @translate="handleTranslateFromEditor"
        @export="handleExport"
      />
    </section>
  </main>
</template>
