import { useMemo, useState } from "react";
import { Box, Button, Group, Text } from "@mantine/core";
import { ChevronDown, ChevronRight } from "lucide-react";

type Op = "same" | "added" | "removed";
interface Token {
  op: Op;
  text: string;
}

/** Split on whitespace but KEEP it, so rebuilt text preserves its spacing. */
function tokenize(text: string): string[] {
  return text.split(/(\s+)/).filter((t) => t !== "");
}

/**
 * Word-level diff via the classic LCS table.
 *
 * ponytail: O(n·m) time and memory over word counts. Contract articles are
 * paragraphs (hundreds of words), so this is microseconds; the guard below
 * bails to a whole-block replace if an article ever gets pathological. Swap in
 * a real diff library only if that guard starts firing.
 */
const MAX_TOKENS = 1200;

export function diffWords(before: string, after: string): Token[] {
  const a = tokenize(before);
  const b = tokenize(after);

  if (a.length > MAX_TOKENS || b.length > MAX_TOKENS) {
    return [
      { op: "removed", text: before },
      { op: "added", text: after },
    ];
  }

  // lcs[i][j] = length of the longest common subsequence of a[i:] and b[j:].
  const lcs: number[][] = Array.from({ length: a.length + 1 }, () =>
    new Array<number>(b.length + 1).fill(0),
  );
  for (let i = a.length - 1; i >= 0; i--) {
    for (let j = b.length - 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 tokens: Token[] = [];
  // Merge runs of the same op so the output is spans, not one node per word.
  const push = (op: Op, text: string) => {
    const last = tokens[tokens.length - 1];
    if (last && last.op === op) last.text += text;
    else tokens.push({ op, text });
  };

  let i = 0;
  let j = 0;
  while (i < a.length && j < b.length) {
    if (a[i] === b[j]) {
      push("same", a[i]);
      i++;
      j++;
    } else if (lcs[i + 1][j] >= lcs[i][j + 1]) {
      push("removed", a[i]);
      i++;
    } else {
      push("added", b[j]);
      j++;
    }
  }
  while (i < a.length) push("removed", a[i++]);
  while (j < b.length) push("added", b[j++]);

  return tokens;
}

const OP_STYLE: Record<Op, React.CSSProperties> = {
  same: {},
  added: {
    background: "var(--mantine-color-teal-1)",
    color: "var(--mantine-color-teal-9)",
    borderRadius: 3,
  },
  removed: {
    background: "var(--mantine-color-red-1)",
    color: "var(--mantine-color-red-9)",
    borderRadius: 3,
    textDecoration: "line-through",
  },
};

/**
 * Inline before/after of an edited article body: removed words struck through
 * in red, inserted words highlighted in green. Collapsed by default — a
 * revision list stays scannable, and the full text is one click away.
 */
export function ArticleBodyDiff({
  fromBody,
  toBody,
}: {
  fromBody: string;
  toBody: string;
}) {
  const [open, setOpen] = useState(false);
  const tokens = useMemo(
    () => (open ? diffWords(fromBody, toBody) : []),
    [open, fromBody, toBody],
  );

  return (
    <Box mt={4}>
      <Button
        variant="subtle"
        size="compact-xs"
        color="gray"
        px={4}
        leftSection={
          open ? <ChevronDown size={12} /> : <ChevronRight size={12} />
        }
        onClick={() => setOpen((v) => !v)}
      >
        {open ? "Hide changes" : "View changes"}
      </Button>

      {open && (
        <Box
          mt={6}
          p="sm"
          style={{
            borderRadius: 8,
            border: "1px solid var(--mantine-color-gray-3)",
            background: "var(--mantine-color-gray-0)",
            maxHeight: 320,
            overflowY: "auto",
          }}
        >
          <Text
            size="xs"
            component="div"
            style={{ whiteSpace: "pre-wrap", lineHeight: 1.6 }}
          >
            {tokens.map((token, index) => (
              <span key={index} style={OP_STYLE[token.op]}>
                {token.text}
              </span>
            ))}
          </Text>
          <Group gap="md" mt="xs">
            <Group gap={4}>
              <Box w={10} h={10} style={{ ...OP_STYLE.removed, borderRadius: 2 }} />
              <Text size="10px" c="dimmed">
                Removed
              </Text>
            </Group>
            <Group gap={4}>
              <Box w={10} h={10} style={{ ...OP_STYLE.added, borderRadius: 2 }} />
              <Text size="10px" c="dimmed">
                Added
              </Text>
            </Group>
          </Group>
        </Box>
      )}
    </Box>
  );
}
