<script lang="ts">
  import { claudeStore, type TerminalLine } from "$lib/stores/claude";
  import { afterUpdate, tick, onMount, onDestroy } from "svelte";
  import { get } from "svelte/store";
  import { invoke } from "@tauri-apps/api/core";

  import ConversationTabs from "./ConversationTabs.svelte";
  import Markdown from "./Markdown.svelte";
  import HighlightedText from "./HighlightedText.svelte";
  import ThinkingBlock from "./ThinkingBlock.svelte";
  import ToolCallBlock from "./ToolCallBlock.svelte";
  import { searchState, searchQuery } from "$lib/stores/search";
  import { clipboardStore } from "$lib/stores/clipboard";
  import { shouldHidePaths, maskPaths, showThinkingBlocks } from "$lib/stores/config";

  // Virtual windowing constants — keeps the DOM lean during long sessions
  const WINDOW_SIZE = 150; // max lines rendered at once
  const LOAD_CHUNK = 50; // how many older lines to load when scrolling up
  const AVG_LINE_HEIGHT = 60; // rough px estimate per line, used for top spacer

  let terminalElement: HTMLDivElement;
  let shouldAutoScroll = true;
  let lines: TerminalLine[] = [];
  let currentSearchQuery = "";
  let currentConversationId: string | null = null;
  let isRestoringScroll = false;
  let windowStart = 0;
  let isLoadingMore = false;
  let isSwitchingConversation = false;

  let searchDebounceTimer: ReturnType<typeof setTimeout> | null = null;
  searchQuery.subscribe((value) => {
    if (searchDebounceTimer) clearTimeout(searchDebounceTimer);
    searchDebounceTimer = setTimeout(() => {
      currentSearchQuery = value;
    }, 150);
  });

  let hidePaths = false;
  shouldHidePaths.subscribe((value) => {
    hidePaths = value;
  });

  let showThinking = true;
  showThinkingBlocks.subscribe((value) => {
    showThinking = value;
  });

  claudeStore.terminalLines.subscribe((value) => {
    lines = value;
  });

  claudeStore.activeConversationId.subscribe(async (newId) => {
    if (!newId) return;

    // Save current conversation's scroll position before switching
    if (currentConversationId && currentConversationId !== newId && terminalElement) {
      const position = shouldAutoScroll ? -1 : terminalElement.scrollTop;
      claudeStore.saveScrollPosition(currentConversationId, position);
    }

    currentConversationId = newId;

    // Guard the $: reactive auto-scroll block from firing with stale `lines`
    // (the old conversation's data) during the switch. Without this, Svelte's
    // reactive system can re-run the window-advance block before `terminalLines`
    // has recomputed for the new conversation, overriding our correct windowStart.
    isSwitchingConversation = true;

    // Read the new conversation's lines directly from the store — the derived
    // `terminalLines` store (and thus `lines`) may not have recomputed yet when
    // this subscriber fires, so using `lines` here would give stale data.
    const newConvLines = get(claudeStore.conversations).get(newId)?.terminalLines ?? [];
    const savedPosition = claudeStore.getScrollPosition(newId);
    if (savedPosition === -1) {
      // Will auto-scroll: pin the window to the tail of the new conversation
      shouldAutoScroll = true;
      windowStart = Math.max(0, newConvLines.length - WINDOW_SIZE);
    } else {
      // Will restore a specific position: always start from the top of history
      shouldAutoScroll = false;
      windowStart = 0;
    }

    // Block the scroll handler during the entire DOM transition — scroll events
    // can fire mid-tick when the content changes, and handleScroll would see
    // scrollTop not at the bottom yet and set shouldAutoScroll = false, breaking
    // autoscroll for the new conversation permanently.
    isRestoringScroll = true;

    // Restore scroll position for the new conversation after DOM updates.
    // Clear the switch guard first so the $: block can react to new lines
    // arriving after the switch settles.
    await tick();
    isSwitchingConversation = false;
    if (terminalElement) {
      if (savedPosition === -1) {
        terminalElement.scrollTop = terminalElement.scrollHeight;
      } else {
        terminalElement.scrollTop = savedPosition;
      }
      // Small delay to prevent the scroll handler from overriding our restore
      setTimeout(() => {
        isRestoringScroll = false;
      }, 50);
    }
  });

  async function handleScroll() {
    if (!terminalElement || isRestoringScroll) return;
    const { scrollTop, scrollHeight, clientHeight } = terminalElement;
    shouldAutoScroll = scrollHeight - scrollTop - clientHeight < 100;

    // Load older lines when the user scrolls near the top of the visible window.
    // Use windowStart * AVG_LINE_HEIGHT (the spacer height) as the baseline so
    // we trigger at the top of the rendered content, not the absolute container top.
    if (scrollTop < windowStart * AVG_LINE_HEIGHT + 300 && windowStart > 0 && !isLoadingMore) {
      isLoadingMore = true;
      const prevScrollHeight = terminalElement.scrollHeight;
      const prevScrollTop = terminalElement.scrollTop;

      windowStart = Math.max(0, windowStart - LOAD_CHUNK);

      await tick();

      if (terminalElement) {
        // Compensate for the new items pushing content down
        terminalElement.scrollTop =
          prevScrollTop + (terminalElement.scrollHeight - prevScrollHeight);
      }
      isLoadingMore = false;
    }
  }

  afterUpdate(() => {
    if (shouldAutoScroll && terminalElement && !isRestoringScroll) {
      terminalElement.scrollTop = terminalElement.scrollHeight;
    }
  });

  function getLineClass(type: string): string {
    switch (type) {
      case "user":
        return "terminal-user";
      case "assistant":
        return "terminal-assistant";
      case "system":
        return "terminal-system italic";
      case "tool":
        return "terminal-tool";
      case "error":
        return "terminal-error";
      case "thinking":
        return "terminal-thinking";
      case "rate-limit":
        return "terminal-rate-limit";
      case "compact-prompt":
        return "terminal-compact-prompt";
      case "worktree":
        return "terminal-worktree";
      case "config-change":
        return "terminal-config-change";
      default:
        return "terminal-default";
    }
  }

  function getLinePrefix(type: string): string {
    switch (type) {
      case "user":
        return ">";
      case "assistant":
        return "";
      case "system":
        return "[system]";
      case "tool":
        return "[tool]";
      case "error":
        return "[error]";
      case "rate-limit":
        return "[rate-limit]";
      case "worktree":
        return "[worktree]";
      case "config-change":
        return "[config]";
      default:
        return "";
    }
  }

  function formatTime(date: Date): string {
    return date.toLocaleTimeString("en-US", {
      hour: "2-digit",
      minute: "2-digit",
    });
  }

  // Visible slice — only render lines within the current window
  $: visibleLines = lines.slice(windowStart, windowStart + WINDOW_SIZE);

  // Height of the invisible spacer above the visible window
  $: topSpacerHeight = windowStart * AVG_LINE_HEIGHT;

  // Advance the window forward when auto-scrolling and new lines overflow it.
  // Skip during conversation switches — `lines` may still hold the previous
  // conversation's data, which would push windowStart past the new conv's end.
  $: if (shouldAutoScroll && !isSwitchingConversation && lines.length > windowStart + WINDOW_SIZE) {
    windowStart = Math.max(0, lines.length - WINDOW_SIZE);
  }

  $: {
    if (currentSearchQuery && lines.length > 0) {
      const escapedQuery = currentSearchQuery.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
      const regex = new RegExp(escapedQuery, "gi");
      let totalMatches = 0;
      for (const line of lines) {
        const matches = line.content.match(regex);
        if (matches) {
          totalMatches += matches.length;
        }
      }
      searchState.setMatchCount(totalMatches);
    } else {
      searchState.setMatchCount(0);
    }
  }

  // Handle manual text selection copy events
  function handleCopy() {
    const selection = window.getSelection();
    const selectedText = selection?.toString();

    if (selectedText && selectedText.trim().length > 0) {
      // Only capture multi-line or longer text (likely code/meaningful content)
      if (selectedText.includes("\n") || selectedText.length > 50) {
        clipboardStore.captureClipboard(selectedText, null, "Copied from chat");
      }
    }
  }

  onMount(() => {
    // Listen for copy events on the terminal
    if (terminalElement) {
      terminalElement.addEventListener("copy", handleCopy);
    }
  });

  onDestroy(() => {
    if (terminalElement) {
      terminalElement.removeEventListener("copy", handleCopy);
    }
    if (searchDebounceTimer) clearTimeout(searchDebounceTimer);
  });

  // Copy message content to clipboard
  async function copyMessage(content: string) {
    try {
      await navigator.clipboard.writeText(content);
      // Optionally capture to clipboard history
      await clipboardStore.captureClipboard(content, null, "Message from chat");

      // Visual feedback could be added here if needed
    } catch (error) {
      console.error("Failed to copy message:", error);
    }
  }

  // State for showing "Copied!" feedback
  let copiedMessageId: string | null = null;

  async function handleCopyMessage(messageId: string, content: string) {
    await copyMessage(content);
    copiedMessageId = messageId;
    setTimeout(() => {
      copiedMessageId = null;
    }, 2000);
  }

  async function handleCompact() {
    if (!currentConversationId) return;
    await invoke("send_prompt", { conversationId: currentConversationId, message: "/compact" });
  }
