<script lang="ts">
  import {
    Archive,
    ArrowLeft,
    ArrowRight,
    FileDiff,
    FileMinus2,
    FileType,
    FileX,
    Folder,
    FolderOpen,
    FolderMinus,
    FolderTree,
    FolderX,
    RotateCcw,
  } from "@lucide/svelte";
  import iconUrl from "../../../src-tauri/icons/icon.png";
  import type { FileStatusKind, GitFileStatus, GitIgnoreKind, GitStatus } from "../types";

  interface Props {
    changedFiles: GitFileStatus[];
    stagedCount: number;
    unstagedCount: number;
    hasRepository: boolean;
    isBusy: boolean;
    operation: string;
    status: GitStatus | null;
    selectedFilePath: string;
    onSelectFile: (file: GitFileStatus) => void;
    onStage: (files: GitFileStatus[]) => void;
    onUnstage: (files: GitFileStatus[]) => void;
    onDiscard: (files: GitFileStatus[], staged: boolean) => void;
    onDiscardMany: (files: GitFileStatus[]) => void;
    onStash: (files: GitFileStatus[], label: string) => void;
    onIgnore: (target: string, kind: GitIgnoreKind) => void;
    onStopTracking: (target: string, kind: "file" | "folder") => void;
    onPatch: (file: GitFileStatus, staged: boolean) => void;
    onStageAll: () => void;
    onUnstageAll: () => void;
  }

  type StatusViewMode = "list" | "tree";
  type StatusLaneKind = "unstaged" | "staged";

  interface StatusFolderNode {
    kind: "folder";
    name: string;
    path: string;
    depth: number;
    fileCount: number;
    children: StatusTreeNode[];
  }

  interface StatusFileNode {
    kind: "file";
    name: string;
    path: string;
    depth: number;
    file: GitFileStatus;
  }

  type StatusTreeNode = StatusFolderNode | StatusFileNode;

  interface StatusContextTarget {
    lane: StatusLaneKind;
    kind: "file" | "folder";
    label: string;
    files: GitFileStatus[];
  }

  let {
    changedFiles = [],
    stagedCount = 0,
    unstagedCount = 0,
    hasRepository = false,
    isBusy = false,
    operation = "",
    status = null,
    selectedFilePath = "",
    onSelectFile = () => {},
    onStage = () => {},
    onUnstage = () => {},
    onDiscard = () => {},
    onDiscardMany = () => {},
    onStash = () => {},
    onIgnore = () => {},
    onStopTracking = () => {},
    onPatch = () => {},
    onStageAll = () => {},
    onUnstageAll = () => {},
  }: Props = $props();

  function statusLabel(kind: FileStatusKind | null): string {
    return kind ?? "none";
  }

  function displayPath(file: GitFileStatus): string {
    return file.old_path ? `${file.old_path} -> ${file.path}` : file.path;
  }

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

  function fileName(file: GitFileStatus): string {
    return file.old_path ? `${baseName(file.old_path)} -> ${baseName(file.path)}` : baseName(file.path);
  }

  function canPatch(kind: FileStatusKind | null): boolean {
    return kind === "modified";
  }

  function fileKey(file: GitFileStatus): string {
    return `${file.old_path ?? ""}:${file.path}`;
  }

  function sortStatusTree(nodes: StatusTreeNode[]) {
    nodes.sort((a, b) => {
      if (a.kind !== b.kind) return a.kind === "folder" ? -1 : 1;
      return a.name.localeCompare(b.name);
    });
    for (const node of nodes) {
      if (node.kind === "folder") sortStatusTree(node.children);
    }
  }

  function buildStatusTree(files: GitFileStatus[]): StatusTreeNode[] {
    const roots: StatusTreeNode[] = [];
    const folders = new Map<string, StatusFolderNode>();

    for (const file of [...files].sort((a, b) => a.path.localeCompare(b.path))) {
      const parts = file.path.split(/[\\/]+/).filter(Boolean);
      if (parts.length === 0) continue;

      let currentPath = "";
      let siblings = roots;
      const ancestors: StatusFolderNode[] = [];

      for (let index = 0; index < parts.length - 1; index++) {
        currentPath = currentPath ? `${currentPath}/${parts[index]}` : parts[index];
        let folder = folders.get(currentPath);
        if (!folder) {
          folder = {
            kind: "folder",
            name: parts[index],
            path: currentPath,
            depth: index,
            fileCount: 0,
            children: [],
          };
          folders.set(currentPath, folder);
          siblings.push(folder);
        }
        ancestors.push(folder);
        siblings = folder.children;
      }

      siblings.push({
        kind: "file",
        name: parts[parts.length - 1] ?? file.path,
        path: file.path,
        depth: Math.max(parts.length - 1, 0),
        file,
      });
      for (const folder of ancestors) folder.fileCount += 1;
    }

    sortStatusTree(roots);
    return roots;
  }

  function flattenStatusTree(nodes: StatusTreeNode[], lane: StatusLaneKind): StatusTreeNode[] {
    const visible: StatusTreeNode[] = [];
    for (const node of nodes) {
      visible.push(node);
      if (node.kind === "folder" && !collapsedStatusFolders.has(`${lane}:${node.path}`)) {
        visible.push(...flattenStatusTree(node.children, lane));
      }
    }
    return visible;
  }

  function listStatusRows(files: GitFileStatus[]): StatusFileNode[] {
    return files.map((file) => ({
      kind: "file",
      name: fileName(file),
      path: file.path,
      depth: 0,
      file,
    }));
  }

  let selectedStatusPaths = $state<Set<string>>(new Set());
  let selectionAnchorKey = $state("");
  let statusView = $state<StatusViewMode>("list");
  let collapsedStatusFolders = $state<Set<string>>(new Set());
  let statusContextTarget = $state<StatusContextTarget | null>(null);
  let statusContextMenuX = $state(0);
  let statusContextMenuY = $state(0);
  let statusContextMenuElement = $state<HTMLDivElement | null>(null);

  function toggleStatusFolder(lane: StatusLaneKind, path: string) {
    const next = new Set(collapsedStatusFolders);
    const key = `${lane}:${path}`;
    if (next.has(key)) next.delete(key);
    else next.add(key);
    collapsedStatusFolders = next;
  }

  function filesInStatusFolder(lane: StatusLaneKind, path: string): GitFileStatus[] {
    const prefix = `${path.replace(/\\/g, "/").replace(/\/$/, "")}/`;
    const files = lane === "unstaged" ? unstagedFiles : stagedFiles;
    return files.filter((file) => {
      const currentPath = file.path.replace(/\\/g, "/");
      const oldPath = file.old_path?.replace(/\\/g, "/") ?? "";
      return currentPath.startsWith(prefix) || oldPath.startsWith(prefix);
    });
  }

  function openStatusContextMenu(
    event: MouseEvent,
    lane: StatusLaneKind,
    kind: "file" | "folder",
    label: string,
    files: GitFileStatus[],
  ) {
    event.preventDefault();
    event.stopPropagation();
    if (files.length === 0) return;
    statusContextMenuX = Math.max(8, Math.min(event.clientX, window.innerWidth - 288));
    statusContextMenuY = Math.max(8, Math.min(event.clientY, window.innerHeight - 174));
    statusContextTarget = { lane, kind, label, files };
    requestAnimationFrame(() => {
      if (!statusContextMenuElement) return;
      const bounds = statusContextMenuElement.getBoundingClientRect();
      statusContextMenuX = Math.max(8, Math.min(event.clientX, window.innerWidth - bounds.width - 8));
      statusContextMenuY = Math.max(8, Math.min(event.clientY, window.innerHeight - bounds.height - 8));
    });
  }

  function statusContextName(label: string): string {
    const normalized = label.replace(/\\/g, "/").replace(/\/+$/, "");
    return normalized.split("/").pop() || label;
  }

  function statusContextParent(label: string): string {
    const normalized = label.replace(/\\/g, "/").replace(/\/+$/, "");
    const separator = normalized.lastIndexOf("/");
    return separator > 0 ? normalized.slice(0, separator) : "Repository root";
  }

  function closeStatusContextMenu() {
    statusContextTarget = null;
  }

  function runStatusContextStageAction() {
    const target = statusContextTarget;
    if (!target) return;
    closeStatusContextMenu();
    if (target.lane === "unstaged") onStage(target.files);
    else onUnstage(target.files);
  }

  function runStatusContextStashAction() {
    const target = statusContextTarget;
    if (!target) return;
    closeStatusContextMenu();
    onStash(target.files, target.label);
  }

  function isIgnoreableNewFile(file: GitFileStatus): boolean {
    const path = file.path.replace(/\\/g, "/").toLowerCase();
    if (path === ".gitignore") return false;
    return file.unstaged === "untracked" || (file.staged === "added" && file.old_path === null);
  }

  function isTrackedStatusFile(file: GitFileStatus): boolean {
    return file.unstaged !== "untracked" && file.staged !== "deleted";
  }

  function statusContextExtension(label: string): string {
    const name = statusContextName(label);
    const separator = name.lastIndexOf(".");
    return separator > 0 && separator < name.length - 1 ? name.slice(separator + 1) : "";
  }

  function statusContextFolder(target: StatusContextTarget): string {
    if (target.kind === "folder") return target.label.replace(/\\/g, "/").replace(/\/+$/, "");
    const normalized = target.label.replace(/\\/g, "/");
    const separator = normalized.lastIndexOf("/");
    return separator > 0 ? normalized.slice(0, separator) : "";
  }

  function runStatusContextIgnoreAction(kind: GitIgnoreKind) {
    const target = statusContextTarget;
    if (!target) return;
    const ignoreTarget = kind === "folder" ? statusContextFolder(target) : target.label;
    if (!ignoreTarget) return;
    closeStatusContextMenu();
    onIgnore(ignoreTarget, kind);
  }

  function runStatusContextStopTracking() {
    const target = statusContextTarget;
    if (!target) return;
    closeStatusContextMenu();
    onStopTracking(target.label, target.kind);
  }

  function handleStatusWindowKeydown(event: KeyboardEvent) {
    if (event.key === "Escape") closeStatusContextMenu();
  }

  function isStatusSelected(file: GitFileStatus): boolean {
    return selectedStatusPaths.has(fileKey(file));
  }

  function selectedFiles(): GitFileStatus[] {
    return changedFiles.filter((file) => selectedStatusPaths.has(fileKey(file)));
  }

  function selectedStageTargets(file: GitFileStatus): GitFileStatus[] {
    const files = isStatusSelected(file) ? selectedFiles() : [file];
    return files.filter((item) => item.unstaged !== null);
  }

  function selectedUnstageTargets(file: GitFileStatus): GitFileStatus[] {
    const files = isStatusSelected(file) ? selectedFiles() : [file];
    return files.filter((item) => item.staged !== null);
  }

  function handleFileSelect(event: MouseEvent, file: GitFileStatus) {
    const key = fileKey(file);
    const allKeys = changedFiles.map(fileKey);
    const next = new Set(selectedStatusPaths);

    if (event.shiftKey && selectionAnchorKey) {
      const anchorIndex = allKeys.indexOf(selectionAnchorKey);
      const currentIndex = allKeys.indexOf(key);
      if (anchorIndex >= 0 && currentIndex >= 0) {
        const start = Math.min(anchorIndex, currentIndex);
        const end = Math.max(anchorIndex, currentIndex);
        for (const itemKey of allKeys.slice(start, end + 1)) next.add(itemKey);
      } else {
        next.add(key);
      }
    } else if (event.ctrlKey || event.metaKey) {
      if (next.has(key)) next.delete(key);
      else next.add(key);
      selectionAnchorKey = key;
    } else {
      next.clear();
      next.add(key);
      selectionAnchorKey = key;
    }

    selectedStatusPaths = next;
    onSelectFile(file);
  }

  function stageFromFile(file: GitFileStatus) {
    const targets = selectedStageTargets(file);
    if (targets.length === 0) return;
    onStage(targets);
  }

  function unstageFromFile(file: GitFileStatus) {
    const targets = selectedUnstageTargets(file);
    if (targets.length === 0) return;
    onUnstage(targets);
  }

  // Discard mirrors the stage/unstage target selection: if the clicked row is
  // part of the current multi-selection, the whole selection (filtered to the
  // relevant lane) is discarded; otherwise just that one file.
  function discardStagedFromFile(file: GitFileStatus) {
    const targets = selectedUnstageTargets(file);
    if (targets.length === 0) return;
    onDiscard(targets, true);
  }

  function discardUnstagedFromFile(file: GitFileStatus) {
    const targets = selectedStageTargets(file);
    if (targets.length === 0) return;
    onDiscard(targets, false);
  }

  let hasUnstaged = $derived(changedFiles.some((f) => f.unstaged !== null));
  let hasStaged = $derived(changedFiles.some((f) => f.staged !== null));
  let unstagedFiles = $derived(changedFiles.filter((file) => file.unstaged !== null));
  let stagedFiles = $derived(changedFiles.filter((file) => file.staged !== null));
  let unstagedTree = $derived(buildStatusTree(unstagedFiles));
  let stagedTree = $derived(buildStatusTree(stagedFiles));
  let visibleUnstagedRows = $derived(statusView === "tree" ? flattenStatusTree(unstagedTree, "unstaged") : listStatusRows(unstagedFiles));
  let visibleStagedRows = $derived(statusView === "tree" ? flattenStatusTree(stagedTree, "staged") : listStatusRows(stagedFiles));
  let selectedUnstagedCount = $derived(changedFiles.filter((f) => selectedStatusPaths.has(fileKey(f)) && f.unstaged !== null).length);
  let selectedStagedCount = $derived(changedFiles.filter((f) => selectedStatusPaths.has(fileKey(f)) && f.staged !== null).length);
  let statusContextCanIgnore = $derived(statusContextTarget?.files.some(isIgnoreableNewFile) ?? false);
  let statusContextCanStopTracking = $derived(statusContextTarget?.files.some(isTrackedStatusFile) ?? false);
  let statusContextIgnoreExtension = $derived(statusContextTarget?.kind === "file" ? statusContextExtension(statusContextTarget.label) : "");
  let statusContextIgnoreFolder = $derived(statusContextTarget ? statusContextFolder(statusContextTarget) : "");

  $effect(() => {
    const validKeys = new Set(changedFiles.map(fileKey));
    const next = new Set([...selectedStatusPaths].filter((key) => validKeys.has(key)));
    if (next.size !== selectedStatusPaths.size) selectedStatusPaths = next;
    if (selectionAnchorKey && !validKeys.has(selectionAnchorKey)) selectionAnchorKey = "";
  });
