import { useMemo, useRef, useEffect, useState } from 'react';
import { codeToHtml } from 'shiki';
import type { GitDiffFile } from '@/api/types';
import { parseDiff, buildSplitRows, reconstructNewContent, type SplitRow } from '@/utils/diff-layout';
import { inferLanguage } from '@/lib/attachments';
import { cn } from '@/lib/utils';

interface DiffSplitViewProps {
  file: GitDiffFile;
  wrapLines?: boolean;
}

/** Side-by-side split diff renderer. Left = deletions, right = additions. */
export function DiffSplitView({ file, wrapLines = false }: DiffSplitViewProps) {
  // ── Edge cases (rendered before hooks) ──────────────────────────────────
  if (file.is_binary) {
    return <p className="text-xs text-muted-foreground italic px-2 py-1">Binary file</p>;
  }
  if (file.is_too_large) {
    return <p className="text-xs text-muted-foreground italic px-2 py-1">Diff too large to display</p>;
  }
  if (file.change_type === 'untracked' && !file.diff_body) {
    return <p className="text-xs text-muted-foreground italic px-2 py-1">Untracked file</p>;
  }
  if (!file.diff_body) {
    return <p className="text-xs text-muted-foreground italic px-2 py-1">No diff content</p>;
  }

  return <DiffSplitViewInner file={file} wrapLines={wrapLines} />;
}

/**
 * Inner component — assumes file.diff_body is non-null.
 * Separated so the early-return edge cases above don't violate rules of hooks.
 */
function DiffSplitViewInner({ file, wrapLines }: { file: GitDiffFile; wrapLines: boolean }) {
  // ── Parse diff ───────────────────────────────────────────────────────────
  const parsed = useMemo(() => parseDiff(file.diff_body!), [file.diff_body]);
  const parsedFile = parsed[0];

  const rows = useMemo(() => {
    if (!parsedFile) return [] as SplitRow[];
    return buildSplitRows(parsedFile);
  }, [parsedFile]);

  const newContent = useMemo(() => {
    if (!parsedFile) return '';
    return reconstructNewContent(parsedFile.hunks);
  }, [parsedFile]);

  // ── Syntax highlighting ──────────────────────────────────────────────────
  const [highlightedLines, setHighlightedLines] = useState<string[] | null>(null);
  const [highlighting, setHighlighting] = useState(false);
  const highlightKeyRef = useRef<string | null>(null);

  useEffect(() => {
    if (!newContent) return;
    if (highlightKeyRef.current === newContent) return;
    highlightKeyRef.current = newContent;

    let cancelled = false;
    setHighlighting(true);
    setHighlightedLines(null);

    const lang = inferLanguage(file.path) ?? 'plaintext';

    void codeToHtml(newContent, { lang, theme: 'github-dark' })
      .then((html) => {
        if (cancelled) return;
        const container = document.createElement('div');
        // eslint-disable-next-line no-unsanitized/property
        container.innerHTML = html;
        const codeEl = container.querySelector('code');
        if (codeEl) {
          const lineSpans = codeEl.querySelectorAll('.line');
          setHighlightedLines(Array.from(lineSpans, (span) => span.innerHTML));
        } else {
          setHighlightedLines(null);
        }
      })
      .catch(() => {
        if (!cancelled) setHighlightedLines(null);
      })
      .finally(() => {
        if (!cancelled) setHighlighting(false);
      });

    return () => { cancelled = true; };
  }, [newContent, file.path]);

  // ── Build new-line-number → highlighted-HTML map ───────────────────────
  // Walk the hunks counting only add/context lines (which form the new file)
  // and map each 1-based new-line-number to its highlighted HTML string.
  const newLineHtmlMap = useMemo(() => {
    if (!highlightedLines || !parsedFile) return new Map<number, string>();
    const map = new Map<number, string>();
    let idx = 0;
    for (const hunk of parsedFile.hunks) {
      let newLineNo = hunk.newStart;
      for (const line of hunk.lines) {
        if (line.type === 'header') continue;
        if (line.type === 'add' || line.type === 'context') {
          if (idx < highlightedLines.length) {
            map.set(newLineNo, highlightedLines[idx]!);
          }
          idx++;
          newLineNo++;
        }
      }
    }
    return map;
  }, [highlightedLines, parsedFile]);

  // ── Render ───────────────────────────────────────────────────────────────
  return (
    <div className={cn('text-[11px] font-mono overflow-x-auto', wrapLines && 'break-all')}>
      {highlighting && (
        <p className="text-xs text-muted-foreground px-2 py-1">Highlighting…</p>
      )}
      <table className="w-full border-collapse">
        <colgroup>
          <col className="w-[40px]" />
          <col />
          <col className="w-px" />
          <col className="w-[40px]" />
          <col />
        </colgroup>
        <tbody>
          {rows.map((row, idx) => {
            if (row.kind === 'header') {
              return (
                <tr key={`h-${idx}`} className="bg-muted/30">
                  <td
                    colSpan={5}
                    className="text-muted-foreground text-[11px] px-2 py-0.5 select-none"
                  >
                    {row.content}
                  </td>
                </tr>
              );
            }

            const left = row.left;
            const right = row.right;

            const leftBg = left?.type === 'remove' ? 'bg-red-950/30' : '';
            const rightBg = right?.type === 'add' ? 'bg-green-950/30' : '';

            const leftHtml = left?.lineNumber != null ? newLineHtmlMap.get(left.lineNumber) : undefined;
            const rightHtml = right?.lineNumber != null ? newLineHtmlMap.get(right.lineNumber) : undefined;

            return (
              <tr key={`p-${idx}`} className="hover:bg-muted/10">
                <td className={cn(leftBg, 'border-r border-border/20 align-top')}>
                  <span className="text-muted-foreground text-right pr-1 select-none text-[11px] block">
                    {left?.lineNumber ?? ''}
                  </span>
                </td>
                <td className={cn(leftBg, 'align-top')}>
                  <div
                    className={cn(
                      'pl-2 text-[11px]',
                      wrapLines ? 'whitespace-pre-wrap break-all' : 'whitespace-pre',
                    )}
                  >
                    {left ? (
                      leftHtml ? (
                        // eslint-disable-next-line no-unsanitized/property
                        <span dangerouslySetInnerHTML={{ __html: leftHtml }} />
                      ) : (
                        <span>{left.content}</span>
                      )
                    ) : null}
                  </div>
                </td>
                <td className="border-l border-border/30 w-px p-0" />
                <td className={cn(rightBg, 'border-r border-border/20 align-top')}>
                  <span className="text-muted-foreground text-right pr-1 select-none text-[11px] block">
                    {right?.lineNumber ?? ''}
                  </span>
                </td>
                <td className={cn(rightBg, 'align-top')}>
                  <div
                    className={cn(
                      'pl-2 text-[11px]',
                      wrapLines ? 'whitespace-pre-wrap break-all' : 'whitespace-pre',
                    )}
                  >
                    {right ? (
                      rightHtml ? (
                        // eslint-disable-next-line no-unsanitized/property
                        <span dangerouslySetInnerHTML={{ __html: rightHtml }} />
                      ) : (
                        <span>{right.content}</span>
                      )
                    ) : null}
                  </div>
                </td>
              </tr>
            );
          })}
        </tbody>
      </table>
    </div>
  );
}