<script lang="ts">
  import SelectMenu from "./SelectMenu.svelte";
  import {
    CalendarDays,
    FileCode,
    FileText,
    GitCompare,
    History,
    LoaderCircle,
    RotateCcw,
    Search,
    User,
    X,
  } from "@lucide/svelte";
  import { languageIconForPath } from "../languageIcons";
  import type { GitCommit, GitRepositoryFile, GitSearchHit } from "../types";
  import LanguageIcon from "./LanguageIcon.svelte";

  const commitDateFormatter = new Intl.DateTimeFormat(undefined, {
    dateStyle: "medium",
    timeStyle: "short",
  });

  type SearchTab = "code" | "files";

  interface Props {
    hasRepository: boolean;
    isBusy: boolean;
    isSearching: boolean;
    error: string;
    results: GitSearchHit[];
    files: GitRepositoryFile[];
    fileHistory: GitCommit[];
    selectedFilePath: string;
    onClose: () => void;
    onSearch: (query: string, caseSensitive: boolean, limit: number) => void | Promise<void>;
    onCancel: () => void | Promise<void>;
    onDiff: (hit: GitSearchHit) => void | Promise<void>;
    onSelectFile: (file: GitRepositoryFile) => void | Promise<void>;
    onFileHistoryDiff: (commit: GitCommit) => void | Promise<void>;
    onFileHistoryRestore: (commit: GitCommit) => void | Promise<void>;
  }

  let {
    hasRepository = false,
    isBusy = false,
    isSearching = false,
    error = "",
    results = [],
    files = [],
    fileHistory = [],
    selectedFilePath = "",
    onClose = () => {},
    onSearch = () => {},
    onCancel = () => {},
    onDiff = () => {},
    onSelectFile = () => {},
    onFileHistoryDiff = () => {},
    onFileHistoryRestore = () => {},
  }: Props = $props();

  let activeTab = $state<SearchTab>("code");
  let query = $state("");
  let caseSensitive = $state(false);
  let limit = $state(250);
  let searchedQuery = $state("");
  let searched = $state(false);
  let fileQuery = $state("");

  const fileSearchActive = $derived(fileQuery.trim().length > 0);
  const fileSearchResults = $derived(filterFiles(files, fileQuery, 200));

  function submit(event?: SubmitEvent) {
    event?.preventDefault();
    const value = query.trim();
    if (!value || !hasRepository || isBusy || isSearching) return;
    searched = true;
    searchedQuery = value;
    void onSearch(value, caseSensitive, limit);
  }

  function handleKeydown(event: KeyboardEvent) {
    if ((event.ctrlKey || event.metaKey) && event.key === "Enter") {
      submit();
    }
  }

  function formatCommitDate(value: string): string {
    const date = new Date(value);
    if (Number.isNaN(date.getTime())) return value;
    return commitDateFormatter.format(date);
  }

  function displayPath(hit: GitSearchHit): string {
    return hit.old_file ? `${hit.old_file} -> ${hit.file}` : hit.file;
  }

  function fileName(path: string): string {
    return path.split(/[\\/]/).filter(Boolean).pop() ?? path;
  }

  function folderName(path: string): string {
    const parts = path.split(/[\\/]/).filter(Boolean);
    parts.pop();
    return parts.length > 0 ? parts.join("/") : "Repository root";
  }

  function filterFiles(source: GitRepositoryFile[], value: string, maxResults: number): GitRepositoryFile[] {
    const terms = value
      .trim()
      .toLowerCase()
      .split(/\s+/)
      .filter(Boolean);
    if (terms.length === 0) return [];

    return source
      .filter((file) => {
        const path = file.path.toLowerCase();
        const name = fileName(file.path).toLowerCase();
        return terms.every((term) => path.includes(term) || name.includes(term));
      })
      .sort((a, b) => {
        const aName = fileName(a.path).toLowerCase();
        const bName = fileName(b.path).toLowerCase();
        const first = terms[0] ?? "";
        const aStarts = aName.startsWith(first) ? 0 : 1;
        const bStarts = bName.startsWith(first) ? 0 : 1;
        if (aStarts !== bStarts) return aStarts - bStarts;
        return a.path.localeCompare(b.path);
      })
      .slice(0, maxResults);
  }
</script>

<div
  class="dialog-backdrop"
  role="presentation"
