<script>
  import { onDestroy } from "svelte";
  import { invoke } from "@tauri-apps/api/core";
  import { history, saveHistory, deleteFromHistory, renameHistoryEntry } from "$lib/stores/page.svelte.js";
  import { toasts } from "$lib/stores/toasts.svelte.js";
  import { convertFileSrc } from "@tauri-apps/api/core";
  import { getPreferences } from "$lib/stores/preferences.svelte.js";
  import { clampTextLines, measurePreWrap } from "$lib/utils/textMeasure.js";
  import { bodyPretextLineHeight, pretextFontShorthand } from "$lib/utils/accessibilityTypography.js";
  import { buildCumulativeOffsets, findVisibleRange } from "$lib/utils/virtualList.js";
  import Card from "$lib/components/Card.svelte";
  import EmptyState from "$lib/components/EmptyState.svelte";
  import { formatTime, formatDuration } from "$lib/utils/time.js";
  import { PLAYBACK_SPEEDS } from "$lib/utils/constants.js";
  import { Search, Clock, Play, Pause, FileText, Mic, ChevronDown } from 'lucide-svelte';

  const prefs = getPreferences();
  const COLLAPSED_ROW_MIN_HEIGHT = 54;
  const COLLAPSED_ROW_VERTICAL_PADDING = 24;
  const EXPANDED_BASE_HEIGHT = 92;
  const AUDIO_PLAYER_HEIGHT = 54;
  const HISTORY_PREVIEW_LINES = 2;
  const HISTORY_DURATION_WIDTH = 48;
  const HISTORY_DATE_WIDTH = 90;
  const HISTORY_ICON_WIDTH = 24;
  const HISTORY_SOURCE_WIDTH = 14;
  const HISTORY_CHEVRON_WIDTH = 14;
  const HISTORY_ROW_GAP = 12;
  const HISTORY_ROW_HORIZONTAL_PADDING = 32;
  const HISTORY_PREVIEW_MIN_WIDTH = 120;
  const BUFFER_COUNT = 6;

  let searchQuery = $state("");
  let expandedId = $state(null);
  let playingId = $state(null);
  let audioEl = $state(null);
  let currentTime = $state(0);
  let duration = $state(0);
  let playbackRate = $state(1);
  let animFrameId = null;
  let listEl = $state(null);
  let containerHeight = $state(0);
  let containerWidth = $state(0);
  let scrollTop = $state(0);

  onDestroy(() => {
    stopPlayback();
  });

  let filtered = $derived(
    searchQuery
      ? history.filter((h) => {
          const q = searchQuery.toLowerCase();
          return (
            (h.text && h.text.toLowerCase().includes(q)) ||
            (h.preview && h.preview.toLowerCase().includes(q)) ||
            (h.source && h.source.toLowerCase().includes(q)) ||
            (h.title && h.title.toLowerCase().includes(q))
          );
        })
      : history
  );

  let historyTextFont = $derived(pretextFontShorthand(prefs.accessibility, 13));
  let historyLineHeight = $derived(bodyPretextLineHeight(prefs.accessibility, 13));

  function compactPreviewText(item) {
    return item.title || item.preview || item.text || "";
  }

  let compactPreviews = $derived.by(() => {
    return filtered.map((item) => {
      const text = compactPreviewText(item);
      if (!text) {
        return {
          text: "",
          fullText: "",
          height: historyLineHeight,
        };
      }

      if (containerWidth <= 0) {
        const fallbackText = text.length > 80 ? `${text.slice(0, 79)}…` : text;
        return {
          text: fallbackText,
          fullText: text,
          height: historyLineHeight,
        };
      }

      const reservedWidth =
        HISTORY_ROW_HORIZONTAL_PADDING +
        HISTORY_ICON_WIDTH +
        (item.duration ? HISTORY_DURATION_WIDTH : 0) +
        HISTORY_SOURCE_WIDTH +
        HISTORY_DATE_WIDTH +
        HISTORY_CHEVRON_WIDTH +
        HISTORY_ROW_GAP * (item.duration ? 5 : 4);
      const textWidth = Math.max(HISTORY_PREVIEW_MIN_WIDTH, containerWidth - reservedWidth);
      const preview = clampTextLines(
        text,
        historyTextFont,
        textWidth,
        historyLineHeight,
        HISTORY_PREVIEW_LINES,
      );

      return {
        text: preview.text,
        fullText: text,
        height: preview.height,
      };
    });
  });

  let itemHeights = $derived.by(() => {
    if (!filtered.length) return [];
    const textWidth = Math.max(0, containerWidth - 32);
    return filtered.map((item, index) => {
      const compactHeight = Math.max(
        COLLAPSED_ROW_MIN_HEIGHT,
        (compactPreviews[index]?.height || historyLineHeight) + COLLAPSED_ROW_VERTICAL_PADDING,
      );
      let height = compactHeight;
      if (expandedId === item.id) {
        const transcriptHeight = item.text && textWidth > 0
          ? measurePreWrap(item.text, historyTextFont, textWidth, historyLineHeight).height
          : historyLineHeight;
        height += transcriptHeight + EXPANDED_BASE_HEIGHT;
        if (item.audioPath && playingId === item.id) {
          height += AUDIO_PLAYER_HEIGHT;
        }
      }
      return height;
    });
  });

  let cumulativeOffsets = $derived(buildCumulativeOffsets(itemHeights));
  let totalHeight = $derived(
    cumulativeOffsets.length > 0 ? cumulativeOffsets[cumulativeOffsets.length - 1] : 0
  );
  let visibleRange = $derived.by(() =>
    findVisibleRange(
      cumulativeOffsets,
      filtered.length,
      scrollTop,
      containerHeight,
      BUFFER_COUNT,
    )
  );
  let visibleItems = $derived.by(() => {
    const items = [];
    for (let i = visibleRange.start; i < visibleRange.end; i++) {
      items.push({
        item: filtered[i],
        top: cumulativeOffsets[i],
        height: itemHeights[i],
        preview: compactPreviews[i],
      });
    }
    return items;
  });

  function clearAll() {
    if (!confirm("Delete all history? This can't be undone.")) return;
    history.splice(0);
    saveHistory();
    expandedId = null;
    stopPlayback();
  }

  function toggleExpand(id) {
    expandedId = expandedId === id ? null : id;
  }

  function onListScroll(e) {
    scrollTop = e.target.scrollTop;
  }

  function copyItem(item) {
    invoke("copy_to_clipboard", { text: item.text }).catch(() => {});
  }

  function removeItem(item) {
    const idx = history.indexOf(item);
    if (idx !== -1) {
      deleteFromHistory(idx);
      if (expandedId === item.id) expandedId = null;
      if (playingId === item.id) stopPlayback();
    }
  }

  async function renameItem(item) {
    const name = prompt("Name this transcript:", item.title || "");
    if (name === null) return;

    const trimmed = name.trim();
    item.title = trimmed;
    item.preview = trimmed ? `${trimmed} — ${item.text.slice(0, 80)}` : item.text.slice(0, 120);

    // Persist via the dual-write helper. Updates SQLite + localStorage and
    // surfaces a toast on failure (Day 4 of the upgrade plan, closes
    // architecture-review.md §13).
    try {
      await renameHistoryEntry(item.id, { title: trimmed });
    } catch (err) {
      toasts.warn(
        "Rename did not persist",
        "Your change is visible now but did not save. It may revert on next launch."
      );
    }
  }

  function togglePlay(item) {
    if (playingId === item.id) {
      if (audioEl && !audioEl.paused) {
        audioEl.pause();
        cancelAnimationFrame(animFrameId);
      } else if (audioEl) {
        audioEl.play();
        tickTime();
      }
      return;
    }
    stopPlayback();
    try {
      const src = convertFileSrc(item.audioPath);
      const audio = new Audio(src);
      audio.playbackRate = playbackRate;
      audio.onloadedmetadata = () => { duration = audio.duration; };
      audio.onended = () => { stopPlayback(); };
      audio.onerror = () => { stopPlayback(); };
      audio.play();
      audioEl = audio;
      playingId = item.id;
      tickTime();
    } catch {
      stopPlayback();
    }
  }

  function stopPlayback() {
    if (audioEl) { audioEl.pause(); audioEl.src = ""; audioEl = null; }
    playingId = null;
    currentTime = 0;
    duration = 0;
    cancelAnimationFrame(animFrameId);
  }

  function tickTime() {
    if (audioEl) {
      currentTime = audioEl.currentTime;
      if (!audioEl.paused) {
        animFrameId = requestAnimationFrame(tickTime);
      }
    }
  }

  let seekTimeout = null;
  function seekTo(e) {
    const val = parseFloat(e.target.value);
    currentTime = val; // update UI immediately
    clearTimeout(seekTimeout);
    seekTimeout = setTimeout(() => {
      if (audioEl) audioEl.currentTime = val;
    }, 50);
  }

  function setSpeed(speed) {
    playbackRate = speed;
    if (audioEl) audioEl.playbackRate = speed;
  }

  async function openViewer(item) {
    try {
      localStorage.setItem("kon_viewer_item", JSON.stringify(item));
      await invoke("open_viewer_window");
    } catch {
      localStorage.setItem("kon_viewer_item", JSON.stringify(item));
      window.open("/viewer", "_blank", "width=600,height=700");
    }
  }

  $effect(() => {
    if (!listEl) return;
    const ro = new ResizeObserver((entries) => {
      for (const entry of entries) {
        containerHeight = entry.contentRect.height;
        containerWidth = entry.contentRect.width;
      }
    });
    ro.observe(listEl);
    return () => ro.disconnect();
  });

  $effect(() => {
    if (expandedId && !filtered.some((item) => item.id === expandedId)) {
      expandedId = null;
    }
  });
