<script setup lang="ts">
import { computed, ref, nextTick, watch, onBeforeUpdate } from 'vue'
import type { SubtitleSegment, SubtitleTask } from '../lib/types'

const LOG_ROW_HEIGHT = 20
const LOG_OVERSCAN = 10

const SEGMENT_TIMESTAMP_HEIGHT = 20
const SEGMENT_TEXTAREA_HEIGHT = 60
const SEGMENT_PADDING_GAP = 34
const SEGMENT_CHARS_PER_LINE = 40
const SEGMENT_LINE_HEIGHT = 20
const SEGMENT_OVERSCAN = 5

function estimateSegmentHeight(seg: SubtitleSegment): number {
  const textLines = Math.max(1, Math.ceil((seg.sourceText?.length || 1) / SEGMENT_CHARS_PER_LINE))
  return SEGMENT_TIMESTAMP_HEIGHT + textLines * SEGMENT_LINE_HEIGHT + SEGMENT_TEXTAREA_HEIGHT + SEGMENT_PADDING_GAP
}

const props = defineProps<{
  task: SubtitleTask | null
  logs: string[]
}>()

const isProcessing = computed(() => {
  if (!props.task) return false
  return !['completed', 'failed'].includes(props.task.status)
})

const canExport = computed(() => {
  return props.task?.status === 'completed' && (props.task.segments?.length ?? 0) > 0
})

const canTranslate = computed(() => {
  if (!props.task) return false
  return (
    props.task.segments.length > 0 &&
    props.task.segments.some((s) => s.sourceText)
  )
})

const emit = defineEmits<{
  save: [segment: SubtitleSegment]
  export: [format: 'srt' | 'vtt' | 'ass']
  translate: []
}>()

const sortedSegments = computed(() =>
  [...(props.task?.segments ?? [])].sort((left, right) => {
    if (left.start !== right.start) return left.start - right.start
    if (left.end !== right.end) return left.end - right.end
    return left.id.localeCompare(right.id)
  }),
)

const logsExpanded = ref(false)

const logContainerRef = ref<HTMLElement | null>(null)
const logScrollTop = ref(0)
const isNearBottom = ref(true)

const visibleLogs = computed(() => {
  const total = props.logs.length
  if (total === 0) return { items: [] as string[], start: 0, paddingTop: 0, paddingBottom: 0 }

  const containerHeight = logContainerRef.value?.clientHeight ?? 220
  const startIndex = Math.max(0, Math.floor(logScrollTop.value / LOG_ROW_HEIGHT) - LOG_OVERSCAN)
  const visibleCount = Math.ceil(containerHeight / LOG_ROW_HEIGHT) + LOG_OVERSCAN * 2
  const endIndex = Math.min(total, startIndex + visibleCount)

  return {
    items: props.logs.slice(startIndex, endIndex),
    start: startIndex,
    paddingTop: startIndex * LOG_ROW_HEIGHT,
    paddingBottom: Math.max(0, total * LOG_ROW_HEIGHT - endIndex * LOG_ROW_HEIGHT),
  }
})

function handleLogScroll(event: Event) {
  const el = event.target as HTMLElement
  logScrollTop.value = el.scrollTop
  isNearBottom.value = el.scrollTop + el.clientHeight >= el.scrollHeight - 40
}

watch(() => props.logs.length, () => {
  if (isNearBottom.value) {
    nextTick(() => {
      if (logContainerRef.value) {
        logContainerRef.value.scrollTop = logContainerRef.value.scrollHeight
        logScrollTop.value = logContainerRef.value.scrollTop
      }
    })
  }
})

const segContainerRef = ref<HTMLElement | null>(null)
const segScrollTop = ref(0)
const segHeights = ref(new Map<string, number>())
const segObservers = new Map<string, ResizeObserver>()

const segOffsetCache = computed(() => {
  const segments = sortedSegments.value
  const cache = new Map<string, { height: number; offset: number }>()
  let offset = 0
  for (const seg of segments) {
    const height = segHeights.value.get(seg.id) ?? estimateSegmentHeight(seg)
    cache.set(seg.id, { height, offset })
    offset += height + 8
  }
  return { cache, totalHeight: Math.max(0, offset - 8) }
})

const visibleSegments = computed(() => {
  const segments = sortedSegments.value
  if (segments.length === 0) return { items: [] as SubtitleSegment[], paddingTop: 0, paddingBottom: 0 }

  const containerHeight = segContainerRef.value?.clientHeight ?? 600
  const { cache, totalHeight } = segOffsetCache.value

  let startIndex = segments.length - 1
  for (let i = 0; i < segments.length; i++) {
    const info = cache.get(segments[i].id)!
    if (info.offset + info.height > segScrollTop.value) {
      startIndex = Math.max(0, i - SEGMENT_OVERSCAN)
      break
    }
  }

  let endIndex = segments.length
  for (let i = startIndex; i < segments.length; i++) {
    const info = cache.get(segments[i].id)!
    if (info.offset > segScrollTop.value + containerHeight) {
      endIndex = Math.min(segments.length, i + SEGMENT_OVERSCAN)
      break
    }
  }

  const startOffset = cache.get(segments[startIndex].id)?.offset ?? 0
  const endInfo = cache.get(segments[Math.min(endIndex - 1, segments.length - 1)].id)
  const endOffset = endInfo ? endInfo.offset + endInfo.height : totalHeight
  const paddingBottom = Math.max(0, totalHeight - endOffset)

  return {
    items: segments.slice(startIndex, endIndex),
    paddingTop: startOffset,
    paddingBottom,
  }
})

