<script lang="ts">
  import { marked } from "marked";
  import hljs from "highlight.js";
  import { onMount } from "svelte";
  import { openUrl } from "@tauri-apps/plugin-opener";
  import { invoke } from "@tauri-apps/api/core";
  import { clipboardStore } from "$lib/stores/clipboard";
  import { linkifyFilePaths } from "$lib/utils/filePaths";

  interface Props {
    content: string;
    searchQuery?: string;
  }

  let { content, searchQuery = "" }: Props = $props();
  let containerElement: HTMLDivElement;

  const renderer = new marked.Renderer();

  renderer.code = ({ text, lang }) => {
    const language = lang && hljs.getLanguage(lang) ? lang : "plaintext";
    const highlighted = hljs.highlight(text, { language }).value;
    const escapedText = text.replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
    return `<div class="code-block-wrapper">
      <div class="code-block-header">
        <span class="code-block-lang">${language}</span>
        <button class="copy-code-btn" data-code="${escapedText}" title="Copy code">
          <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">Copy</span>
        </button>
      </div>
      <pre class="hljs-code-block"><code class="hljs language-${language}">${highlighted}</code></pre>
    </div>`;
  };

  renderer.codespan = ({ text }) => {
    const escaped = text.replace(/</g, "&lt;").replace(/>/g, "&gt;");
    return `<code class="hljs-inline">${escaped}</code>`;
  };

  renderer.html = ({ text }) => {
    return text.replace(/</g, "&lt;").replace(/>/g, "&gt;");
  };

  marked.setOptions({
    renderer,
    gfm: true,
    breaks: true,
  });

  function processSpoilers(html: string): string {
    const codeBlockPlaceholders: string[] = [];

    // Temporarily replace code blocks and inline code with placeholders
    let processed = html.replace(/<(pre|code)[^>]*>[\s\S]*?<\/\1>/gi, (match) => {
      codeBlockPlaceholders.push(match);
      return `__CODE_PLACEHOLDER_${codeBlockPlaceholders.length - 1}__`;
    });

    // Apply spoiler transformation only to non-code content
    processed = processed.replace(
      /\|\|(.+?)\|\|/g,
      '<span class="spoiler" role="button" tabindex="0">$1</span>'
    );

    // Restore code blocks
    processed = processed.replace(/__CODE_PLACEHOLDER_(\d+)__/g, (_, index) => {
      return codeBlockPlaceholders[parseInt(index)];
    });

    return processed;
  }

  function highlightSearchMatches(html: string, query: string): string {
    if (!query) return html;

    const codeBlockPlaceholders: string[] = [];
    const tagPlaceholders: string[] = [];

    // Temporarily replace code blocks with placeholders (don't highlight in code)
    let processed = html.replace(/<(pre|code)[^>]*>[\s\S]*?<\/\1>/gi, (match) => {
      codeBlockPlaceholders.push(match);
      return `__CODE_SEARCH_PLACEHOLDER_${codeBlockPlaceholders.length - 1}__`;
    });

    // Temporarily replace all HTML tags with placeholders
    processed = processed.replace(/<[^>]+>/g, (match) => {
      tagPlaceholders.push(match);
      return `__TAG_PLACEHOLDER_${tagPlaceholders.length - 1}__`;
    });

    // Apply search highlighting to text content
    const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
    const regex = new RegExp(`(${escapedQuery})`, "gi");
    processed = processed.replace(regex, '<mark class="search-highlight">$1</mark>');

    // Restore HTML tags
    processed = processed.replace(/__TAG_PLACEHOLDER_(\d+)__/g, (_, index) => {
      return tagPlaceholders[parseInt(index)];
    });

    // Restore code blocks
    processed = processed.replace(/__CODE_SEARCH_PLACEHOLDER_(\d+)__/g, (_, index) => {
      return codeBlockPlaceholders[parseInt(index)];
    });

    return processed;
  }

  // Two-stage reactive rendering:
  // Stage 1 — only re-runs when `content` changes (expensive: marked + hljs + spoilers)
  let parsedHtml = $derived.by(() => {
    try {
      const html = marked.parse(content) as string;
      const withSpoilers = processSpoilers(html);
      return linkifyFilePaths(withSpoilers);
    } catch {
      return content;
    }
  });

  // Stage 2 — re-runs when search changes; skips re-parsing markdown entirely
  let renderedHtml = $derived(highlightSearchMatches(parsedHtml, searchQuery));

  function handleSpoilerClick(event: Event) {
    const target = event.target as HTMLElement;
    if (target.classList.contains("spoiler")) {
      target.classList.toggle("revealed");
    }
  }

  function handleSpoilerKeydown(event: KeyboardEvent) {
    const target = event.target as HTMLElement;
    if (target.classList.contains("spoiler") && (event.key === "Enter" || event.key === " ")) {
      event.preventDefault();
      target.classList.toggle("revealed");
    }
  }

  function handleLinkClick(event: MouseEvent) {
    const target = event.target as HTMLElement;
    const anchor = target.closest("a");
    if (!anchor) return;

    event.preventDefault();

    const filePath = anchor.dataset.filepath;
    if (filePath) {
      void invoke("open_binary_file", { path: filePath });
      return;
    }

    if (anchor.href) {
      void openUrl(anchor.href);
    }
  }

  async function handleCopyClick(event: MouseEvent) {
    const target = event.target as HTMLElement;
    const copyBtn = target.closest(".copy-code-btn") as HTMLButtonElement;
    if (copyBtn) {
      event.preventDefault();
      const code = copyBtn.dataset.code
        ?.replace(/&quot;/g, '"')
        .replace(/&lt;/g, "<")
        .replace(/&gt;/g, ">");
      if (code) {
        await navigator.clipboard.writeText(code);

        // Capture to clipboard history
        const langElement = copyBtn.parentElement?.querySelector(".code-block-lang");
        const language = langElement?.textContent || null;
        await clipboardStore.captureClipboard(code, language, "Claude response");

        const textSpan = copyBtn.querySelector(".copy-text");
        if (textSpan) {
          textSpan.textContent = "Copied!";
          setTimeout(() => {
            textSpan.textContent = "Copy";
          }, 2000);
        }
      }
    }
  }

  onMount(() => {
    if (containerElement) {
      containerElement.querySelectorAll("pre code:not(.hljs)").forEach((block) => {
        hljs.highlightElement(block as HTMLElement);
      });
    }
  });