</script>

<div class="flex flex-col h-full overflow-y-auto animate-fade-in">
  <!-- Header -->
  <div class="flex items-center gap-4 px-7 pt-6 pb-4">
    <h2 class="font-display text-[26px] italic text-text">History</h2>
    <span class="text-[11px] text-text-tertiary mt-1">{history.length} saved</span>
    <div class="flex-1"></div>
    {#if history.length > 0}
      <button
        class="btn-md rounded-lg text-text-tertiary hover:text-danger hover:bg-hover"
        onclick={clearAll}
      >
        Clear All
      </button>
    {/if}
  </div>

  <!-- Search -->
  <div class="px-7 pb-4">
    <Card>
      <div class="flex items-center gap-3 px-4 py-2.5">
        <Search size={16} class="text-text-tertiary flex-shrink-0" aria-hidden="true" />
        <input
          class="flex-1 bg-transparent text-text text-[13px] placeholder:text-text-tertiary focus:outline-none"
          placeholder="Search all transcripts..."
          bind:value={searchQuery}
          data-no-transition
        />
        {#if searchQuery}
          <span class="text-[11px] text-text-tertiary mr-2">{filtered.length} results</span>
          <button
            class="text-[11px] text-text-tertiary hover:text-text"
            onclick={() => searchQuery = ""}
          >Clear</button>
        {/if}
      </div>
    </Card>
  </div>

  <!-- History list -->
  <div class="flex-1 px-7 pb-4 min-h-0">
    <Card classes="h-full flex flex-col overflow-hidden">
      {#if filtered.length === 0}
        <EmptyState
          icon={Clock}
          message={searchQuery ? "No matching transcripts" : "Your transcriptions will be saved here"}
        />
      {:else}
        <div bind:this={listEl} class="flex-1 overflow-y-auto" onscroll={onListScroll}>
          <div class="relative" style="height: {totalHeight}px">
            {#each visibleItems as { item, top, height, preview } (item.id)}
              <div class="absolute left-0 right-0" style="top: {top}px; min-height: {height}px">
                <!-- Compact row -->
                <div
                  class="flex items-center gap-3 px-4 py-3 border-b border-border-subtle bg-bg-card hover:bg-hover cursor-pointer"
                  style="transition-duration: var(--duration-ui)"
                  onclick={() => toggleExpand(item.id)}
                  onkeydown={(e) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); toggleExpand(item.id); } }}
                  role="button"
                  tabindex="0"
                  aria-expanded={expandedId === item.id}
                >
                  <!-- Play button (if audio exists) -->
                  {#if item.audioPath}
                    <button
                      class="w-6 h-6 rounded-full flex items-center justify-center flex-shrink-0
                        {playingId === item.id ? 'bg-accent/20 text-accent' : 'bg-accent/10 text-accent hover:bg-accent/20'}"
                      onclick={(e) => { e.stopPropagation(); togglePlay(item); }}
                      aria-label={playingId === item.id && audioEl && !audioEl.paused ? "Pause" : "Play"}
                    >
                      {#if playingId === item.id && audioEl && !audioEl.paused}
                        <Pause size={10} aria-hidden="true" />
                      {:else}
                        <Play size={10} class="ml-0.5" aria-hidden="true" />
                      {/if}
                    </button>
                  {:else}
                    <div class="w-6 h-6 flex-shrink-0"></div>
                  {/if}

                  <!-- Title / preview text (truncated) -->
                  <div class="flex-1 min-w-0">
                    <p
                      class="text-[13px] text-text whitespace-pre-wrap break-words"
                      style="line-height: {historyLineHeight}px"
                      title={preview?.fullText || ""}
                    >
                      {preview?.text || compactPreviewText(item)}
                    </p>
                  </div>

                  <!-- Duration -->
                  {#if item.duration}
                    <span class="text-[11px] text-text-tertiary flex-shrink-0 tabular-nums">
                      {formatDuration(item.duration)}
                    </span>
                  {/if}

                  <!-- Source icon -->
                  <span class="flex-shrink-0 text-text-tertiary" title={item.source}>
                    {#if item.source && item.source.toLowerCase().includes("file")}
                      <FileText size={14} aria-hidden="true" />
                    {:else}
                      <Mic size={14} aria-hidden="true" />
                    {/if}
                  </span>

                  <!-- Date (right-aligned) -->
                  <span class="text-[11px] text-text-tertiary flex-shrink-0 text-right min-w-[90px]">
                    {item.date}
                  </span>

                  <!-- Expand chevron -->
                  <ChevronDown
                    size={14}
                    class="text-text-tertiary flex-shrink-0 {expandedId === item.id ? 'rotate-180' : ''}"
                    style="transition: transform var(--duration-ui)"
                    aria-hidden="true"
                  />
                </div>

                <!-- Expanded detail -->
                {#if expandedId === item.id}
                  <div class="px-4 py-4 bg-bg-elevated border-b border-border-subtle">
                    <!-- Audio player (if playing this item) -->
                    {#if item.audioPath && playingId === item.id}
                      <div class="flex items-center gap-3 mb-4 px-1">
                        <span class="text-[10px] text-text-tertiary tabular-nums w-[80px]">
                          {formatTime(currentTime)} / {formatTime(duration)}
                        </span>
                        <input
                          type="range"
                          min="0"
                          max={duration || 0}
                          step="0.1"
                          value={currentTime}
                          oninput={seekTo}
                          class="flex-1 accent-accent h-1"
                          data-no-transition
                          onclick={(e) => e.stopPropagation()}
                        />
                        <div class="flex gap-0.5">
                          {#each PLAYBACK_SPEEDS as speed}
                            <button
                              class="text-[9px] px-1.5 py-0.5 rounded-full
                                {playbackRate === speed
                                  ? 'bg-accent/15 text-accent font-medium'
                                  : 'text-text-tertiary hover:text-text-secondary'}"
                              onclick={(e) => { e.stopPropagation(); setSpeed(speed); }}
                            >{speed}x</button>
                          {/each}
                        </div>
                      </div>
                    {/if}

                    <!-- Full transcript -->
                    <p class="text-[13px] text-text whitespace-pre-wrap mb-4" style="line-height: {historyLineHeight}px">{item.text}</p>

                    <!-- Action buttons -->
                    <div class="flex items-center gap-2 flex-wrap">
                      <button
                        class="text-[11px] px-3 py-1.5 rounded-lg bg-hover text-text-secondary hover:text-text"
                        style="transition-duration: var(--duration-ui)"
                        onclick={(e) => { e.stopPropagation(); renameItem(item); }}
                      >Rename</button>
                      <button
                        class="text-[11px] px-3 py-1.5 rounded-lg bg-hover text-text-secondary hover:text-text"
                        style="transition-duration: var(--duration-ui)"
                        onclick={(e) => { e.stopPropagation(); copyItem(item); }}
                      >Copy</button>
                      {#if item.audioPath && item.segments && item.segments.length > 0}
                        <button
                          class="text-[11px] px-3 py-1.5 rounded-lg bg-hover text-accent hover:text-accent-hover"
                          style="transition-duration: var(--duration-ui)"
                          onclick={(e) => { e.stopPropagation(); openViewer(item); }}
                        >Open viewer</button>
                      {/if}
                      <div class="flex-1"></div>
                      <button
                        class="text-[11px] px-3 py-1.5 rounded-lg bg-hover text-text-secondary hover:text-danger"
                        style="transition-duration: var(--duration-ui)"
                        onclick={(e) => { e.stopPropagation(); removeItem(item); }}
                      >Delete</button>
                    </div>
                  </div>
                {/if}
              </div>
            {/each}
          </div>
        </div>
      {/if}
    </Card>
  </div>
</div>
