// Line-based diff (git style) used by the version history diff viewer. A simple
// Longest-Common-Subsequence over lines yields a sequence of equal/added/removed
// rows that can be rendered as a unified diff or split side-by-side.

export type DiffOp = 'equal' | 'add' | 'del';

export interface DiffRow {
  op: DiffOp;
  // Line content. For 'equal' both sides are identical (text).
  text: string;
  // 1-based line numbers in the left/right document; null when absent on a side.
  leftNo: number | null;
  rightNo: number | null;
}

function splitLines(s: string): string[] {
  // Drop a single trailing newline so a file ending in "\n" doesn't yield a
  // spurious empty final row.
  const t = s.endsWith('\n') ? s.slice(0, -1) : s;
  return t.length === 0 ? [] : t.split('\n');
}

// diffLines computes the LCS table and walks it back into ordered rows.
export function diffLines(leftText: string, rightText: string): DiffRow[] {
  const a = splitLines(leftText);
  const b = splitLines(rightText);
  const n = a.length;
  const m = b.length;

  // lcs[i][j] = length of LCS of a[i:] and b[j:].
  const lcs: number[][] = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
  for (let i = n - 1; i >= 0; i--) {
    for (let j = m - 1; j >= 0; j--) {
      lcs[i][j] = a[i] === b[j] ? lcs[i + 1][j + 1] + 1 : Math.max(lcs[i + 1][j], lcs[i][j + 1]);
    }
  }

  const rows: DiffRow[] = [];
  let i = 0;
  let j = 0;
  let la = 1;
  let lb = 1;
  while (i < n && j < m) {
    if (a[i] === b[j]) {
      rows.push({ op: 'equal', text: a[i], leftNo: la++, rightNo: lb++ });
      i++; j++;
    } else if (lcs[i + 1][j] >= lcs[i][j + 1]) {
      rows.push({ op: 'del', text: a[i], leftNo: la++, rightNo: null });
      i++;
    } else {
      rows.push({ op: 'add', text: b[j], leftNo: null, rightNo: lb++ });
      j++;
    }
  }
  while (i < n) rows.push({ op: 'del', text: a[i++], leftNo: la++, rightNo: null });
  while (j < m) rows.push({ op: 'add', text: b[j++], leftNo: null, rightNo: lb++ });
  return rows;
}

export interface DiffStats { added: number; removed: number; }

export function diffStats(rows: DiffRow[]): DiffStats {
  let added = 0;
  let removed = 0;
  for (const r of rows) {
    if (r.op === 'add') added++;
    else if (r.op === 'del') removed++;
  }
  return { added, removed };
}

// sideBySide pairs rows into aligned [left, right] columns: a del with the next
// add becomes one changed row; otherwise each row occupies one side.
export interface SideRow {
  left: DiffRow | null;
  right: DiffRow | null;
}

export function sideBySide(rows: DiffRow[]): SideRow[] {
  const out: SideRow[] = [];
  for (let k = 0; k < rows.length; k++) {
    const r = rows[k];
    if (r.op === 'equal') {
      out.push({ left: r, right: r });
    } else if (r.op === 'del') {
      // Pair consecutive del/add as a single changed line when possible.
      const next = rows[k + 1];
      if (next && next.op === 'add') {
        out.push({ left: r, right: next });
        k++;
      } else {
        out.push({ left: r, right: null });
      }
    } else {
      out.push({ left: null, right: r });
    }
  }
  return out;
}
