import { type CSSProperties } from 'react';

export interface CodeDiffProps {
  /** Original code. */
  before: string;
  /** Modified code. */
  after: string;
  /** Display mode. Default `'split'` (two columns). */
  view?: 'split' | 'unified';
  /** Optional language hint (no syntax highlighting yet, kept for parity). */
  language?: string;
  /** Filename or label rendered in the header (e.g. `useChat.ts`). */
  filename?: string;
  className?: string;
  style?: CSSProperties;
}

type DiffOp = { kind: 'eq' | 'add' | 'del'; text: string };

/**
 * `<CodeDiff>` — pure-React diff renderer with `split` and `unified`
 * views. Internal diff is a small line-LCS implementation (no runtime
 * dependency), good enough for short hunks shown in chat surfaces and
 * tracker PR cards.
 *
 * Wave 9.E.2. Hosts that need byte-perfect Myers diff or syntax
 * highlighting should feed `<CodeDiff>` a pre-computed pair where the
 * server has already done the heavy lift.
 */
export function CodeDiff({
  before,
  after,
  view = 'split',
  language,
  filename,
  className,
  style,
}: CodeDiffProps) {
  const ops = diffLines(before, after);
  return (
    <div
      data-testid="bl-code-diff"
      data-view={view}
      className={className}
      style={{
        border: '1px solid var(--bl-border, rgba(0,0,0,0.12))',
        borderRadius: 12,
        overflow: 'hidden',
        ...style,
      }}
    >
      <header
        style={{
          display: 'flex',
          alignItems: 'center',
          justifyContent: 'space-between',
          padding: '6px 12px',
          background: 'var(--bl-surface-muted, #f6f6f6)',
          borderBottom: '1px solid var(--bl-border-subtle, rgba(0,0,0,0.06))',
          fontSize: 12,
          fontFamily:
            'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
        }}
      >
        <span style={{ fontWeight: 600 }}>{filename ?? 'diff'}</span>
        <span style={{ opacity: 0.6 }}>
          {language ? `${language} · ` : ''}
          {view}
        </span>
      </header>
      {view === 'split' ? (
        <SplitView ops={ops} />
      ) : (
        <UnifiedView ops={ops} />
      )}
    </div>
  );
}

function SplitView({ ops }: { ops: DiffOp[] }) {
  return (
    <div
      style={{
        display: 'grid',
        gridTemplateColumns: '1fr 1fr',
        fontFamily:
          'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
        fontSize: 12,
        lineHeight: 1.55,
      }}
    >
      <div data-testid="bl-code-diff-left" style={paneStyle('left')}>
        {ops.map((op, i) => {
          if (op.kind === 'add') return null;
          return (
            <Row key={`l${i}`} kind={op.kind} side="left" text={op.text} />
          );
        })}
      </div>
      <div data-testid="bl-code-diff-right" style={paneStyle('right')}>
        {ops.map((op, i) => {
          if (op.kind === 'del') return null;
          return (
            <Row key={`r${i}`} kind={op.kind} side="right" text={op.text} />
          );
        })}
      </div>
    </div>
  );
}

function UnifiedView({ ops }: { ops: DiffOp[] }) {
  return (
    <div
      data-testid="bl-code-diff-unified"
      style={{
        fontFamily:
          'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
        fontSize: 12,
        lineHeight: 1.55,
      }}
    >
      {ops.map((op, i) => (
        <Row key={`u${i}`} kind={op.kind} side="unified" text={op.text} />
      ))}
    </div>
  );
}

function Row({
  kind,
  text,
  side,
}: {
  kind: 'eq' | 'add' | 'del';
  side: 'left' | 'right' | 'unified';
  text: string;
}) {
  const bg =
    kind === 'add'
      ? 'color-mix(in srgb, var(--bl-success, #10b981) 14%, transparent)'
      : kind === 'del'
        ? 'color-mix(in srgb, var(--bl-danger, #ef4444) 14%, transparent)'
        : 'transparent';
  const gutter = kind === 'add' ? '+' : kind === 'del' ? '−' : ' ';
  return (
    <div
      data-testid="bl-code-diff-row"
      data-kind={kind}
      data-side={side}
      style={{
        display: 'grid',
        gridTemplateColumns: '20px 1fr',
        background: bg,
        padding: '0 8px',
        whiteSpace: 'pre',
      }}
    >
      <span
        aria-hidden="true"
        style={{ color: 'var(--bl-text-tertiary, #888)', userSelect: 'none' }}
      >
        {gutter}
      </span>
      <span>{text}</span>
    </div>
  );
}

function paneStyle(side: 'left' | 'right'): CSSProperties {
  return {
    background: 'var(--bl-surface, #fff)',
    borderRight:
      side === 'left'
        ? '1px solid var(--bl-border-subtle, rgba(0,0,0,0.06))'
        : undefined,
    overflowX: 'auto',
  };
}

/**
 * Tiny line-level LCS — O(n·m) memory, fine for hunks <2,000 lines.
 * Hosts that diff full files should pre-compute with a real Myers
 * implementation and split into <CodeDiff> per hunk.
 */
function diffLines(before: string, after: string): DiffOp[] {
  const a = before.split('\n');
  const b = after.split('\n');
  const n = a.length;
  const m = b.length;
  // Build LCS length table.
  const dp: number[][] = Array.from({ length: n + 1 }, () =>
    new Array<number>(m + 1).fill(0),
  );
  for (let i = n - 1; i >= 0; i--) {
    for (let j = m - 1; j >= 0; j--) {
      if (a[i] === b[j]) {
        dp[i]![j] = (dp[i + 1]?.[j + 1] ?? 0) + 1;
      } else {
        dp[i]![j] = Math.max(dp[i + 1]?.[j] ?? 0, dp[i]?.[j + 1] ?? 0);
      }
    }
  }
  // Walk back to recover ops.
  const ops: DiffOp[] = [];
  let i = 0;
  let j = 0;
  while (i < n && j < m) {
    if (a[i] === b[j]) {
      ops.push({ kind: 'eq', text: a[i] ?? '' });
      i++;
      j++;
    } else if ((dp[i + 1]?.[j] ?? 0) >= (dp[i]?.[j + 1] ?? 0)) {
      ops.push({ kind: 'del', text: a[i] ?? '' });
      i++;
    } else {
      ops.push({ kind: 'add', text: b[j] ?? '' });
      j++;
    }
  }
  while (i < n) ops.push({ kind: 'del', text: a[i++] ?? '' });
  while (j < m) ops.push({ kind: 'add', text: b[j++] ?? '' });
  return ops;
}