>
  <div class="dialog global-search-dialog" role="dialog" aria-modal="true" aria-label="Global search" tabindex="-1">
    <header class="dialog-header">
      <div>
        <span class="eyebrow">Global search</span>
        <h2 class="dialog-title">{activeTab === "code" ? "Find where code was introduced" : "Find file history"}</h2>
      </div>
      <button class="dialog-close" type="button" onclick={onClose} title="Close">
        <X size={18} aria-hidden="true" />
      </button>
    </header>

    <div class="global-search-body">
      <div class="global-search-tabs" role="tablist" aria-label="Search mode">
        <button
          class="global-search-tab"
          class:active={activeTab === "code"}
          type="button"
          role="tab"
          aria-selected={activeTab === "code"}
          onclick={() => { activeTab = "code"; }}
        >
          <Search size={14} aria-hidden="true" />
          Code
        </button>
        <button
          class="global-search-tab"
          class:active={activeTab === "files"}
          type="button"
          role="tab"
          aria-selected={activeTab === "files"}
          onclick={() => { activeTab = "files"; }}
        >
          <FileText size={14} aria-hidden="true" />
          Files
        </button>
      </div>

      {#if activeTab === "code"}
        <form class="global-search-form" onsubmit={submit}>
          <label class="global-search-query">
            <span>String or function</span>
            <textarea
              bind:value={query}
              onkeydown={handleKeydown}
              disabled={!hasRepository || isBusy || isSearching}
              spellcheck="false"
              placeholder={"Paste a string, symbol, or full function body..."}
            ></textarea>
          </label>

          <div class="global-search-options">
            <label class="check-row">
              <input type="checkbox" bind:checked={caseSensitive} disabled={!hasRepository || isBusy || isSearching} />
              <span>Exact case</span>
            </label>

            <label class="search-limit">
              <span>Results</span>
              <SelectMenu value={String(limit)} options={[100, 250, 500, 1000].map((value) => ({ value: String(value), label: String(value) }))} disabled={!hasRepository || isBusy || isSearching} onChange={(value) => { limit = Number(value); }} />
            </label>

            <button class="btn-primary" type="submit" disabled={!hasRepository || isBusy || isSearching || query.trim().length === 0}>
              {#if isSearching}
                <LoaderCircle class="spin" size={16} aria-hidden="true" />
              {:else}
                <Search size={16} aria-hidden="true" />
              {/if}
              Search
            </button>

            {#if isSearching}
              <button class="btn-secondary search-cancel" type="button" onclick={onCancel}>
                <X size={16} aria-hidden="true" />
                Cancel
              </button>
            {/if}
          </div>
        </form>

        <section class="global-search-results" aria-live="polite">
          {#if !hasRepository}
            <div class="blank-state">Open a repository first.</div>
          {:else if isSearching}
            <div class="blank-state">
              <LoaderCircle class="spin" size={20} aria-hidden="true" />
              Searching all branches...
            </div>
          {:else if error}
            <div class="blank-state search-error">{error}</div>
          {:else if !searched}
            <div class="blank-state">Search a string or paste a complete function to find where it was added.</div>
          {:else if results.length === 0}
            <div class="blank-state">No introduction found for "{searchedQuery}".</div>
          {:else}
            <div class="search-result-head">
              <strong>{results.length}</strong>
              <span>{results.length === 1 ? "introduction" : "introductions"} found for "{searchedQuery}"</span>
            </div>

            <div class="search-hit-list">
              {#each results as hit (`${hit.commit_hash}:${hit.file}:${hit.line_number ?? 0}`)}
                <article class="search-hit">
                  <header class="search-hit-top">
                    <span class="hash">{hit.short_hash}</span>
                    <strong title={hit.summary}>{hit.summary || "No commit message"}</strong>
                    {#if hit.matches_added > 1}
                      <span class="pill pill-active">+{hit.matches_added} matches</span>
                    {/if}
                    <button
                      class="btn-secondary search-hit-diff"
                      type="button"
                      disabled={isBusy}
                      title={`Compare this version of ${hit.file} with the current version`}
                      onclick={() => onDiff(hit)}
                    >
                      <GitCompare size={14} aria-hidden="true" />
                      DIFF
                    </button>
                  </header>

                  <div class="search-hit-meta">
                    <span><User size={12} aria-hidden="true" />{hit.author_name || "Unknown author"}</span>
                    <span><CalendarDays size={12} aria-hidden="true" />{formatCommitDate(hit.date)}</span>
                  </div>

                  <div class="search-hit-file" title={displayPath(hit)}>
                    <FileCode size={14} aria-hidden="true" />
                    <span>{displayPath(hit)}</span>
                    {#if hit.line_number}
                      <strong>:{hit.line_number}</strong>
                    {/if}
                  </div>

                  <pre class="search-hit-line">{hit.line || searchedQuery}</pre>
                </article>
              {/each}
            </div>
          {/if}
        </section>
      {:else}
        <form class="global-search-form file-search-form" onsubmit={(event) => event.preventDefault()}>
          <label class="global-search-query">
            <span>File name or path</span>
            <input
              bind:value={fileQuery}
              disabled={!hasRepository || isBusy}
              spellcheck="false"
              autocomplete="off"
              placeholder="Search files, folders, extensions..."
            />
          </label>
        </form>

        <section class="global-search-results file-search-results" aria-live="polite">
          {#if !hasRepository}
            <div class="blank-state">Open a repository first.</div>
          {:else if files.length === 0}
            <div class="blank-state">No files loaded for this repository.</div>
          {:else}
            <div class="file-search-split">
              <div class="file-search-column">
                {#if !fileSearchActive}
                  <div class="blank-state">Search a file name or path, then choose a result to show its history.</div>
                {:else if fileSearchResults.length === 0}
                  <div class="blank-state">No file found for "{fileQuery.trim()}".</div>
                {:else}
                  <div class="search-result-head">
                    <strong>{fileSearchResults.length}</strong>
                    <span>{fileSearchResults.length === 1 ? "file" : "files"} found for "{fileQuery.trim()}"</span>
                  </div>

                  <div class="file-search-list">
                    {#each fileSearchResults as file (file.path)}
                      {@const languageIcon = languageIconForPath(file.path)}
                      <button
                        class="file-search-hit"
                        class:active={selectedFilePath === file.path}
                        type="button"
                        disabled={isBusy || isSearching}
                        title={`Show history for ${file.path}`}
                        onclick={() => onSelectFile(file)}
                      >
                        <span class="file-search-icon">
                          {#if languageIcon}
                            <LanguageIcon icon={languageIcon.icon} title={languageIcon.title} />
                          {:else}
                            <FileText size={16} aria-hidden="true" />
                          {/if}
                        </span>

                        <span class="file-search-main">
                          <strong>{fileName(file.path)}</strong>
                          <span>{folderName(file.path)}</span>
                        </span>

                        {#if file.status}
                          <small class={`status-badge ${file.status}`}>{file.status}</small>
                        {:else if !file.tracked}
                          <small class="status-badge untracked">untracked</small>
                        {/if}

                        <span class="file-search-action">
                          <History size={14} aria-hidden="true" />
                          History
                        </span>
                      </button>
                    {/each}
                  </div>
                {/if}
              </div>

              <aside class="file-search-history" aria-label="Selected file history">
                <header class="file-search-history-head">
                  <span>History</span>
                  <strong title={selectedFilePath}>{selectedFilePath ? fileName(selectedFilePath) : "No file selected"}</strong>
                </header>

                {#if !selectedFilePath}
                  <div class="blank-state">Select a file result to load its commit history.</div>
                {:else if isBusy}
                  <div class="blank-state">
                    <LoaderCircle class="spin" size={18} aria-hidden="true" />
                    Loading history...
                  </div>
                {:else if fileHistory.length === 0}
                  <div class="blank-state">No history returned for this file.</div>
                {:else}
                  <div class="file-search-history-list">
                    {#each fileHistory as commit (commit.hash)}
                      <article class="file-search-history-row">
                        <div class="file-search-history-main">
                          <span class="hash">{commit.short_hash}</span>
                          <strong title={commit.summary}>{commit.summary || "No commit message"}</strong>
                          <span>{commit.author_name || "Unknown author"} - {formatCommitDate(commit.date)}</span>
                        </div>

                        <div class="file-search-history-actions">
                          <button class="btn-sm" type="button" onclick={() => onFileHistoryDiff(commit)} disabled={isBusy}>
                            <GitCompare size={14} aria-hidden="true" />
                            Diff
                          </button>
                          <button class="btn-sm" type="button" onclick={() => onFileHistoryRestore(commit)} disabled={isBusy}>
                            <RotateCcw size={14} aria-hidden="true" />
                            Restore
                          </button>
                        </div>
                      </article>
                    {/each}
                  </div>
                {/if}
              </aside>
            </div>
          {/if}
        </section>
      {/if}
    </div>
  </div>
</div>