</script>

<svelte:window onclick={closeStatusContextMenu} onkeydown={handleStatusWindowKeydown} />

<section class="panel status-panel relative grid grid-rows-[auto_1fr] overflow-hidden" aria-label="Working tree status">
  <div class="section-head">
    <div>
      <span class="eyebrow">Workspace</span>
      <h2 class="mt-0.5 text-ink text-base font-bold leading-tight">Changes</h2>
    </div>
    <div class="status-view-switch" role="group" aria-label="Changes view">
      <button class:active={statusView === "list"} type="button" onclick={() => statusView = "list"} title="List view" aria-label="List view" aria-pressed={statusView === "list"}>
        <i class="status-view-icon list-icon" aria-hidden="true"></i><span>List</span>
      </button>
      <button class:active={statusView === "tree"} type="button" onclick={() => statusView = "tree"} title="Tree view" aria-label="Tree view" aria-pressed={statusView === "tree"}>
        <FolderTree size={13} aria-hidden="true" /><span>Tree</span>
      </button>
    </div>
    <div class="status-head-actions">
      <span class="pill pill-count">{stagedCount} staged</span>
      <span class="pill pill-count">{unstagedCount} unstaged</span>
      {#if hasRepository && changedFiles.length > 0}
        <button class="btn-sm status-discard-all" type="button" onclick={() => onDiscardMany(changedFiles)} disabled={isBusy} title="Discard all staged and unstaged changes">
          <RotateCcw size={13} aria-hidden="true" /> Discard all
        </button>
      {/if}
    </div>
  </div>

  {#if !hasRepository}
    <div class="blank-state">No repository loaded.</div>
  {:else if status?.clean}
    <div class="blank-state">Working tree is clean.</div>
  {:else if changedFiles.length === 0}
    <div class="blank-state">No file changes returned.</div>
  {:else}
    <div class="status-lanes">
      <section class="status-lane unstaged-lane" aria-label="Unstaged changes">
        <header class="status-lane-head">
          <div class="status-lane-title">
            <div class="status-lane-copy"><strong>Unstaged</strong><small>Working tree</small></div>
            <span class="status-lane-count">{unstagedCount}</span>
          </div>
          <div class="status-lane-actions">
            {#if selectedUnstagedCount > 1}
              <span class="status-selection-count">{selectedUnstagedCount} selected</span>
              <button class="btn-sm" type="button" onclick={() => onStage(changedFiles.filter((file) => selectedStatusPaths.has(fileKey(file)) && file.unstaged !== null))} disabled={isBusy} title={`Stage ${selectedUnstagedCount} selected files`}>
                <ArrowRight size={13} aria-hidden="true" /> Stage {selectedUnstagedCount}
              </button>
            {/if}
            <button class="btn-sm" type="button" onclick={onStageAll} disabled={isBusy || !hasUnstaged} title="Stage all unstaged files">
              <ArrowRight size={13} aria-hidden="true" /> Stage all
            </button>
            {#if selectedUnstagedCount > 1}
              <button class="btn-sm danger" type="button" onclick={() => onDiscard(changedFiles.filter((file) => selectedStatusPaths.has(fileKey(file)) && file.unstaged !== null), false)} disabled={isBusy} title={`Discard unstaged changes in ${selectedUnstagedCount} selected files`}>
                <RotateCcw size={13} aria-hidden="true" /> Discard {selectedUnstagedCount}
              </button>
            {/if}
          </div>
        </header>
        <div class="status-file-list">
          {#each visibleUnstagedRows as row (`unstaged:${row.kind}:${row.kind === "file" ? fileKey(row.file) : row.path}`)}
            {#if row.kind === "folder"}
              <button class="status-folder-row" type="button" style={`--tree-indent: ${row.depth * 15}px`} onclick={() => toggleStatusFolder("unstaged", row.path)} oncontextmenu={(event) => openStatusContextMenu(event, "unstaged", "folder", row.path, filesInStatusFolder("unstaged", row.path))} title={row.path} aria-expanded={!collapsedStatusFolders.has(`unstaged:${row.path}`)}>
                <i class:expanded={!collapsedStatusFolders.has(`unstaged:${row.path}`)} class="status-tree-chevron" aria-hidden="true"></i>
                {#if collapsedStatusFolders.has(`unstaged:${row.path}`)}<Folder class="status-folder-lucide" size={15} aria-hidden="true" />{:else}<FolderOpen class="status-folder-lucide" size={15} aria-hidden="true" />{/if}
                <strong>{row.name}</strong>
                <span class="status-folder-count">{row.fileCount}</span>
              </button>
            {:else}
              {@const file = row.file}
              {@const stageTargets = selectedStageTargets(file)}
              <article class={`status-file-row${statusView === "tree" ? " status-tree-file" : ""}`} style={`--tree-indent: ${row.depth * 15}px`} class:selected={isStatusSelected(file)} class:active={selectedFilePath === file.path} oncontextmenu={(event) => openStatusContextMenu(event, "unstaged", "file", file.path, [file])}>
                <button class="status-file-main" type="button" onclick={(event) => handleFileSelect(event, file)} title={`Select ${displayPath(file)} in Explorer`}>
                  <strong>{fileName(file)}</strong>
                  <span>{displayPath(file)}</span>
                </button>
                <span class={`status-badge ${file.unstaged ?? "none"}`}>{statusLabel(file.unstaged)}</span>
                <div class="status-file-actions">
                  <button type="button" onclick={() => stageFromFile(file)} disabled={isBusy || stageTargets.length === 0} title="Stage file"><ArrowRight size={14} aria-hidden="true" /></button>
                  <button type="button" onclick={() => onPatch(file, false)} disabled={isBusy || !canPatch(file.unstaged)} title="Show unstaged details"><FileDiff size={14} aria-hidden="true" /></button>
                  <button class="danger" type="button" onclick={() => discardUnstagedFromFile(file)} disabled={isBusy || stageTargets.length === 0} title="Discard unstaged changes"><RotateCcw size={14} aria-hidden="true" /></button>
                </div>
              </article>
            {/if}
          {/each}
          {#if unstagedCount === 0}<p class="status-lane-empty">No unstaged changes.</p>{/if}
        </div>
      </section>

      <div class="status-flow-divider" aria-hidden="true">
        <span><ArrowRight size={12} /></span>
      </div>

      <section class="status-lane staged-lane" aria-label="Staged changes">
        <header class="status-lane-head">
          <div class="status-lane-title">
            <div class="status-lane-copy"><strong>Staged</strong><small>Next commit</small></div>
            <span class="status-lane-count">{stagedCount}</span>
          </div>
          <div class="status-lane-actions">
            {#if selectedStagedCount > 1}
              <span class="status-selection-count">{selectedStagedCount} selected</span>
              <button class="btn-sm" type="button" onclick={() => onUnstage(changedFiles.filter((file) => selectedStatusPaths.has(fileKey(file)) && file.staged !== null))} disabled={isBusy} title={`Unstage ${selectedStagedCount} selected files`}>
                <ArrowLeft size={13} aria-hidden="true" /> Unstage {selectedStagedCount}
              </button>
            {/if}
            <button class="btn-sm" type="button" onclick={onUnstageAll} disabled={isBusy || !hasStaged} title="Unstage all staged files">
              <ArrowLeft size={13} aria-hidden="true" /> Unstage all
            </button>
            {#if selectedStagedCount > 1}
              <button class="btn-sm danger" type="button" onclick={() => onDiscard(changedFiles.filter((file) => selectedStatusPaths.has(fileKey(file)) && file.staged !== null), true)} disabled={isBusy} title={`Discard staged changes in ${selectedStagedCount} selected files`}>
                <RotateCcw size={13} aria-hidden="true" /> Discard {selectedStagedCount}
              </button>
            {/if}
          </div>
        </header>
        <div class="status-file-list">
          {#each visibleStagedRows as row (`staged:${row.kind}:${row.kind === "file" ? fileKey(row.file) : row.path}`)}
            {#if row.kind === "folder"}
              <button class="status-folder-row" type="button" style={`--tree-indent: ${row.depth * 15}px`} onclick={() => toggleStatusFolder("staged", row.path)} oncontextmenu={(event) => openStatusContextMenu(event, "staged", "folder", row.path, filesInStatusFolder("staged", row.path))} title={row.path} aria-expanded={!collapsedStatusFolders.has(`staged:${row.path}`)}>
                <i class:expanded={!collapsedStatusFolders.has(`staged:${row.path}`)} class="status-tree-chevron" aria-hidden="true"></i>
                {#if collapsedStatusFolders.has(`staged:${row.path}`)}<Folder class="status-folder-lucide" size={15} aria-hidden="true" />{:else}<FolderOpen class="status-folder-lucide" size={15} aria-hidden="true" />{/if}
                <strong>{row.name}</strong>
                <span class="status-folder-count">{row.fileCount}</span>
              </button>
            {:else}
              {@const file = row.file}
              {@const unstageTargets = selectedUnstageTargets(file)}
              <article class={`status-file-row${statusView === "tree" ? " status-tree-file" : ""}`} style={`--tree-indent: ${row.depth * 15}px`} class:selected={isStatusSelected(file)} class:active={selectedFilePath === file.path} oncontextmenu={(event) => openStatusContextMenu(event, "staged", "file", file.path, [file])}>
                <button class="status-file-main" type="button" onclick={(event) => handleFileSelect(event, file)} title={`Select ${displayPath(file)} in Explorer`}>
                  <strong>{fileName(file)}</strong>
                  <span>{displayPath(file)}</span>
                </button>
                <span class={`status-badge ${file.staged ?? "none"}`}>{statusLabel(file.staged)}</span>
                <div class="status-file-actions">
                  <button type="button" onclick={() => unstageFromFile(file)} disabled={isBusy || unstageTargets.length === 0} title="Unstage file"><ArrowLeft size={14} aria-hidden="true" /></button>
                  <button type="button" onclick={() => onPatch(file, true)} disabled={isBusy || !canPatch(file.staged)} title="Show staged details"><FileDiff size={14} aria-hidden="true" /></button>
                  <button class="danger" type="button" onclick={() => discardStagedFromFile(file)} disabled={isBusy || unstageTargets.length === 0} title="Discard staged changes"><RotateCcw size={14} aria-hidden="true" /></button>
                </div>
              </article>
            {/if}
          {/each}
          {#if stagedCount === 0}<p class="status-lane-empty">Stage files to include them in the next commit.</p>{/if}
        </div>
      </section>
    </div>
  {/if}

  {#if isBusy && hasRepository}
    <div class="status-panel-overlay" role="status" aria-live="polite">
      <div class="status-panel-overlay-card">
        <div class="status-panel-overlay-mark">
          <span class="status-panel-overlay-halo halo-one"></span>
          <span class="status-panel-overlay-halo halo-two"></span>
          <svg class="status-panel-overlay-traces" viewBox="0 0 220 220" aria-hidden="true">
            <path class="trace trace-main" d="M28 154 C72 114, 88 108, 110 110 S156 116, 192 68" />
            <path class="trace trace-branch" d="M62 74 C100 88, 126 124, 158 166" />
            <path class="trace trace-cut" d="M46 180 L174 180" />
          </svg>
          <img src={iconUrl} alt="" class="status-panel-overlay-icon" />
        </div>
        <span class="status-panel-overlay-label">{operation || "Working"}…</span>
        <div class="status-panel-overlay-bar"><span></span></div>
      </div>
    </div>
  {/if}
</section>

{#if statusContextTarget}
  <div bind:this={statusContextMenuElement} class="status-context-menu" style={`left: ${statusContextMenuX}px; top: ${statusContextMenuY}px;`} role="menu" tabindex="-1" aria-label={`Actions for ${statusContextTarget.label}`} onclick={(event) => event.stopPropagation()} onkeydown={(event) => { if (event.key === "Escape") closeStatusContextMenu(); event.stopPropagation(); }}>
    <div class="status-context-label">
      <span class="status-context-object-icon" aria-hidden="true">
        {#if statusContextTarget.kind === "folder"}<FolderOpen size={16} />{:else}<FileDiff size={16} />{/if}
      </span>
      <span class="status-context-object-copy">
        <span class="status-context-kind">{statusContextTarget.lane === "unstaged" ? "Unstaged" : "Staged"} {statusContextTarget.kind}</span>
        <strong title={statusContextTarget.label}>{statusContextName(statusContextTarget.label)}</strong>
        <span class="status-context-path" title={statusContextTarget.label}><Folder size={10} aria-hidden="true" />{statusContextParent(statusContextTarget.label)}</span>
      </span>
      <span class="status-context-count" title={`${statusContextTarget.files.length} ${statusContextTarget.files.length === 1 ? "file" : "files"}`}>
        {statusContextTarget.files.length}
      </span>
    </div>
    <button type="button" role="menuitem" onclick={runStatusContextStageAction} disabled={isBusy}>
      <span class="status-context-action-icon" aria-hidden="true">
        {#if statusContextTarget.lane === "unstaged"}<ArrowRight size={15} />{:else}<ArrowLeft size={15} />{/if}
      </span>
      <span class="status-context-action-copy">
        <strong>{statusContextTarget.lane === "unstaged" ? "Stage" : "Unstage"} {statusContextTarget.kind}</strong>
        <span>{statusContextTarget.lane === "unstaged" ? "Add to the next commit" : "Move back to working changes"}</span>
      </span>
    </button>
    <button type="button" role="menuitem" onclick={runStatusContextStashAction} disabled={isBusy}>
      <span class="status-context-action-icon" aria-hidden="true"><Archive size={15} /></span>
      <span class="status-context-action-copy">
        <strong>Stash {statusContextTarget.kind}</strong>
        <span>Save {statusContextTarget.files.length === 1 ? "this file" : `${statusContextTarget.files.length} files`} for later</span>
      </span>
    </button>
    {#if statusContextCanIgnore || statusContextCanStopTracking}
      <div class="menu-separator" role="separator"></div>
    {/if}
    {#if statusContextCanStopTracking}
      <button type="button" role="menuitem" onclick={runStatusContextStopTracking} disabled={isBusy} title="Keep the working-tree content and remove it from the Git index">
        <span class="status-context-action-icon untrack" aria-hidden="true">
          {#if statusContextTarget.kind === "folder"}<FolderMinus size={15} />{:else}<FileMinus2 size={15} />{/if}
        </span>
        <span class="status-context-action-copy">
          <strong>Stop tracking {statusContextTarget.kind}</strong>
          <span>Keep it on disk and remove it from Git</span>
        </span>
      </button>
    {/if}
    {#if statusContextCanIgnore}
      {#if statusContextTarget.kind === "file"}
        <button type="button" role="menuitem" onclick={() => runStatusContextIgnoreAction("file")} disabled={isBusy} title={`Add /${statusContextTarget.label.replace(/\\/g, "/")} to .gitignore`}>
          <span class="status-context-action-icon ignore" aria-hidden="true"><FileX size={15} /></span>
          <span class="status-context-action-copy">
            <strong>Ignore file</strong>
            <span>Add only this file to .gitignore</span>
          </span>
        </button>
      {/if}
      {#if statusContextIgnoreExtension}
        <button type="button" role="menuitem" onclick={() => runStatusContextIgnoreAction("extension")} disabled={isBusy} title={`Add *.${statusContextIgnoreExtension} to .gitignore`}>
          <span class="status-context-action-icon ignore" aria-hidden="true"><FileType size={15} /></span>
          <span class="status-context-action-copy">
            <strong>Ignore all *.{statusContextIgnoreExtension} files</strong>
            <span>Match this file type repository-wide</span>
          </span>
        </button>
      {/if}
      {#if statusContextTarget.kind === "folder" && statusContextIgnoreFolder}
        <button type="button" role="menuitem" onclick={() => runStatusContextIgnoreAction("folder")} disabled={isBusy} title={`Add /${statusContextIgnoreFolder}/ to .gitignore`}>
          <span class="status-context-action-icon ignore" aria-hidden="true"><FolderX size={15} /></span>
          <span class="status-context-action-copy">
            <strong>Ignore folder</strong>
            <span>Add this folder and its contents to .gitignore</span>
          </span>
        </button>
      {/if}
    {/if}
  </div>
{/if}

<style>
  .status-panel-overlay {
    position: absolute;
    inset: 0;
    z-index: 20;
    display: grid;
    place-items: center;
    background:
      radial-gradient(
        circle at 50% 38%,
        color-mix(in srgb, var(--color-accent) 13%, transparent),
        transparent 55%
      ),
      var(--app-dialog-backdrop);
    backdrop-filter: blur(4px);
    animation: status-panel-overlay-in 120ms ease;
  }

  .status-panel-overlay-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    width: min(300px, calc(100% - 32px));
    padding: 26px 28px 26px;
    border: 1px solid var(--color-border-input);
    border-radius: 16px;
    background:
      var(--app-panel-highlight),
      var(--app-dialog-bg);
    color: var(--color-ink);
    box-shadow: var(--app-dialog-shadow);
  }

  .status-panel-overlay-mark {
    position: relative;
    width: 104px;
    height: 104px;
    display: grid;
    place-items: center;
    isolation: isolate;
  }

  .status-panel-overlay-halo {
    position: absolute;
    inset: 6px;
    border: 1px solid color-mix(in srgb, var(--color-primary) 34%, transparent);
    border-radius: 22px;
    transform: rotate(45deg);
  }
  .status-panel-overlay-halo.halo-one {
    animation: status-panel-overlay-halo-breathe 2.4s ease-in-out infinite;
  }
  .status-panel-overlay-halo.halo-two {
    inset: 16px;
    border-color: color-mix(in srgb, var(--color-accent) 38%, transparent);
    animation: status-panel-overlay-halo-breathe 2.4s ease-in-out infinite reverse;
  }

  .status-panel-overlay-traces {
    position: absolute;
    inset: -12px;
    width: 128px;
    height: 128px;
    overflow: visible;
    z-index: 0;
  }
  .status-panel-overlay-traces .trace {
    fill: none;
    stroke-width: 3;
    stroke-linecap: round;
    stroke-dasharray: 165;
    stroke-dashoffset: 165;
    filter: drop-shadow(0 0 6px color-mix(in srgb, var(--color-accent) 38%, transparent));
    animation: status-panel-overlay-trace-draw 2.6s ease-in-out infinite;
  }
  .status-panel-overlay-traces .trace-main { stroke: var(--color-primary); }
  .status-panel-overlay-traces .trace-branch {
    stroke: var(--color-accent);
    animation-delay: 0.28s;
  }
  .status-panel-overlay-traces .trace-cut {
    stroke: color-mix(in srgb, var(--color-ink-muted) 52%, transparent);
    stroke-dasharray: 128;
    stroke-dashoffset: 128;
    animation-delay: 0.55s;
  }

  .status-panel-overlay-icon {
    position: relative;
    z-index: 1;
    width: 64px;
    height: 64px;
    object-fit: contain;
    filter:
      drop-shadow(0 8px 12px color-mix(in srgb, var(--color-ink) 24%, transparent))
      drop-shadow(0 0 10px color-mix(in srgb, var(--color-accent) 24%, transparent));
    animation: status-panel-overlay-icon-float 2.4s ease-in-out infinite;
  }

  .status-panel-overlay-label {
    max-width: 100%;
    overflow: hidden;
    color: var(--color-ink);
    font-size: 14px;
    font-weight: 700;
    text-align: center;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .status-panel-overlay-bar {
    position: relative;
    width: min(190px, 100%);
    height: 4px;
    overflow: hidden;
    border-radius: 999px;
    border: 1px solid var(--color-border-subtle);
    background: color-mix(in srgb, var(--color-primary) 12%, var(--color-surface-dim));
  }
  .status-panel-overlay-bar span {
    position: absolute;
    inset: 0;
    width: 46%;
    border-radius: inherit;
    background: linear-gradient(90deg, transparent, var(--color-primary) 40%, var(--color-accent) 74%, transparent);
    box-shadow: 0 0 12px color-mix(in srgb, var(--color-accent) 32%, transparent);
    animation: status-panel-overlay-bar-slide 1.35s ease-in-out infinite;
  }

  @keyframes status-panel-overlay-in { from { opacity: 0; } to { opacity: 1; } }
  @keyframes status-panel-overlay-icon-float {
    0%, 100% { transform: translateY(0) scale(1); }
    50% { transform: translateY(-3px) scale(1.015); }
  }
  @keyframes status-panel-overlay-halo-breathe {
    0%, 100% { opacity: 0.35; transform: rotate(45deg) scale(0.95); }
    50% { opacity: 0.8; transform: rotate(45deg) scale(1.04); }
  }
  @keyframes status-panel-overlay-bar-slide {
    0% { transform: translateX(-120%); }
    100% { transform: translateX(320%); }
  }
  @keyframes status-panel-overlay-trace-draw {
    0% { stroke-dashoffset: 165; opacity: 0; }
    36% { opacity: 1; }
    64%, 100% { stroke-dashoffset: 0; opacity: 0.72; }
  }

  @media (prefers-reduced-motion: reduce) {
    .status-panel-overlay,
    .status-panel-overlay-halo,
    .status-panel-overlay-traces .trace,
    .status-panel-overlay-icon,
    .status-panel-overlay-bar span { animation: none; }
    .status-panel-overlay-traces .trace { stroke-dashoffset: 0; opacity: 0.72; }
  }
</style>
