<script lang="ts">
  import { Check, ChevronDown, ChevronRight, Folder, FolderOpen, GitBranch, GitMerge, Pencil, Plus, Trash2, X } from "@lucide/svelte";
  import type { GitBranch as GitBranchInfo } from "../types";

  type BranchTreeNode = BranchFolderNode | BranchLeafNode;

  interface BranchFolderNode {
    kind: "folder";
    id: string;
    name: string;
    children: BranchTreeNode[];
    branchCount: number;
    current: boolean;
    folders: Map<string, BranchFolderNode>;
  }

  interface BranchLeafNode {
    kind: "branch";
    id: string;
    branch: GitBranchInfo;
    displayName: string;
  }

  type BranchRow = BranchFolderRow | BranchLeafRow;

  interface BranchFolderRow {
    kind: "folder";
    id: string;
    name: string;
    depth: number;
    branchCount: number;
    current: boolean;
  }

  interface BranchLeafRow {
    kind: "branch";
    id: string;
    branch: GitBranchInfo;
    displayName: string;
    depth: number;
    scopeLabel: string;
  }

  interface Props {
    branches: GitBranchInfo[];
    localBranches: GitBranchInfo[];
    remoteBranches: GitBranchInfo[];
    hasRepository: boolean;
    isBusy: boolean;
    onCheckout: (branch: GitBranchInfo) => void;
    onMerge: (branch: GitBranchInfo) => void;
    onCreateBranch: (branchName: string) => void | Promise<void>;
    onRenameBranch: (branch: GitBranchInfo) => void | Promise<void>;
    onDeleteBranch: (branch: GitBranchInfo) => void | Promise<void>;
  }

  let {
    branches = [],
    localBranches = [],
    remoteBranches = [],
    hasRepository = false,
    isBusy = false,
    onCheckout = () => {},
    onMerge = () => {},
    onCreateBranch = () => {},
    onRenameBranch = () => {},
    onDeleteBranch = () => {},
  }: Props = $props();

  let localOpen = $state(true);
  let remoteOpen = $state(false);
  let createOpen = $state(false);
  let newBranchName = $state("");
  let createInput = $state<HTMLInputElement | null>(null);
  let panelElement = $state<HTMLElement | null>(null);
  let contextBranch = $state<GitBranchInfo | null>(null);
  let contextMenuX = $state(0);
  let contextMenuY = $state(0);
  let collapsedBranchFolders = $state<Set<string>>(new Set());

  let localBranchRows = $derived(buildBranchRows("local", localBranches, "local"));
  let remoteBranchRows = $derived(buildBranchRows("remote", remoteBranches, "remote"));

  function createFolder(id: string, name: string): BranchFolderNode {
    return {
      kind: "folder",
      id,
      name,
      children: [],
      branchCount: 0,
      current: false,
      folders: new Map(),
    };
  }

  function buildBranchRows(scope: string, branchList: GitBranchInfo[], scopeLabel: string): BranchRow[] {
    const root = createFolder(`${scope}:root`, "");

    for (const branch of branchList) {
      const parts = branch.name.split("/").filter(Boolean);
      const displayName = parts.length > 0 ? parts[parts.length - 1] : branch.name;
      const folderParts = parts.slice(0, -1);
      let parent = root;

      for (let index = 0; index < folderParts.length; index += 1) {
        const folderName = folderParts[index];
        const folderPath = folderParts.slice(0, index + 1).join("/");
        let folder = parent.folders.get(folderName);

        if (!folder) {
          folder = createFolder(`${scope}:folder:${folderPath}`, folderName);
          parent.folders.set(folderName, folder);
          parent.children.push(folder);
        }

        folder.branchCount += 1;
        folder.current ||= branch.current;
        parent = folder;
      }

      parent.children.push({
        kind: "branch",
        id: `${scope}:branch:${branch.name}`,
        branch,
        displayName,
      });
    }

    sortBranchNodes(root.children);

    const rows: BranchRow[] = [];
    flattenBranchNodes(root.children, rows, 0, scopeLabel);
    return rows;
  }

  function sortBranchNodes(nodes: BranchTreeNode[]) {
    nodes.sort((left, right) => {
      if (left.kind !== right.kind) return left.kind === "folder" ? -1 : 1;
      const leftName = left.kind === "folder" ? left.name : left.displayName;
      const rightName = right.kind === "folder" ? right.name : right.displayName;
      return leftName.localeCompare(rightName, undefined, { sensitivity: "base" });
    });

    for (const node of nodes) {
      if (node.kind === "folder") sortBranchNodes(node.children);
    }
  }

  function flattenBranchNodes(nodes: BranchTreeNode[], rows: BranchRow[], depth: number, scopeLabel: string) {
    for (const node of nodes) {
      if (node.kind === "folder") {
        rows.push({
          kind: "folder",
          id: node.id,
          name: node.name,
          depth,
          branchCount: node.branchCount,
          current: node.current,
        });

        if (isBranchFolderOpen(node.id)) {
          flattenBranchNodes(node.children, rows, depth + 1, scopeLabel);
        }
      } else {
        rows.push({
          kind: "branch",
          id: node.id,
          branch: node.branch,
          displayName: node.displayName,
          depth,
          scopeLabel,
        });
      }
    }
  }

  function isBranchFolderOpen(id: string) {
    return !collapsedBranchFolders.has(id);
  }

  function toggleBranchFolder(id: string) {
    const next = new Set(collapsedBranchFolders);
    if (next.has(id)) next.delete(id);
    else next.add(id);
    collapsedBranchFolders = next;
  }

  function openCreateForm() {
    if (!hasRepository || isBusy) return;
    createOpen = true;
    queueMicrotask(() => createInput?.focus());
  }

  function closeCreateForm() {
    createOpen = false;
    newBranchName = "";
  }

  async function submitCreate(event: SubmitEvent) {
    event.preventDefault();
    const value = newBranchName.trim();
    if (!value || !hasRepository || isBusy) return;
    await onCreateBranch(value);
    newBranchName = "";
    createOpen = false;
    localOpen = true;
  }

  function checkoutOnDoubleClick(event: MouseEvent, branch: GitBranchInfo) {
    if (branch.current || isBusy) return;
    const target = event.target instanceof HTMLElement ? event.target : null;
    if (target?.closest("button")) return;
    onCheckout(branch);
  }

  function openBranchContextMenu(event: MouseEvent, branch: GitBranchInfo) {
    event.preventDefault();
    event.stopPropagation();
    if (isBusy || branch.remote) return;

    const rect = panelElement?.getBoundingClientRect();
    const rawX = rect ? event.clientX - rect.left : event.offsetX;
    const rawY = rect ? event.clientY - rect.top : event.offsetY;
    const maxX = Math.max(8, (rect?.width ?? window.innerWidth) - 192);
    const maxY = Math.max(8, (rect?.height ?? window.innerHeight) - 92);

    contextBranch = branch;
    contextMenuX = Math.max(8, Math.min(rawX, maxX));
    contextMenuY = Math.max(8, Math.min(rawY, maxY));
  }

  function closeBranchContextMenu() {
    contextBranch = null;
  }

  async function renameContextBranch() {
    const branch = contextBranch;
    if (!branch || isBusy) return;
    closeBranchContextMenu();
    await onRenameBranch(branch);
  }

  async function deleteContextBranch() {
    const branch = contextBranch;
    if (!branch || branch.current || isBusy) return;
    closeBranchContextMenu();
    await onDeleteBranch(branch);
  }

  function handleWindowKeydown(event: KeyboardEvent) {
    if (event.key === "Escape") closeBranchContextMenu();
  }
