// Adapted from jalcoui (MIT) — github.com/jal-co/ui

'use client';

import { useMemo } from 'react';
import type { Commit, Edge, GraphRow } from '../types';
import { pickLaneColor } from './useLaneColors';

/**
 * Topological layout pass for the commit graph.
 *
 * Walks the commit list in order (newest first) and tracks active rails —
 * each slot holds the hash of the parent the rail is "waiting for". The
 * algorithm produces:
 *
 *  - The rail a commit's dot occupies.
 *  - The rails state after the row (for the next iteration).
 *  - Edges to draw on this row (pass-throughs, merge curves, fork curves).
 *
 * Rail colors are looked up at layout time so the rendered SVG can use raw
 * hex `stroke=` values — Canvas2D/SVG won't parse Tailwind classes.
 */
function computeLayout(commits: Commit[], laneColors: string[]): GraphRow[] {
  const rows: GraphRow[] = [];
  const rails: (string | null)[] = [];
  const color = (rail: number) => pickLaneColor(laneColors, rail);

  for (const commit of commits) {
    const hash = commit.hash;

    // Find which rail this commit occupies (if any rail is waiting for it).
    let commitRail = rails.indexOf(hash);

    if (commitRail === -1) {
      // New branch — find first empty slot or append.
      const emptySlot = rails.indexOf(null);
      if (emptySlot !== -1) {
        commitRail = emptySlot;
        rails[commitRail] = hash;
      } else {
        commitRail = rails.length;
        rails.push(hash);
      }
    }

    const commitColor = color(commitRail);
    const edges: Edge[] = [];

    // Pass-through rails (everything not on the commit rail).
    for (let r = 0; r < rails.length; r++) {
      if (r !== commitRail && rails[r] !== null) {
        edges.push({ fromRail: r, toRail: r, color: color(r), type: 'straight' });
      }
    }

    // Clear this rail — the commit has been rendered.
    rails[commitRail] = null;

    // First parent continues on the commit's rail unless already expected
    // elsewhere (merge of two existing branches).
    const parents = commit.parents;
    if (parents.length >= 1) {
      const firstParent = parents[0];
      const existingRail = rails.indexOf(firstParent);
      if (existingRail !== -1) {
        edges.push({
          fromRail: commitRail,
          toRail: existingRail,
          color: commitColor,
          type: 'merge-in',
        });
      } else {
        rails[commitRail] = firstParent;
        edges.push({
          fromRail: commitRail,
          toRail: commitRail,
          color: commitColor,
          type: 'straight',
        });
      }
    }

    // Second+ parents (merge sources / forks).
    for (let p = 1; p < parents.length; p++) {
      const parentHash = parents[p];
      const existingRail = rails.indexOf(parentHash);
      if (existingRail !== -1) {
        edges.push({
          fromRail: existingRail,
          toRail: commitRail,
          color: color(existingRail),
          type: 'merge-in',
        });
      } else {
        const emptySlot = rails.indexOf(null);
        const newRail = emptySlot !== -1 ? emptySlot : rails.length;
        if (newRail >= rails.length) rails.push(null);
        rails[newRail] = parentHash;
        edges.push({
          fromRail: commitRail,
          toRail: newRail,
          color: color(newRail),
          type: 'fork-out',
        });
      }
    }

    // Trim trailing nulls so `maxRails` stays accurate.
    while (rails.length > 0 && rails[rails.length - 1] === null) {
      rails.pop();
    }

    rows.push({
      commit,
      rail: commitRail,
      rails: [...rails],
      edges,
    });
  }

  return rows;
}

/**
 * Auto-infer a linear topology when no commit declares parents — the
 * jalcoui examples ship a flat list and rely on the component to chain
 * them.
 */
function ensureTopology(commits: Commit[]): Commit[] {
  const hasTopology = commits.some((c) => c.parents && c.parents.length > 0);
  if (hasTopology) return commits;
  return commits.map((c, i) => ({
    ...c,
    parents: i < commits.length - 1 ? [commits[i + 1].hash] : [],
  }));
}

export interface GraphLayout {
  rows: GraphRow[];
  maxRails: number;
}

/**
 * React-friendly wrapper around {@link computeLayout}. Memoizes on
 * `commits` + `laneColors` identity so theme switches re-color but do not
 * reshuffle topology.
 */
export function useGraphLayout(
  commits: Commit[],
  laneColors: string[],
): GraphLayout {
  return useMemo(() => {
    const resolved = ensureTopology(commits);
    if (resolved.length === 0) {
      return { rows: [], maxRails: 0 };
    }
    const rows = computeLayout(resolved, laneColors);
    const maxRails = Math.max(
      ...rows.map((r) =>
        Math.max(
          r.rail + 1,
          r.rails.length,
          ...r.edges.map((e) => Math.max(e.fromRail, e.toRail) + 1),
        ),
      ),
    );
    return { rows, maxRails };
  }, [commits, laneColors]);
}