</script>

<div
  bind:this={containerElement}
  class="markdown-content"
  onclick={(e) => {
    handleSpoilerClick(e);
    handleLinkClick(e);
    handleCopyClick(e);
  }}
  onkeydown={handleSpoilerKeydown}
  role="presentation"
>
  <!-- eslint-disable-next-line svelte/no-at-html-tags -- Markdown rendering requires @html; content is from Claude API -->
  {@html renderedHtml}
</div>

<style>
  .markdown-content {
    line-height: 1.6;
  }

  .markdown-content :global(p) {
    margin: 0.5em 0;
  }

  .markdown-content :global(p:first-child) {
    margin-top: 0;
  }

  .markdown-content :global(p:last-child) {
    margin-bottom: 0;
  }

  .markdown-content :global(.code-block-wrapper) {
    margin: 0.75em 0;
    border-radius: 6px;
    border: 1px solid var(--border-color);
    overflow: hidden;
  }

  .markdown-content :global(.code-block-header) {
    display: flex;
    justify-content: space-between;
    align-items: center;
    background: var(--bg-secondary);
    padding: 0.4em 0.75em;
    border-bottom: 1px solid var(--border-color);
    font-size: 0.8em;
  }

  .markdown-content :global(.code-block-lang) {
    color: var(--text-secondary);
    font-family: "JetBrains Mono", "Fira Code", monospace;
    text-transform: lowercase;
  }

  .markdown-content :global(.copy-code-btn) {
    display: flex;
    align-items: center;
    gap: 0.4em;
    background: transparent;
    border: none;
    color: var(--text-secondary);
    cursor: pointer;
    padding: 0.25em 0.5em;
    border-radius: 4px;
    font-size: 0.9em;
    transition: all 0.15s ease;
  }

  .markdown-content :global(.copy-code-btn:hover) {
    background: var(--bg-hover);
    color: var(--text-primary);
  }

  .markdown-content :global(.copy-code-btn svg) {
    flex-shrink: 0;
  }

  .markdown-content :global(.hljs-code-block) {
    background: var(--bg-code, #1e1e2e);
    border-radius: 0;
    padding: 1em;
    margin: 0;
    overflow-x: auto;
    border: none;
  }

  .markdown-content :global(.hljs-code-block code) {
    background: transparent;
    padding: 0;
    font-size: 0.9em;
    font-family: "JetBrains Mono", "Fira Code", monospace;
  }

  .markdown-content :global(.hljs-inline) {
    background: var(--bg-code, #1e1e2e);
    padding: 0.2em 0.4em;
    border-radius: 4px;
    font-size: 0.9em;
    font-family: "JetBrains Mono", "Fira Code", monospace;
  }

  .markdown-content :global(ul) {
    margin: 0.5em 0;
    padding-left: 1.5em;
    list-style-type: disc;
  }

  .markdown-content :global(ol) {
    margin: 0.5em 0;
    padding-left: 1.5em;
    list-style-type: decimal;
  }

  .markdown-content :global(li) {
    margin: 0.25em 0;
  }

  .markdown-content :global(blockquote) {
    border-left: 3px solid var(--border-color);
    margin: 0.75em 0;
    padding-left: 1em;
    color: var(--text-secondary);
  }

  .markdown-content :global(a) {
    color: var(--accent-primary, #f472b6);
    text-decoration: underline;
  }

  .markdown-content :global(a:hover) {
    color: var(--accent-secondary, #e879f9);
  }

  .markdown-content :global(strong) {
    font-weight: 600;
  }

  .markdown-content :global(h1),
  .markdown-content :global(h2),
  .markdown-content :global(h3),
  .markdown-content :global(h4) {
    margin: 1em 0 0.5em 0;
    font-weight: 600;
  }

  .markdown-content :global(h1:first-child),
  .markdown-content :global(h2:first-child),
  .markdown-content :global(h3:first-child),
  .markdown-content :global(h4:first-child) {
    margin-top: 0;
  }

  .markdown-content :global(hr) {
    border: none;
    border-top: 1px solid var(--border-color);
    margin: 1em 0;
  }

  .markdown-content :global(table) {
    border-collapse: collapse;
    margin: 0.75em 0;
    width: 100%;
  }

  .markdown-content :global(th),
  .markdown-content :global(td) {
    border: 1px solid var(--border-color);
    padding: 0.5em;
    text-align: left;
  }

  .markdown-content :global(th) {
    background: var(--bg-secondary);
    font-weight: 600;
  }

  /* Highlight.js theme colors - using CSS variables for light/dark mode support */
  .markdown-content :global(.hljs) {
    color: var(--text-primary);
  }

  .markdown-content :global(.hljs-keyword),
  .markdown-content :global(.hljs-selector-tag),
  .markdown-content :global(.hljs-built_in),
  .markdown-content :global(.hljs-name) {
    color: var(--hljs-keyword);
  }

  .markdown-content :global(.hljs-string),
  .markdown-content :global(.hljs-attr),
  .markdown-content :global(.hljs-symbol),
  .markdown-content :global(.hljs-bullet) {
    color: var(--hljs-string);
  }

  .markdown-content :global(.hljs-number),
  .markdown-content :global(.hljs-literal) {
    color: var(--hljs-number);
  }

  .markdown-content :global(.hljs-comment),
  .markdown-content :global(.hljs-quote) {
    color: var(--hljs-comment);
    font-style: italic;
  }

  .markdown-content :global(.hljs-function),
  .markdown-content :global(.hljs-title) {
    color: var(--hljs-function);
  }

  .markdown-content :global(.hljs-type),
  .markdown-content :global(.hljs-class) {
    color: var(--hljs-type);
  }

  .markdown-content :global(.hljs-variable),
  .markdown-content :global(.hljs-template-variable) {
    color: var(--hljs-variable);
  }

  .markdown-content :global(.hljs-meta) {
    color: var(--hljs-meta);
  }

  .markdown-content :global(.hljs-tag) {
    color: var(--hljs-keyword);
  }

  .markdown-content :global(.hljs-attribute) {
    color: var(--hljs-function);
  }

  .markdown-content :global(.hljs-params) {
    color: var(--text-primary);
  }

  /* Spoiler tag styles */
  .markdown-content :global(.spoiler) {
    background: var(--text-primary);
    color: transparent;
    border-radius: 4px;
    padding: 0 0.25em;
    cursor: pointer;
    transition: all 0.2s ease;
    user-select: none;
  }

  .markdown-content :global(.spoiler:hover) {
    opacity: 0.8;
  }

  .markdown-content :global(.spoiler:focus) {
    outline: 2px solid var(--accent-primary);
    outline-offset: 2px;
  }

  .markdown-content :global(.spoiler.revealed) {
    background: var(--bg-hover);
    color: var(--text-primary);
    user-select: text;
  }

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

  .markdown-content :global(.file-link) {
    display: inline-flex;
    align-items: center;
    gap: 0.25em;
    color: var(--accent-primary, #f472b6);
    text-decoration: none;
    border: 1px solid color-mix(in srgb, var(--accent-primary) 30%, transparent);
    background: color-mix(in srgb, var(--accent-primary) 8%, transparent);
    border-radius: 4px;
    padding: 0.1em 0.4em;
    font-family: "JetBrains Mono", "Fira Code", monospace;
    font-size: 0.875em;
    cursor: pointer;
    transition: all 0.15s ease;
    word-break: break-all;
  }

  .markdown-content :global(.file-link:hover) {
    background: color-mix(in srgb, var(--accent-primary) 18%, transparent);
    border-color: color-mix(in srgb, var(--accent-primary) 60%, transparent);
    color: var(--accent-secondary, #e879f9);
  }
</style>
