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

'use client';

import * as React from 'react';
import type { Language, PrismTheme } from 'prism-react-renderer';
import { Virtuoso } from 'react-virtuoso';
import { cn } from '@djangocfg/ui-core/lib';
import { DIFF_TONE_CLASSES, type DiffLine } from '../types';
import { DiffLineContent } from './DiffLineContent';

interface SplitViewProps {
  lines: DiffLine[];
  numWidth: number;
  language: Language | null;
  prismTheme: PrismTheme;
}

// See UnifiedView for context — same threshold + viewport size keeps both
// layouts behaviorally aligned.
const VIRTUALIZE_THRESHOLD = 100;
const VIRTUAL_VIEWPORT_HEIGHT = 480;

/**
 * Align consecutive `removed` / `added` runs into a pair of columns.
 * Pure-add rows leave the left column empty; pure-remove rows leave the
 * right column empty. Context rows duplicate on both sides.
 *
 * The pairing is the same shape jalcoui ships — we keep it so downstream
 * consumers' visual baseline doesn't move.
 */
function pairLines(lines: DiffLine[]): Array<{
  left: DiffLine | null;
  right: DiffLine | null;
}> {
  const pairs: Array<{ left: DiffLine | null; right: DiffLine | null }> = [];

  let i = 0;
  while (i < lines.length) {
    const line = lines[i];
    if (line.type === 'context') {
      pairs.push({ left: line, right: line });
      i++;
      continue;
    }
    if (line.type === 'removed') {
      const removed: DiffLine[] = [];
      while (i < lines.length && lines[i].type === 'removed') {
        removed.push(lines[i]);
        i++;
      }
      const added: DiffLine[] = [];
      while (i < lines.length && lines[i].type === 'added') {
        added.push(lines[i]);
        i++;
      }
      const maxLen = Math.max(removed.length, added.length);
      for (let j = 0; j < maxLen; j++) {
        pairs.push({
          left: j < removed.length ? removed[j] : null,
          right: j < added.length ? added[j] : null,
        });
      }
      continue;
    }
    // line.type === 'added' with no preceding removed run
    pairs.push({ left: null, right: line });
    i++;
  }

  return pairs;
}

function sidePrefix(side: 'old' | 'new', type: DiffLine['type'] | undefined) {
  if (!type || type === 'context') return ' ';
  if (side === 'old' && type === 'removed') return '-';
  if (side === 'new' && type === 'added') return '+';
  return ' ';
}

interface HalfProps {
  line: DiffLine | null;
  side: 'old' | 'new';
  numWidth: number;
  language: Language | null;
  prismTheme: PrismTheme;
}

function Half({ line, side, numWidth, language, prismTheme }: HalfProps) {
  const tone = line ? DIFF_TONE_CLASSES[line.type] : DIFF_TONE_CLASSES.context;
  const gutterCh = `calc(${numWidth}ch + 1rem)`;
  return (
    <div
      className={cn('grid items-start min-w-0', tone.row)}
      style={{
        gridTemplateColumns: `${gutterCh} 1.5rem minmax(0, 1fr)`,
      }}
    >
      <div
        className={cn(
          'select-none px-2 text-right tabular-nums whitespace-nowrap',
          tone.num,
        )}
      >
        {(side === 'old' ? line?.oldNumber : line?.newNumber) ?? ''}
      </div>
      <div
        className={cn(
          'select-none text-center whitespace-nowrap',
          tone.num,
        )}
      >
        {sidePrefix(side, line?.type)}
      </div>
      <div
        className={cn(
          'whitespace-pre px-3 overflow-x-auto',
          line ? tone.text : 'text-muted-foreground/40',
        )}
      >
        {line ? (
          <DiffLineContent
            content={line.content}
            language={language}
            prismTheme={prismTheme}
          />
        ) : (
          ' '
        )}
      </div>
    </div>
  );
}

interface PairRowProps {
  pair: { left: DiffLine | null; right: DiffLine | null };
  numWidth: number;
  language: Language | null;
  prismTheme: PrismTheme;
}

function PairRow({ pair, numWidth, language, prismTheme }: PairRowProps) {
  return (
    <div className="grid grid-cols-2 font-mono text-[13px] leading-relaxed">
      <div className="border-r border-border/40 min-w-0">
        <Half
          line={pair.left}
          side="old"
          numWidth={numWidth}
          language={language}
          prismTheme={prismTheme}
        />
      </div>
      <div className="min-w-0">
        <Half
          line={pair.right}
          side="new"
          numWidth={numWidth}
          language={language}
          prismTheme={prismTheme}
        />
      </div>
    </div>
  );
}

/**
 * Side-by-side diff. Left half = old, right half = new. Each row is a
 * single grid wrapping two `<Half>` halves so a `<Virtuoso>` list can
 * virtualize the whole stack with naturally synchronized scrolling.
 *
 * Both halves receive the SAME globally-computed `numWidth` so the two
 * gutter columns are pixel-identical regardless of which side has the
 * longer line numbers.
 */
export function SplitView({
  lines,
  numWidth,
  language,
  prismTheme,
}: SplitViewProps) {
  const pairs = React.useMemo(() => pairLines(lines), [lines]);

  if (pairs.length > VIRTUALIZE_THRESHOLD) {
    return (
      <div data-slot="diff-viewer-split">
        <Virtuoso
          data={pairs}
          style={{ height: VIRTUAL_VIEWPORT_HEIGHT }}
          // See UnifiedView for why `initialItemCount` is required —
          // without it, embedded iframes start with a zero-height list.
          initialItemCount={Math.min(pairs.length, 30)}
          itemContent={(_, pair) => (
            <PairRow
              pair={pair}
              numWidth={numWidth}
              language={language}
              prismTheme={prismTheme}
            />
          )}
          increaseViewportBy={{ top: 200, bottom: 400 }}
        />
      </div>
    );
  }

  return (
    <div data-slot="diff-viewer-split">
      {pairs.map((pair, idx) => (
        <PairRow
          key={idx}
          pair={pair}
          numWidth={numWidth}
          language={language}
          prismTheme={prismTheme}
        />
      ))}
    </div>
  );
}