</script>

<div
  class="terminal-container flex-1 overflow-hidden rounded-lg bg-[var(--bg-terminal)] border border-[var(--border-color)]"
>
  <div
    class="terminal-header flex items-center gap-2 px-4 py-2 border-b border-[var(--border-color)] bg-[var(--bg-secondary)]"
  >
    <div class="flex gap-1.5">
      <div class="w-3 h-3 rounded-full bg-red-500"></div>
      <div class="w-3 h-3 rounded-full bg-yellow-500"></div>
      <div class="w-3 h-3 rounded-full bg-green-500"></div>
    </div>
    <span class="text-sm terminal-header-text ml-2">Terminal</span>
  </div>

  <ConversationTabs />

  <div
    bind:this={terminalElement}
    onscroll={handleScroll}
    class="terminal-content h-[calc(100%-76px)] overflow-y-auto p-4 font-mono"
    style="font-size: var(--terminal-font-size, 14px); font-family: var(--terminal-font-family, monospace);"
  >
    {#if lines.length === 0}
      <div class="terminal-waiting italic">
        Waiting for Claude... Type a message below to start!
      </div>
    {:else}
      <div style="height: {topSpacerHeight}px" aria-hidden="true"></div>
      {#if windowStart > 0}
        <div class="terminal-older-indicator">
          ↑ {windowStart} older {windowStart === 1 ? "message" : "messages"} — scroll up to load
        </div>
      {/if}
      {#each visibleLines as line (line.id)}
        {#if line.type === "thinking"}
          {#if showThinking}
            <ThinkingBlock content={line.content} timestamp={line.timestamp} />
          {/if}
        {:else if line.type === "tool"}
          <div
            style={line.parentToolUseId
              ? "margin-left: 16px; padding-left: 8px; border-left: 2px solid var(--accent-primary);"
              : ""}
          >
            <ToolCallBlock
              toolName={line.toolName ?? null}
              content={maskPaths(line.content, hidePaths)}
              timestamp={line.timestamp}
            />
          </div>
        {:else}
          <div
            class="terminal-line mb-2 {getLineClass(line.type)} relative group"
            style={line.parentToolUseId
              ? "margin-left: 16px; padding-left: 8px; border-left: 2px solid var(--accent-primary);"
              : ""}
          >
            <span class="terminal-timestamp text-xs mr-2">{formatTime(line.timestamp)}</span>
            {#if line.parentToolUseId}
              <span class="text-xs mr-2 opacity-60" title="Message from subagent">
                <svg
                  class="inline-block w-3 h-3 -mt-0.5"
                  fill="none"
                  stroke="currentColor"
                  viewBox="0 0 24 24"
                >
                  <path
                    stroke-linecap="round"
                    stroke-linejoin="round"
                    stroke-width="2"
                    d="M9 5l7 7-7 7"
                  />
                </svg>
              </span>
            {/if}
            {#if line.cost && line.cost.costUsd > 0}
              <span
                class="terminal-cost text-xs mr-2"
                title="Input: {line.cost.inputTokens} | Output: {line.cost.outputTokens}"
              >
                ${line.cost.costUsd < 0.01
                  ? line.cost.costUsd.toFixed(4)
                  : line.cost.costUsd.toFixed(3)}
              </span>
            {/if}
            {#if getLinePrefix(line.type)}
              <span class="terminal-prefix mr-2">{getLinePrefix(line.type)}</span>
            {/if}
            {#if line.type === "compact-prompt"}
              <button class="compact-action-btn" onclick={handleCompact}>
                ⚡ Compact Conversation
              </button>
            {:else if line.type === "assistant" || line.type === "user"}
              <div class="message-content-wrapper">
                <Markdown
                  content={maskPaths(line.content, hidePaths)}
                  searchQuery={currentSearchQuery}
                />
                <button
                  class="copy-message-btn opacity-0 group-hover:opacity-100 transition-opacity"
                  onclick={() => handleCopyMessage(line.id, line.content)}
                  title="Copy message"
                >
                  <svg
                    width="14"
                    height="14"
                    viewBox="0 0 24 24"
                    fill="none"
                    stroke="currentColor"
                    stroke-width="2"
                    stroke-linecap="round"
                    stroke-linejoin="round"
                  >
                    <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
                    <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
                  </svg>
                  <span class="copy-text">{copiedMessageId === line.id ? "Copied!" : "Copy"}</span>
                </button>
              </div>
            {:else}
              <HighlightedText
                content={maskPaths(line.content, hidePaths)}
                searchQuery={currentSearchQuery}
              />
            {/if}
          </div>
        {/if}
      {/each}
    {/if}
  </div>
</div>

<style>
  .terminal-content {
    scrollbar-width: thin;
    scrollbar-color: var(--border-color) var(--bg-terminal);
  }

  /* Terminal text colors that adapt to theme */
  .terminal-user {
    color: var(--terminal-user, #22d3ee);
  }

  .terminal-assistant {
    color: var(--text-primary);
  }

  .terminal-system {
    color: var(--text-secondary);
  }

  .terminal-tool {
    color: var(--terminal-tool, #c084fc);
  }

  .terminal-error {
    color: var(--terminal-error, #f87171);
  }

  .terminal-rate-limit {
    color: var(--terminal-rate-limit, #fb923c);
  }

  .terminal-compact-prompt {
    color: var(--text-secondary);
  }

  .terminal-worktree {
    color: var(--terminal-worktree, #34d399);
  }

  .terminal-config-change {
    color: var(--terminal-config-change, #a78bfa);
  }

  .compact-action-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.4em;
    background: var(--bg-secondary);
    border: 1px solid var(--terminal-error, #f87171);
    color: var(--terminal-error, #f87171);
    padding: 0.3em 0.8em;
    cursor: pointer;
    border-radius: 4px;
    font-size: 0.9em;
    font-family: inherit;
    transition: all 0.15s ease;
  }

  .compact-action-btn:hover {
    background: color-mix(in srgb, var(--terminal-error, #f87171) 15%, transparent);
    color: var(--terminal-error, #f87171);
  }

  .terminal-default {
    color: var(--text-primary);
  }

  .terminal-timestamp {
    color: var(--text-tertiary, #6b7280);
  }

  .terminal-cost {
    color: var(--terminal-cost, #10b981);
    background: var(--terminal-cost-bg, rgba(16, 185, 129, 0.1));
    padding: 0 4px;
    border-radius: 3px;
    font-family: monospace;
  }

  .terminal-prefix {
    color: var(--text-secondary);
  }

  .terminal-tool-name {
    color: var(--terminal-tool-name, #ddd6fe);
  }

  .terminal-waiting {
    color: var(--text-secondary);
  }

  .terminal-older-indicator {
    color: var(--text-tertiary, #6b7280);
    font-size: 0.75rem;
    text-align: center;
    padding: 0.25rem 0;
    margin-bottom: 0.5rem;
    border-bottom: 1px solid var(--border-color);
  }

  .terminal-header-text {
    color: var(--text-secondary);
  }

  :global(.search-highlight) {
    background-color: var(--search-highlight, #fbbf24);
    color: var(--search-highlight-text, #000);
    border-radius: 2px;
    padding: 0 2px;
  }

  /* Message content wrapper for positioning */
  .message-content-wrapper {
    position: relative;
    display: inline-block;
    width: 100%;
  }

  /* Copy button styling */
  .copy-message-btn {
    position: absolute;
    top: 0;
    right: 0;
    display: flex;
    align-items: center;
    gap: 0.4em;
    background: var(--bg-secondary);
    border: 1px solid var(--border-color);
    color: var(--text-secondary);
    padding: 0.25em 0.5em;
    cursor: pointer;
    border-radius: 4px;
    font-size: 0.85em;
    font-family: inherit;
    transition: all 0.15s ease;
  }

  .copy-message-btn:hover {
    background: var(--bg-hover);
    color: var(--text-primary);
    border-color: var(--border-hover, var(--border-color));
  }

  .copy-message-btn svg {
    flex-shrink: 0;
  }

  /* Ensure relative positioning for parent */
  .terminal-line {
    position: relative;
  }
</style>
