<script lang="ts">
  import { untrack } from "svelte";
  import { AlertCircle, Check, ExternalLink, GitMerge, LoaderCircle, X } from "@lucide/svelte";
  import type { ConflictChoice, ConflictFile, ConflictPart, GitFileStatus, PreparedResolution } from "../types";

  type ConflictRegion = Extract<ConflictPart, { kind: "conflict" }>;
  type ResolveSplitRow =
    | { type: "marker"; conflictIndex: number }
    | {
        type: "pair";
        conflictIndex?: number;
        leftNum?: number; leftText?: string; leftKind: "context" | "ours" | "empty";
        rightNum?: number; rightText?: string; rightKind: "context" | "theirs" | "empty";
      };

  interface Props {
    conflictedFiles: GitFileStatus[];
    conflictTarget: string;
    conflict: ConflictFile | null;
    preparedResolutions: Record<string, PreparedResolution>;
    isBusy: boolean;
    operation: string;
    onClose: () => void;
    onSelectFile: (path: string) => void;
    onMarkResolved: (path: string, resolution: PreparedResolution) => void;
    onApply: () => void;
    onExternalMerge: (path: string) => void | Promise<void>;
    language?: "en" | "de";
    mergeName?: string;
  }

  let {
    conflictedFiles = [],
    conflictTarget = "",
    conflict = null,
    preparedResolutions = {},
    isBusy = false,
    operation = "",
    onClose = () => {},
    onSelectFile = () => {},
    onMarkResolved = () => {},
    onApply = () => {},
    onExternalMerge = () => {},
    language = "en",
    mergeName = "merge tool",
  }: Props = $props();
  const isGerman = $derived(language === "de");

  let conflictParts = $derived<ConflictPart[]>(
    conflict && !conflict.binary ? parseConflicts(conflict.content) : [],
  );
  let conflictChoices = $state<(ConflictChoice | null)[]>([]);
  let resolveContent = $state("");
  let manualMode = $state(false);
  let binarySide = $state<"ours" | "theirs" | null>(null);
  let resolveBeforePane = $state<HTMLDivElement | null>(null);
  let resolveAfterPane = $state<HTMLDivElement | null>(null);
  let isSyncingResolveScroll = false;

  $effect(() => {
    const c = conflict;
    const target = conflictTarget;
    const parts = conflictParts;

    untrack(() => {
      if (!c) {
        conflictChoices = [];
        resolveContent = "";
        manualMode = false;
        binarySide = null;
        return;
      }

      const prepared = preparedResolutions[target];

      if (c.binary) {
        conflictChoices = [];
        resolveContent = "";
        manualMode = false;
        binarySide = prepared?.kind === "side" ? prepared.side : null;
        return;
      }

      binarySide = null;
      conflictChoices = parts.filter((p) => p.kind === "conflict").map(() => null);

      if (prepared?.kind === "content") {
        resolveContent = prepared.content;
        manualMode = true;
      } else {
        resolveContent = c.content;
        manualMode = false;
      }
    });
  });

  let conflictRegionCount = $derived(conflictParts.filter((p) => p.kind === "conflict").length);
  let conflictRegions = $derived(conflictOnlyParts(conflictParts));
  let resolveSplitRows = $derived(buildResolveSplitRows(conflictParts));
  let unresolvedCount = $derived(manualMode ? 0 : conflictChoices.filter((c) => c == null).length);
  let resolvedContent = $derived(manualMode ? resolveContent : buildResolution(conflictParts, conflictChoices));
  let resolveHasMarkers = $derived(/^<{7}/m.test(resolvedContent) || /^>{7}/m.test(resolvedContent));
  let preparedCount = $derived(Object.keys(preparedResolutions).length);
  let currentPrepared = $derived(conflictTarget.length > 0 && preparedResolutions[conflictTarget] != null);
  let canMarkResolved = $derived(!!conflict && !isBusy && (conflict.binary ? binarySide != null : !resolveHasMarkers));

  function parseConflicts(content: string): ConflictPart[] {
    const lines = content.split("\n");
    const parts: ConflictPart[] = [];
    let textLines: string[] = [];
    let regionIndex = 0;
    let index = 0;

    const flushText = () => {
      if (textLines.length > 0) { parts.push({ kind: "text", lines: textLines }); textLines = []; }
    };

    while (index < lines.length) {
      const line = lines[index];
      if (line.startsWith("<<<<<<<")) {
        flushText(); index++;
        const oursLines: string[] = [];
        while (index < lines.length && !lines[index].startsWith("=======") && !lines[index].startsWith("|||||||")) oursLines.push(lines[index++]);
        if (index < lines.length && lines[index].startsWith("|||||||")) { index++; while (index < lines.length && !lines[index].startsWith("=======")) index++; }
        if (index < lines.length && lines[index].startsWith("=======")) index++;
        const theirsLines: string[] = [];
        while (index < lines.length && !lines[index].startsWith(">>>>>>>")) theirsLines.push(lines[index++]);
        if (index < lines.length && lines[index].startsWith(">>>>>>>")) index++;
        parts.push({ kind: "conflict", index: regionIndex++, oursLines, theirsLines });
      } else { textLines.push(line); index++; }
    }
    flushText();
    return parts;
  }

  function buildResolution(parts: ConflictPart[], choices: (ConflictChoice | null)[]): string {
    const out: string[] = [];
    for (const part of parts) {
      if (part.kind === "text") { out.push(...part.lines); continue; }
      const choice = choices[part.index];
      if (choice === "ours") out.push(...part.oursLines);
      else if (choice === "theirs") out.push(...part.theirsLines);
      else if (choice === "both-ot") out.push(...part.oursLines, ...part.theirsLines);
      else if (choice === "both-to") out.push(...part.theirsLines, ...part.oursLines);
      else out.push("<<<<<<< current", ...part.oursLines, "=======", ...part.theirsLines, ">>>>>>> incoming");
    }
    return out.join("\n");
  }

  function conflictOnlyParts(parts: ConflictPart[]): ConflictRegion[] {
    return parts.filter((part): part is ConflictRegion => part.kind === "conflict");
  }

  function buildResolveSplitRows(parts: ConflictPart[]): ResolveSplitRow[] {
    const rows: ResolveSplitRow[] = [];
    let leftNum = 0;
    let rightNum = 0;

    for (const part of parts) {
      if (part.kind === "text") {
        for (const line of part.lines) {
          leftNum++;
          rightNum++;
          rows.push({
            type: "pair",
            leftNum,
            leftText: line,
            leftKind: "context",
            rightNum,
            rightText: line,
            rightKind: "context",
          });
        }
        continue;
      }

      rows.push({ type: "marker", conflictIndex: part.index });
      const count = Math.max(part.oursLines.length, part.theirsLines.length);
      for (let i = 0; i < count; i++) {
        const hasOurs = i < part.oursLines.length;
        const hasTheirs = i < part.theirsLines.length;
        if (hasOurs) leftNum++;
        if (hasTheirs) rightNum++;
        rows.push({
          type: "pair",
          conflictIndex: part.index,
          leftNum: hasOurs ? leftNum : undefined,
          leftText: hasOurs ? part.oursLines[i] : undefined,
          leftKind: hasOurs ? "ours" : "empty",
          rightNum: hasTheirs ? rightNum : undefined,
          rightText: hasTheirs ? part.theirsLines[i] : undefined,
          rightKind: hasTheirs ? "theirs" : "empty",
        });
      }
    }

    return rows;
  }

  function syncResolveScroll(source: "before" | "after") {
    if (isSyncingResolveScroll) return;
    const sourcePane = source === "before" ? resolveBeforePane : resolveAfterPane;
    const targetPane = source === "before" ? resolveAfterPane : resolveBeforePane;
    if (!sourcePane || !targetPane) return;

    isSyncingResolveScroll = true;
    targetPane.scrollTop = sourcePane.scrollTop;
    targetPane.scrollLeft = sourcePane.scrollLeft;
    requestAnimationFrame(() => {
      isSyncingResolveScroll = false;
    });
  }

  function legacyConflictParts(): any[] {
    return [];
  }

  function setConflictChoice(index: number, choice: ConflictChoice) {
    const next = [...conflictChoices];
    next[index] = choice;
    conflictChoices = next;
  }

  function setAllConflicts(choice: ConflictChoice) {
    conflictChoices = conflictParts.filter((p) => p.kind === "conflict").map(() => choice);
  }

  function enableManualEdit() {
    resolveContent = buildResolution(conflictParts, conflictChoices);
    manualMode = true;
  }

  function oursActive(choice: ConflictChoice | null): boolean {
    return choice === "ours" || choice === "both-ot" || choice === "both-to";
  }

  function theirsActive(choice: ConflictChoice | null): boolean {
    return choice === "theirs" || choice === "both-ot" || choice === "both-to";
  }

  function displayLine(line: string): string {
    return line.replace(/\r$/, "");
  }

  function formatBytes(size: number | null): string {
    if (size == null) return "missing";
    if (size < 1024) return `${size} B`;
    if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`;
    return `${(size / (1024 * 1024)).toFixed(1)} MB`;
  }

  function handleMarkResolved() {
    if (!conflict || !conflictTarget || !canMarkResolved) return;
    const prepared: PreparedResolution = conflict.binary
      ? { kind: "side", side: binarySide as "ours" | "theirs" }
      : { kind: "content", content: resolvedContent };
    onMarkResolved(conflictTarget, prepared);
  }
</script>

<div
  class="dialog-backdrop"
  role="presentation"
>
  <div
    class="dialog"
    style="grid-template-rows: auto minmax(0,1fr) auto;"
    role="dialog"
    aria-modal="true"
    aria-label="Resolve conflicts"
    tabindex="-1"
  >
    <header class="dialog-header">
      <div>
        <span class="eyebrow">Resolve</span>
        <h2 class="dialog-title">Conflicts</h2>
      </div>
      <div class="dialog-header-actions">
        {#if conflictTarget}
          <div class="tool-surface-choice" aria-label={isGerman ? "Konflikt bearbeiten mit" : "Edit conflict with"}>
            <span>{isGerman ? "Bearbeiten mit" : "Edit with"}</span>
            <button class="active" type="button" aria-pressed="true" title={isGerman ? "In Gitty bearbeiten" : "Edit in Gitty"}>
              <GitMerge size={13} aria-hidden="true" />Gitty
            </button>
            <button type="button" onclick={() => onExternalMerge(conflictTarget)} disabled={isBusy} title={isGerman ? `In ${mergeName} öffnen` : `Open in ${mergeName}`}>
              <ExternalLink size={13} aria-hidden="true" /><span>{mergeName}</span>
            </button>
          </div>
        {/if}
        <button class="dialog-close" type="button" onclick={onClose} title={isGerman ? "Schließen" : "Close"} aria-label={isGerman ? "Konflikte schließen" : "Close conflicts"}>
          <X size={18} aria-hidden="true" />
        </button>
      </div>
    </header>

    {#if conflictedFiles.length === 0}
      <div class="blank-state">All conflicts resolved. Continue the current operation when ready.</div>
    {:else}
      <div class="dialog-body">
        <aside class="dialog-files" aria-label="Conflicted files">
          {#each conflictedFiles as file (file.path)}
            <button
              class="dialog-file-row"
              class:active={conflictTarget === file.path}
              class:prepared={preparedResolutions[file.path] != null}
              type="button"
              onclick={() => onSelectFile(file.path)}
              disabled={isBusy}
              title={file.path}
            >
              <span class={`status-badge ${preparedResolutions[file.path] ? "added" : "conflicted"}`}>
                {preparedResolutions[file.path] ? "ready" : "conflicted"}
              </span>
              <strong>{file.path}</strong>
              {#if preparedResolutions[file.path]}
                <Check size={15} aria-hidden="true" />
              {/if}
            </button>
          {/each}
        </aside>

        <div class="resolve-editor" aria-label="Conflict editor">
          {#if !conflict}
            <div class="blank-state">Select a file to resolve.</div>
          {:else if conflict.binary}
            <div class="resolve-binary">
              <div class="resolve-binary-note">
                <AlertCircle size={16} aria-hidden="true" />
                <span>Binary file — pick which version to keep, then mark it resolved.</span>
              </div>
              <div class="resolve-binary-options">
                <button
                  class="resolve-binary-card ours"
                  class:active={binarySide === "ours"}
                  type="button"
                  onclick={() => { binarySide = "ours"; }}
                  disabled={isBusy || conflict.ours_size == null}
                >
                  <span class="resolve-side-label">Current (ours)</span>
                  <strong>{formatBytes(conflict.ours_size)}</strong>
                  <span class="resolve-binary-hint">{conflict.ours_size == null ? "Deleted on this side" : "Keep this version"}</span>
                </button>
                <button
                  class="resolve-binary-card theirs"
                  class:active={binarySide === "theirs"}
                  type="button"
                  onclick={() => { binarySide = "theirs"; }}
                  disabled={isBusy || conflict.theirs_size == null}
                >
                  <span class="resolve-side-label">Incoming (theirs)</span>
                  <strong>{formatBytes(conflict.theirs_size)}</strong>
                  <span class="resolve-binary-hint">{conflict.theirs_size == null ? "Deleted on this side" : "Keep this version"}</span>
                </button>
              </div>
            </div>

            <div class="resolve-actions">
              <span class="resolve-path" title={conflictTarget}>{conflictTarget}</span>
              {#if currentPrepared}
                <span class="prepared-tag"><Check size={14} aria-hidden="true" /> Prepared</span>
              {/if}
              <button type="button" onclick={handleMarkResolved} disabled={!canMarkResolved}>
                <Check size={16} aria-hidden="true" />
                {currentPrepared ? "Update decision" : "Mark as resolved"}
              </button>
            </div>
          {:else}
            <div class="resolve-toolbar">
              {#if manualMode}
                <button type="button" onclick={() => { manualMode = false; }} disabled={isBusy}>Back to guided</button>
              {:else}
                <span class="resolve-toolbar-label">Apply to all:</span>
                <button type="button" onclick={() => setAllConflicts("ours")} disabled={isBusy}>Current</button>
                <button type="button" onclick={() => setAllConflicts("theirs")} disabled={isBusy}>Incoming</button>
                <button type="button" onclick={() => setAllConflicts("both-ot")} disabled={isBusy}>Both</button>
                <button type="button" onclick={enableManualEdit} disabled={isBusy}>Edit manually</button>
              {/if}
              <span class="resolve-status">
                {#if unresolvedCount > 0}
                  <AlertCircle size={14} aria-hidden="true" />
                  {unresolvedCount} of {conflictRegionCount} unresolved
                {:else if resolveHasMarkers}
                  <AlertCircle size={14} aria-hidden="true" />
                  Conflict markers still present
                {:else}
                  <Check size={14} aria-hidden="true" />
                  {conflictRegionCount} {conflictRegionCount === 1 ? "conflict" : "conflicts"} resolved
                {/if}
              </span>
            </div>

            {#if manualMode}
              <textarea
                class="resolve-textarea"
                bind:value={resolveContent}
                spellcheck="false"
                disabled={isBusy}
              ></textarea>
            {:else}
              <div class="resolve-structured">
                {#if conflictRegions.length > 0}
                  <div class="resolve-conflict-controls" aria-label="Conflict decisions">
                    {#each conflictRegions as part (part.index)}
                      <div class="resolve-conflict-bar" class:unresolved={conflictChoices[part.index] == null}>
                        <span class="resolve-conflict-label">Conflict {part.index + 1}</span>
                        <div class="resolve-choice-buttons">
                          <button type="button" class:active={conflictChoices[part.index] === "ours"} onclick={() => setConflictChoice(part.index, "ours")} disabled={isBusy}>Current</button>
                          <button type="button" class:active={conflictChoices[part.index] === "theirs"} onclick={() => setConflictChoice(part.index, "theirs")} disabled={isBusy}>Incoming</button>
                          <button type="button" class:active={conflictChoices[part.index] === "both-ot"} onclick={() => setConflictChoice(part.index, "both-ot")} disabled={isBusy} title="Both - current first">Both C+I</button>
                          <button type="button" class:active={conflictChoices[part.index] === "both-to"} onclick={() => setConflictChoice(part.index, "both-to")} disabled={isBusy} title="Both - incoming first">Both I+C</button>
                        </div>
                      </div>
                    {/each}
                  </div>
                {/if}

                <div class="resolve-split" role="table" aria-label="Full conflict comparison">
                  <div
                    class="resolve-pane"
                    bind:this={resolveBeforePane}
                    aria-label="Current file content"
                    onscroll={() => syncResolveScroll("before")}
                  >
                    <div class="resolve-pane-grid">
                      {#each resolveSplitRows as row, i (`left-${i}`)}
                        {#if row.type === "marker"}
                          <div class="resolve-split-marker" class:unresolved={conflictChoices[row.conflictIndex] == null}>Conflict {row.conflictIndex + 1}</div>
                        {:else}
                          <div class="resolve-num" class:ours={row.leftKind === "ours"} class:empty={row.leftKind === "empty"}>{row.leftNum ?? ""}</div>
                          <div
                            class="resolve-cell"
                            class:ours={row.leftKind === "ours"}
                            class:empty={row.leftKind === "empty"}
                            class:dimmed={row.conflictIndex != null && !oursActive(conflictChoices[row.conflictIndex])}
                          >{displayLine(row.leftText ?? "") || " "}</div>
                        {/if}
                      {/each}
                    </div>
                  </div>
                  <div
                    class="resolve-pane"
                    bind:this={resolveAfterPane}
                    aria-label="Incoming file content"
                    onscroll={() => syncResolveScroll("after")}
                  >
                    <div class="resolve-pane-grid">
                      {#each resolveSplitRows as row, i (`right-${i}`)}
                        {#if row.type === "marker"}
                          <div class="resolve-split-marker" class:unresolved={conflictChoices[row.conflictIndex] == null}>Conflict {row.conflictIndex + 1}</div>
                        {:else}
                          <div class="resolve-num" class:theirs={row.rightKind === "theirs"} class:empty={row.rightKind === "empty"}>{row.rightNum ?? ""}</div>
                          <div
                            class="resolve-cell"
                            class:theirs={row.rightKind === "theirs"}
                            class:empty={row.rightKind === "empty"}
                            class:dimmed={row.conflictIndex != null && !theirsActive(conflictChoices[row.conflictIndex])}
                          >{displayLine(row.rightText ?? "") || " "}</div>
                        {/if}
                      {/each}
                    </div>
                  </div>
                </div>

                {#if false}
                {#each legacyConflictParts() as part, partIndex (partIndex)}
                  {#if part.kind === "text"}
                    {#if part.lines.length > 0}
                      <pre class="resolve-context">{#each part.lines as line}<span class="resolve-line context">{displayLine(line) || " "}</span>{/each}</pre>
                    {/if}
                  {:else}
                    <div class="resolve-conflict" class:unresolved={conflictChoices[part.index] == null}>
                      <div class="resolve-conflict-bar">
                        <span class="resolve-conflict-label">Conflict {part.index + 1}</span>
                        <div class="resolve-choice-buttons">
                          <button type="button" class:active={conflictChoices[part.index] === "ours"} onclick={() => setConflictChoice(part.index, "ours")} disabled={isBusy}>Current</button>
                          <button type="button" class:active={conflictChoices[part.index] === "theirs"} onclick={() => setConflictChoice(part.index, "theirs")} disabled={isBusy}>Incoming</button>
                          <button type="button" class:active={conflictChoices[part.index] === "both-ot"} onclick={() => setConflictChoice(part.index, "both-ot")} disabled={isBusy} title="Both — current first">Both C+I</button>
                          <button type="button" class:active={conflictChoices[part.index] === "both-to"} onclick={() => setConflictChoice(part.index, "both-to")} disabled={isBusy} title="Both — incoming first">Both I+C</button>
                        </div>
                      </div>
                      <div class="resolve-side ours" class:dimmed={!oursActive(conflictChoices[part.index])}>
                        <span class="resolve-side-label">Current (ours)</span>
                        <pre class="resolve-lines">{#each part.oursLines as line}<span class="resolve-line ours">{displayLine(line) || " "}</span>{/each}</pre>
                      </div>
                      <div class="resolve-side theirs" class:dimmed={!theirsActive(conflictChoices[part.index])}>
                        <span class="resolve-side-label">Incoming (theirs)</span>
                        <pre class="resolve-lines">{#each part.theirsLines as line}<span class="resolve-line theirs">{displayLine(line) || " "}</span>{/each}</pre>
                      </div>
                    </div>
                  {/if}
                {/each}
                {/if}
              </div>
            {/if}

            <div class="resolve-actions">
              <span class="resolve-path" title={conflictTarget}>{conflictTarget}</span>
              {#if currentPrepared}
                <span class="prepared-tag"><Check size={14} aria-hidden="true" /> Prepared</span>
              {/if}
              <button type="button" onclick={handleMarkResolved} disabled={!canMarkResolved}>
                <Check size={16} aria-hidden="true" />
                {currentPrepared ? "Update decision" : "Mark as resolved"}
              </button>
            </div>
          {/if}
        </div>
      </div>

      <footer class="dialog-footer">
        <span class="dialog-footer-info">{preparedCount} of {conflictedFiles.length} prepared</span>
        <button class="btn-primary" type="button" onclick={onApply} disabled={isBusy || preparedCount === 0}>
          {#if operation.startsWith("Resolving")}
            <LoaderCircle class="spin" size={16} aria-hidden="true" />
          {:else}
            <Check size={16} aria-hidden="true" />
          {/if}
          Apply {preparedCount} resolved
        </button>
      </footer>
    {/if}
  </div>
</div>