function handleSegScroll(event: Event) {
  const el = event.target as HTMLElement
  segScrollTop.value = el.scrollTop
}

function onSegMount(el: HTMLElement, segmentId: string) {
  const existing = segObservers.get(segmentId)
  if (existing) existing.disconnect()

  const ro = new ResizeObserver((entries) => {
    for (const entry of entries) {
      const actualHeight = entry.contentBoxSize
        ? entry.contentBoxSize[0].blockSize
        : entry.borderBoxSize
          ? entry.borderBoxSize[0].blockSize
          : (entry.target as HTMLElement).offsetHeight
      if (actualHeight > 0) {
        segHeights.value = new Map(segHeights.value).set(segmentId, actualHeight)
      }
    }
  })
  ro.observe(el)
  segObservers.set(segmentId, ro)
}

function onSegUnmount(segmentId: string) {
  const ro = segObservers.get(segmentId)
  if (ro) {
    ro.disconnect()
    segObservers.delete(segmentId)
  }
}

onBeforeUpdate(() => {
  segScrollTop.value = segContainerRef.value?.scrollTop ?? 0
})

function formatTime(seconds: number) {
  const ms = Math.round(seconds * 1000)
  const h = Math.floor(ms / 3600000)
  const m = Math.floor((ms % 3600000) / 60000)
  const s = Math.floor((ms % 60000) / 1000)
  const milli = ms % 1000
  return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}.${String(milli).padStart(3, '0')}`
}

function updateTranslatedText(segment: SubtitleSegment, value: string) {
  emit('save', { ...segment, translatedText: value })
}
</script>

<template>
  <section class="panel workspace-panel">
    <div class="workspace-header">
      <div>
        <strong>{{ task?.fileName ?? $t('editor.title') }}</strong>
        <p class="panel-subtitle">
          {{ task ? $t('editor.segments', { count: sortedSegments.length }) : $t('editor.selectTask') }}
        </p>
      </div>
      <div v-if="task" class="export-actions">
        <button
          v-if="canTranslate"
          class="button primary small"
          :disabled="isProcessing"
          @click="emit('translate')"
        >{{ $t('editor.translate') }}</button>
        <button class="button secondary small" :disabled="!canExport" @click="emit('export', 'srt')">SRT</button>
        <button class="button secondary small" :disabled="!canExport" @click="emit('export', 'vtt')">VTT</button>
        <button class="button secondary small" :disabled="!canExport" @click="emit('export', 'ass')">ASS</button>
      </div>
    </div>

    <div v-if="!task" class="empty-state">
      <p>{{ $t('editor.empty') }}</p>
      <p style="margin-top: 6px; font-size: 11px;">{{ $t('editor.emptyHint') }}</p>
    </div>

    <div v-else-if="sortedSegments.length === 0" class="empty-state">
      <template v-if="isProcessing">{{ $t('editor.processing') }}</template>
      <template v-else-if="task?.status === 'failed'">{{ $t('editor.failed') }}</template>
      <template v-else>{{ $t('editor.noSegments') }}</template>
    </div>

    <div v-else ref="segContainerRef" class="segment-list" @scroll="handleSegScroll">
      <div class="seg-spacer" :style="{ paddingTop: `${visibleSegments.paddingTop}px`, paddingBottom: `${visibleSegments.paddingBottom}px` }">
        <article
          v-for="segment in visibleSegments.items"
          :key="segment.id"
          :ref="(el: any) => { if (el) onSegMount(el as HTMLElement, segment.id); else onSegUnmount(segment.id) }"
          class="segment-item"
        >
          <div class="task-row subtle">
            <span>{{ formatTime(segment.start) }} - {{ formatTime(segment.end) }}</span>
            <span class="segment-id">{{ segment.id }}</span>
          </div>
          <p class="source-text">{{ segment.sourceText || $t('editor.waiting') }}</p>
          <textarea
            class="editor-input"
            :value="segment.translatedText ?? ''"
            :placeholder="task.outputMode === 'translate' ? $t('editor.translation') : $t('editor.source')"
            :disabled="task.outputMode === 'source'"
            @change="updateTranslatedText(segment, ($event.target as HTMLTextAreaElement).value)"
          />
        </article>
      </div>
    </div>

    <div class="log-drawer" :class="{ expanded: logsExpanded }">
      <button class="log-toggle" type="button" @click="logsExpanded = !logsExpanded">
        <span>{{ $t('editor.logs') }}</span>
        <span class="subtle">{{ logs.length }}</span>
        <span class="log-chevron">{{ logsExpanded ? '−' : '+' }}</span>
      </button>
      <div v-if="logsExpanded" class="log-panel">
        <div v-if="logs.length === 0" class="empty-state">
          {{ $t('editor.noLogs') }}
        </div>
        <div v-else ref="logContainerRef" class="log-list" @scroll="handleLogScroll">
          <div class="log-spacer" :style="{ paddingTop: `${visibleLogs.paddingTop}px`, paddingBottom: `${visibleLogs.paddingBottom}px` }">
            <pre v-for="(line, i) in visibleLogs.items" :key="visibleLogs.start + i" class="log-line">{{ line }}</pre>
          </div>
        </div>
      </div>
    </div>
  </section>
</template>