</script>

<svelte:window on:click={closeBranchContextMenu} on:keydown={handleWindowKeydown} />

<section bind:this={panelElement} class="panel branch-panel grid grid-rows-[auto_1fr] overflow-hidden" aria-label="Branches">
  <div class="section-head">
    <div>
      <span class="eyebrow">Branches</span>
      <h2 class="mt-0.5 text-ink text-base font-bold leading-tight">Refs</h2>
    </div>
    <div class="branch-head-actions">
      <button
        class="branch-create-toggle"
        type="button"
        onclick={openCreateForm}
        disabled={!hasRepository || isBusy}
        title="Create new branch"
        aria-label="Create new branch"
      >
        <Plus size={14} aria-hidden="true" />
      </button>
      <span class="pill pill-count">{branches.length}</span>
    </div>
  </div>

  {#if !hasRepository}
    <p class="blank-state">Open a repository to list branches.</p>
  {:else if branches.length === 0}
    <p class="blank-state">No branches returned.</p>
  {:else}
    <div class="branch-list overflow-auto p-2 flex flex-col gap-0">
      {#if createOpen}
        <form class="branch-create-form" onsubmit={submitCreate}>
          <GitBranch size={15} aria-hidden="true" />
          <input
            bind:this={createInput}
            bind:value={newBranchName}
            disabled={isBusy}
            autocomplete="off"
            spellcheck="false"
            placeholder="new-branch-name"
            aria-label="New branch name"
          />
          <button class="branch-create-action confirm" type="submit" disabled={isBusy || newBranchName.trim().length === 0} title="Create branch">
            <Check size={14} aria-hidden="true" />
          </button>
          <button class="branch-create-action" type="button" onclick={closeCreateForm} disabled={isBusy} title="Cancel">
            <X size={14} aria-hidden="true" />
          </button>
        </form>
      {/if}

      <div class="branch-group">
        <button
          class="branch-group-toggle"
          type="button"
          onclick={() => { localOpen = !localOpen; }}
          aria-expanded={localOpen}
        >
          {#if localOpen}
            <ChevronDown size={14} aria-hidden="true" />
          {:else}
            <ChevronRight size={14} aria-hidden="true" />
          {/if}
          <span>Local</span>
          <span class="branch-group-count">{localBranches.length}</span>
        </button>

        {#if localOpen}
          {#if localBranches.length === 0}
            <div class="branch-empty">No local branches.</div>
          {:else}
            {#each localBranchRows as row (row.id)}
              {#if row.kind === "folder"}
                <button
                  class="branch-folder-row"
                  class:current={row.current}
                  style={`--branch-indent: ${row.depth * 16}px;`}
                  type="button"
                  onclick={() => toggleBranchFolder(row.id)}
                  aria-expanded={isBranchFolderOpen(row.id)}
                  title={`${row.name} (${row.branchCount})`}
                >
                  {#if isBranchFolderOpen(row.id)}
                    <ChevronDown size={14} aria-hidden="true" />
                    <FolderOpen size={15} aria-hidden="true" />
                  {:else}
                    <ChevronRight size={14} aria-hidden="true" />
                    <Folder size={15} aria-hidden="true" />
                  {/if}
                  <span class="branch-folder-name">{row.name}</span>
                  <span class="branch-folder-count">{row.branchCount}</span>
                </button>
              {:else}
                <article
                  class="branch-row"
                  class:current={row.branch.current}
                  style={`--branch-indent: ${row.depth * 16}px;`}
                  ondblclick={(event) => checkoutOnDoubleClick(event, row.branch)}
                  oncontextmenu={(event) => openBranchContextMenu(event, row.branch)}
                  title={row.branch.current ? "Current branch" : row.branch.name}
                >
                  <div class="branch-info">
                    <GitBranch size={16} aria-hidden="true" />
                    <div>
                      <strong>{row.displayName}</strong>
                      <span>{row.scopeLabel}</span>
                    </div>
                  </div>
                  {#if row.branch.current}
                    <span class="pill pill-active">Current</span>
                  {:else}
                    <div class="branch-actions">
                      <button class="btn-sm" type="button" onclick={() => onCheckout(row.branch)} disabled={isBusy}>
                        Checkout
                      </button>
                      <button class="btn-sm" type="button" onclick={() => onMerge(row.branch)} disabled={isBusy} title="Merge into current">
                        <GitMerge size={15} aria-hidden="true" />
                        Merge
                      </button>
                    </div>
                  {/if}
                </article>
              {/if}
            {/each}
          {/if}
        {/if}
      </div>

      <div class="branch-group">
        <button
          class="branch-group-toggle"
          type="button"
          onclick={() => { remoteOpen = !remoteOpen; }}
          aria-expanded={remoteOpen}
        >
          {#if remoteOpen}
            <ChevronDown size={14} aria-hidden="true" />
          {:else}
            <ChevronRight size={14} aria-hidden="true" />
          {/if}
          <span>Remote</span>
          <span class="branch-group-count">{remoteBranches.length}</span>
        </button>

        {#if remoteOpen}
          {#if remoteBranches.length === 0}
            <div class="branch-empty">No remote branches.</div>
          {:else}
            {#each remoteBranchRows as row (row.id)}
              {#if row.kind === "folder"}
                <button
                  class="branch-folder-row"
                  class:current={row.current}
                  style={`--branch-indent: ${row.depth * 16}px;`}
                  type="button"
                  onclick={() => toggleBranchFolder(row.id)}
                  aria-expanded={isBranchFolderOpen(row.id)}
                  title={`${row.name} (${row.branchCount})`}
                >
                  {#if isBranchFolderOpen(row.id)}
                    <ChevronDown size={14} aria-hidden="true" />
                    <FolderOpen size={15} aria-hidden="true" />
                  {:else}
                    <ChevronRight size={14} aria-hidden="true" />
                    <Folder size={15} aria-hidden="true" />
                  {/if}
                  <span class="branch-folder-name">{row.name}</span>
                  <span class="branch-folder-count">{row.branchCount}</span>
                </button>
              {:else}
                <article
                  class="branch-row"
                  class:current={row.branch.current}
                  style={`--branch-indent: ${row.depth * 16}px;`}
                  ondblclick={(event) => checkoutOnDoubleClick(event, row.branch)}
                  title={row.branch.current ? "Current branch" : row.branch.name}
                >
                  <div class="branch-info">
                    <GitBranch size={16} aria-hidden="true" />
                    <div>
                      <strong>{row.displayName}</strong>
                      <span>{row.scopeLabel}</span>
                    </div>
                  </div>
                  {#if row.branch.current}
                    <span class="pill pill-active">Current</span>
                  {:else}
                    <div class="branch-actions">
                      <button class="btn-sm" type="button" onclick={() => onCheckout(row.branch)} disabled={isBusy}>
                        Checkout
                      </button>
                      <button class="btn-sm" type="button" onclick={() => onMerge(row.branch)} disabled={isBusy} title="Merge into current">
                        <GitMerge size={15} aria-hidden="true" />
                        Merge
                      </button>
                    </div>
                  {/if}
                </article>
              {/if}
            {/each}
          {/if}
        {/if}
      </div>
    </div>
  {/if}

  {#if contextBranch}
    <div
      class="branch-context-menu"
      style={`left: ${contextMenuX}px; top: ${contextMenuY}px;`}
      role="menu"
      tabindex="-1"
      aria-label={`Actions for ${contextBranch.name}`}
    >
      <button type="button" role="menuitem" onclick={renameContextBranch} disabled={isBusy}>
        <Pencil size={14} aria-hidden="true" />
        Rename
      </button>
      <button
        class="danger"
        type="button"
        role="menuitem"
        onclick={deleteContextBranch}
        disabled={isBusy || contextBranch.current}
        title={contextBranch.current ? "Current branch cannot be deleted" : "Delete local branch"}
      >
        <Trash2 size={14} aria-hidden="true" />
        Delete
      </button>
    </div>
  {/if}
</section>
