<script lang="ts">
  import { Check, FileDiff, RotateCcw, Undo2 } from "@lucide/svelte";
  import iconUrl from "../../../src-tauri/icons/icon.png";
  import type { FileStatusKind, GitFileStatus, 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;
    onPatch: (file: GitFileStatus, staged: boolean) => void;
    onStageAll: () => void;
    onUnstageAll: () => void;
  }

  let {
    changedFiles = [],
    stagedCount = 0,
    unstagedCount = 0,
    hasRepository = false,
    isBusy = false,
    operation = "",
    status = null,
    selectedFilePath = "",
    onSelectFile = () => {},
    onStage = () => {},
    onUnstage = () => {},
    onDiscard = () => {},
    onDiscardMany = () => {},
    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}`;
  }

  let selectedStatusPaths = $state<Set<string>>(new Set());
  let selectionAnchorKey = $state("");

  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 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);

  $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>

<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-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 overflow-auto">
      <section class="status-lane" aria-label="Unstaged changes">
        <header class="status-lane-head">
          <div class="status-lane-title"><strong>Unstaged</strong><span>{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`}>
                <Check 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">
              <Check 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 unstagedFiles as file (`unstaged:${fileKey(file)}`)}
            {@const stageTargets = selectedStageTargets(file)}
            <article class="status-file-row" class:selected={isStatusSelected(file)} class:active={selectedFilePath === file.path}>
              <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"><Check 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>
          {/each}
          {#if unstagedCount === 0}<p class="status-lane-empty">No unstaged changes.</p>{/if}
        </div>
      </section>

      <section class="status-lane" aria-label="Staged changes">
        <header class="status-lane-head">
          <div class="status-lane-title"><strong>Staged</strong><span>{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`}>
                <Undo2 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">
              <Undo2 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 stagedFiles as file (`staged:${fileKey(file)}`)}
            {@const unstageTargets = selectedUnstageTargets(file)}
            <article class="status-file-row" class:selected={isStatusSelected(file)} class:active={selectedFilePath === file.path}>
              <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"><Undo2 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>
          {/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>

<style>
  .status-panel-overlay {
    position: absolute;
    inset: 0;
    z-index: 20;
    display: grid;
    place-items: center;
    background:
      radial-gradient(circle at 50% 38%, rgba(111, 140, 255, 0.1), transparent 55%),
      rgba(7, 10, 16, 0.62);
    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 rgba(90, 111, 154, 0.28);
    border-radius: 16px;
    background:
      linear-gradient(180deg, rgba(23, 29, 43, 0.92), rgba(12, 17, 27, 0.94)),
      var(--color-surface-raised);
    color: var(--color-ink);
    box-shadow: 0 18px 44px rgba(0, 0, 0, 0.34), inset 0 1px 0 rgba(255, 255, 255, 0.05);
  }

  .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 rgba(111, 140, 255, 0.22);
    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: rgba(77, 182, 214, 0.24);
    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 rgba(77, 182, 214, 0.32));
    animation: status-panel-overlay-trace-draw 2.6s ease-in-out infinite;
  }
  .status-panel-overlay-traces .trace-main { stroke: #6f8cff; }
  .status-panel-overlay-traces .trace-branch {
    stroke: #4db6d6;
    animation-delay: 0.28s;
  }
  .status-panel-overlay-traces .trace-cut {
    stroke: rgba(177, 186, 208, 0.42);
    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 rgba(0, 0, 0, 0.5))
      drop-shadow(0 0 10px rgba(77, 182, 214, 0.2));
    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;
    background: rgba(111, 140, 255, 0.14);
  }
  .status-panel-overlay-bar span {
    position: absolute;
    inset: 0;
    width: 46%;
    border-radius: inherit;
    background: linear-gradient(90deg, transparent, #6f8cff 40%, #4db6d6 74%, transparent);
    box-shadow: 0 0 12px rgba(77, 182, 214, 0.26);
    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>
