import { useMemo, useRef, useState } from "react";
import { useParams } from "react-router-dom";
import {
  ActionIcon,
  Badge,
  Box,
  Button,
  Card,
  Center,
  Group,
  Loader,
  Menu,
  Modal,
  Paper,
  ScrollArea,
  Stack,
  Switch,
  Text,
  Textarea,
  TextInput,
  Title,
  Tooltip,
} from "@mantine/core";
import {
  AlertTriangle,
  ArrowDown,
  ArrowUp,
  Building2,
  CalendarClock,
  CalendarDays,
  CalendarRange,
  ChevronDown,
  Coins,
  Hash,
  ListOrdered,
  ListPlus,
  ListTree,
  Mail,
  MapPin,
  Package,
  Pencil,
  Phone,
  Plus,
  RefreshCw,
  Settings2,
  Trash2,
  Weight,
} from "lucide-react";

import { PageContainer, PageHeader } from "@/components/page";
import {
  useAddArticle,
  useContractTemplate,
  useContractTemplatePreview,
  useRemoveArticle,
  useReplaceArticles,
  useUpdateArticle,
  useUpdateContractTemplate,
} from "@/hooks/contract-templates/useContractTemplates";
import type { ContractTemplateArticle } from "@/services/contract-templates.service";

const BODY_HINT =
  'One clause per line. Use "New clause" for the next number (1., 2., …), "Sub-clause" for a nested number (1.1, then 1.1.1), and "Bullet" for a • point — the number or bullet is typed for you, just add the text. Placeholders are filled from the contract when the document is generated.';

interface ArticleDraft {
  id?: string;
  title: string;
  body: string;
}

interface PlaceholderDef {
  token: string;
  label: string;
  icon: typeof Building2;
  hint: string;
}

/**
 * Placeholders the renderer fills from the contract view model
 * (contract-view-model.builder.ts). Quick row = the ones template authors
 * reach for constantly; the rest live in the grouped "More" menu.
 */
const QUICK_PLACEHOLDERS: PlaceholderDef[] = [
  {
    token: "{{client.companyName}}",
    label: "Client name",
    icon: Building2,
    hint: "Company name of the contracting client",
  },
  {
    token: "{{reference}}",
    label: "Reference",
    icon: Hash,
    hint: "Contract reference number",
  },
  {
    token: "{{contractDate}}",
    label: "Contract date",
    icon: CalendarDays,
    hint: "Full signature date of the contract",
  },
  {
    token: "{{contractYear}}",
    label: "Contract year",
    icon: CalendarRange,
    hint: "Year the contract is signed",
  },
];

const MORE_PLACEHOLDER_GROUPS: { label: string; items: PlaceholderDef[] }[] = [
  {
    label: "Client",
    items: [
      {
        token: "{{client.companyAddress}}",
        label: "Client address",
        icon: MapPin,
        hint: "Street address of the client",
      },
      {
        token: "{{client.companyLocation}}",
        label: "Client location",
        icon: MapPin,
        hint: "Region / city of the client",
      },
      {
        token: "{{client.phone}}",
        label: "Client phone",
        icon: Phone,
        hint: "Client phone number",
      },
      {
        token: "{{client.email}}",
        label: "Client email",
        icon: Mail,
        hint: "Client email address",
      },
      {
        token: "{{client.tinNumber}}",
        label: "Client TIN",
        icon: Hash,
        hint: "Client tax identification number",
      },
    ],
  },
  {
    label: "Route & cargo",
    items: [
      {
        token: "{{schedule.originLabel}}",
        label: "Origin",
        icon: MapPin,
        hint: "Origin yard / station",
      },
      {
        token: "{{schedule.destinationLabel}}",
        label: "Destination",
        icon: MapPin,
        hint: "Destination yard / station",
      },
      {
        token: "{{schedule.serviceType}}",
        label: "Service type",
        icon: Settings2,
        hint: "Contracted service type name",
      },
      {
        token: "{{schedule.cargoDescription}}",
        label: "Cargo description",
        icon: Package,
        hint: "Description of the cargo",
      },
      {
        token: "{{schedule.totalWeightVgm}}",
        label: "Total weight",
        icon: Weight,
        hint: "Total verified gross mass",
      },
      {
        token: "{{schedule.equipmentReturn}}",
        label: "Equipment return",
        icon: RefreshCw,
        hint: "Empty-equipment return terms",
      },
      {
        token: "{{schedule.scheduledDate}}",
        label: "Scheduled date",
        icon: CalendarClock,
        hint: "Scheduled shipment date",
      },
    ],
  },
  {
    label: "Pricing",
    items: [
      {
        token: "{{pricing.currency}}",
        label: "Currency",
        icon: Coins,
        hint: "Payment currency (e.g. USD)",
      },
    ],
  },
  {
    label: "Service provider (EDR)",
    items: [
      {
        token: "{{provider.name}}",
        label: "Provider name",
        icon: Building2,
        hint: "EDR legal company name",
      },
      {
        token: "{{provider.address}}",
        label: "Provider address",
        icon: MapPin,
        hint: "EDR principal place of business",
      },
      {
        token: "{{provider.phone}}",
        label: "Provider phone",
        icon: Phone,
        hint: "EDR phone number",
      },
      {
        token: "{{provider.email}}",
        label: "Provider email",
        icon: Mail,
        hint: "EDR email address",
      },
    ],
  },
];

const ALL_PLACEHOLDERS: PlaceholderDef[] = [
  ...QUICK_PLACEHOLDERS,
  ...MORE_PLACEHOLDER_GROUPS.flatMap((g) => g.items),
];

const KNOWN_TOKENS = new Set<string>([
  ...ALL_PLACEHOLDERS.map((p) => p.token),
  // Still filled by the renderer, just no longer offered as an insert button.
  "{{pricing.totalAmount}}",
]);

/** Any {{…}} tokens in the text the renderer does not know how to fill. */
function unknownTokens(text: string): string[] {
  const found = text.match(/\{\{[^{}]+\}\}/g) ?? [];
  return [...new Set(found.filter((t) => !KNOWN_TOKENS.has(t)))];
}

interface ParsedClause {
  text: string;
  /** Computed outline number, e.g. "3" or "2.1.4". */
  number: string;
  /** Nesting level: 1 = clause, 2 = sub-clause (x.y), 3 = x.y.z, … */
  depth: number;
  bullets: string[];
}

interface ParsedBody {
  /** Set (instead of clauses) when the body is one plain paragraph. */
  paragraph?: string;
  clauses: ParsedClause[];
}

/**
 * Leading outline token on a clause line ("1. ", "2.1 ", "1.1.1) ") — its
 * segment count sets the depth; the digits themselves are recomputed. Single
 * segment requires "."/")" so prose like "10 tons…" is untouched; a token may
 * end the line (empty clause still being typed).
 */
const CLAUSE_NUMBER_RE = /^(?:(\d+(?:\.\d+)+)[.)]?|(\d+)[.)])(?:\s+|$)/;

/** Depth of the outline token in a CLAUSE_NUMBER_RE match, else null. */
function matchDepth(match: RegExpExecArray | null): number | null {
  if (!match) return null;
  const token = match[1] ?? match[2];
  return Math.min(token.split(".").length, MAX_CLAUSE_DEPTH);
}

/** Deepest supported sub-clause level. */
const MAX_CLAUSE_DEPTH = 6;

/**
 * Mirror of the API renderer's rules (contract-article.util.ts): one clause per
 * line; a leading outline number ("2. ", "2.1 ") nests the line as a sub-clause
 * at that depth and is renumbered sequentially; "- " nests a bullet under the
 * previous clause; a single un-numbered bullet-less clause renders as a plain
 * paragraph instead of a numbered list of one.
 */
function parseArticleBody(body: string): ParsedBody {
  const clauses: ParsedClause[] = [];
  const counters: number[] = [];
  let sawNumberToken = false;
  for (const raw of body.split("\n")) {
    const line = raw.trim();
    if (!line) continue;
    if (line.startsWith("- ") && clauses.length > 0) {
      clauses[clauses.length - 1].bullets.push(line.slice(2).trim());
      continue;
    }
    const cleaned = line.replace(/^- /, "");
    const match = CLAUSE_NUMBER_RE.exec(cleaned);
    let depth = matchDepth(match) ?? 1;
    // A sub-clause can only sit directly under an existing parent.
    depth = Math.min(depth, counters.length + 1);
    if (match) sawNumberToken = true;
    counters.splice(depth);
    while (counters.length < depth) counters.push(0);
    counters[depth - 1] += 1;
    clauses.push({
      text: match ? cleaned.slice(match[0].length).trim() : cleaned,
      number: counters.slice(0, depth).join("."),
      depth,
      bullets: [],
    });
  }
  if (
    clauses.length === 1 &&
    clauses[0].bullets.length === 0 &&
    !sawNumberToken
  ) {
    return { paragraph: clauses[0].text, clauses: [] };
  }
  return { clauses };
}

/**
 * Rewrite the leading outline tokens in a body so every numbered clause line
 * carries its computed sequential number (stale numbers self-heal). Lines
 * without a number token and bullet lines pass through untouched.
 */
function renumberBody(body: string): string {
  const counters: number[] = [];
  return body
    .split("\n")
    .map((raw) => {
      const line = raw.trim();
      if (!line || line.startsWith("- ")) return raw;
      const match = CLAUSE_NUMBER_RE.exec(line);
      let depth = matchDepth(match) ?? 1;
      depth = Math.min(depth, counters.length + 1);
      counters.splice(depth);
      while (counters.length < depth) counters.push(0);
      counters[depth - 1] += 1;
      if (!match) return raw;
      const number = counters.slice(0, depth).join(".");
      return `${number}. ${line.slice(match[0].length).trim()}`;
    })
    .join("\n");
}

/** Render clause text with {{placeholders}} highlighted as green chips. */
function HighlightedText({ text }: { text: string }) {
  const parts = text.split(/(\{\{[^{}]+\}\})/g);
  return (
    <>
      {parts.map((part, i) =>
        /^\{\{[^{}]+\}\}$/.test(part) ? (
          <Text
            key={i}
            component="span"
            size="xs"
            fw={600}
            c={KNOWN_TOKENS.has(part) ? "edr-green.8" : "red.7"}
            px={4}
            style={{
              borderRadius: 4,
              background: KNOWN_TOKENS.has(part)
                ? "var(--mantine-color-edr-green-0)"
                : "var(--mantine-color-red-0)",
              whiteSpace: "nowrap",
            }}
          >
            {part}
          </Text>
        ) : (
          <span key={i}>{part}</span>
        ),
      )}
    </>
  );
}

export default function ContractTemplateEditorPage() {
  const { code } = useParams<{ code: string }>();
  const { data: template, isLoading } = useContractTemplate(code);
  const preview = useContractTemplatePreview(code);

  const updateTemplate = useUpdateContractTemplate(code ?? "");
  const addArticle = useAddArticle(code ?? "");
  const updateArticle = useUpdateArticle(code ?? "");
  const removeArticle = useRemoveArticle(code ?? "");
  const replaceArticles = useReplaceArticles(code ?? "");

  const [articleDraft, setArticleDraft] = useState<ArticleDraft | null>(null);
  const [deleteTarget, setDeleteTarget] = useState<ContractTemplateArticle | null>(null);
  const [detailsOpen, setDetailsOpen] = useState(false);

  const sortedArticles = useMemo(
    () => [...(template?.articles ?? [])].sort((a, b) => a.order - b.order),
    [template],
  );

  const moveArticle = (index: number, delta: -1 | 1) => {
    const next = [...sortedArticles];
    const target = index + delta;
    if (target < 0 || target >= next.length) return;
    [next[index], next[target]] = [next[target], next[index]];
    replaceArticles.mutate(
      next.map(({ id, title, body }) => ({ id, title, body })),
    );
  };

  const saveArticle = (values: { title: string; body: string }) => {
    if (!articleDraft) return;
    if (articleDraft.id) {
      updateArticle.mutate({ articleId: articleDraft.id, payload: values });
    } else {
      addArticle.mutate(values);
    }
    setArticleDraft(null);
  };

  if (isLoading || !template) {
    return (
      <PageContainer>
        <Center h={360}>
          <Loader color="edr-green" />
        </Center>
      </PageContainer>
    );
  }

  return (
    <PageContainer>
      <PageHeader
        title={template.name}
        subtitle={template.documentTitle}
        backTo="/dashboard/contract-templates"
        meta={
          <Group gap={6}>
            <Badge variant="outline" color="edr-green">
              {template.code.replaceAll("_", " · ")}
            </Badge>
            {!template.isActive && (
              <Badge variant="light" color="red">
                Inactive
              </Badge>
            )}
          </Group>
        }
        action={
          <Group gap="sm">
            <Switch
              color="edr-green"
              label="Active"
              checked={template.isActive}
              onChange={(event) =>
                updateTemplate.mutate({ isActive: event.currentTarget.checked })
              }
            />
            <Button
              variant="light"
              color="edr-green"
              leftSection={<Settings2 size={16} />}
              onClick={() => setDetailsOpen(true)}
            >
              Document details
            </Button>
            <Button
              color="edr-green"
              leftSection={<Plus size={16} />}
              onClick={() => setArticleDraft({ title: "", body: "" })}
            >
              Add article
            </Button>
          </Group>
        }
      />

      <div className="grid grid-cols-1 gap-4 xl:grid-cols-2">
        {/* ── Article list ─────────────────────────────────────────────── */}
        <Stack gap="sm">
          {sortedArticles.map((article, index) => (
            <Card key={article.id} withBorder radius="lg" padding="md">
              <Group justify="space-between" align="flex-start" wrap="nowrap">
                <div style={{ minWidth: 0 }}>
                  <Text fw={700} size="sm" c="edr-green.7">
                    Article {index + 1}
                  </Text>
                  <Title order={5}>{article.title}</Title>
                  <Text size="sm" c="dimmed" lineClamp={2} mt={4}>
                    {article.body}
                  </Text>
                </div>
                <Group gap={4} wrap="nowrap">
                  <Tooltip label="Move up">
                    <ActionIcon
                      variant="subtle"
                      color="gray"
                      disabled={index === 0}
                      onClick={() => moveArticle(index, -1)}
                    >
                      <ArrowUp size={16} />
                    </ActionIcon>
                  </Tooltip>
                  <Tooltip label="Move down">
                    <ActionIcon
                      variant="subtle"
                      color="gray"
                      disabled={index === sortedArticles.length - 1}
                      onClick={() => moveArticle(index, 1)}
                    >
                      <ArrowDown size={16} />
                    </ActionIcon>
                  </Tooltip>
                  <Tooltip label="Edit article">
                    <ActionIcon
                      variant="subtle"
                      color="edr-green"
                      onClick={() =>
                        setArticleDraft({
                          id: article.id,
                          title: article.title,
                          body: article.body,
                        })
                      }
                    >
                      <Pencil size={16} />
                    </ActionIcon>
                  </Tooltip>
                  <Tooltip label="Remove article">
                    <ActionIcon
                      variant="subtle"
                      color="red"
                      onClick={() => setDeleteTarget(article)}
                    >
                      <Trash2 size={16} />
                    </ActionIcon>
                  </Tooltip>
                </Group>
              </Group>
            </Card>
          ))}
          {sortedArticles.length === 0 && (
            <Card withBorder radius="lg" padding="xl">
              <Center>
                <Text c="dimmed">
                  No articles yet — add the first article to build this contract.
                </Text>
              </Center>
            </Card>
          )}
        </Stack>

        {/* ── Live preview ─────────────────────────────────────────────── */}
        <Card withBorder radius="lg" padding="sm" className="xl:sticky xl:top-4 self-start">
          <Group justify="space-between" mb="xs" px={4}>
            <Text fw={600} size="sm">
              Document preview (mock data)
            </Text>
            <Tooltip label="Refresh preview">
              <ActionIcon
                variant="subtle"
                color="edr-green"
                loading={preview.isFetching}
                onClick={() => void preview.refetch()}
              >
                <RefreshCw size={16} />
              </ActionIcon>
            </Tooltip>
          </Group>
          <Paper withBorder radius="md" style={{ overflow: "hidden" }}>
            {preview.isLoading ? (
              <Center h={480}>
                <Loader color="edr-green" />
              </Center>
            ) : (
              <iframe
                title="Template preview"
                srcDoc={preview.data?.html}
                style={{
                  width: "100%",
                  height: "calc(100vh - 240px)",
                  minHeight: 480,
                  border: 0,
                  background: "#f3f8f5",
                }}
              />
            )}
          </Paper>
        </Card>
      </div>

      {/* ── Add / edit article modal ───────────────────────────────────── */}
      {articleDraft && (
        <ArticleEditorModal
          initial={articleDraft}
          saving={addArticle.isPending || updateArticle.isPending}
          onClose={() => setArticleDraft(null)}
          onSave={saveArticle}
        />
      )}

      {/* ── Delete confirm ─────────────────────────────────────────────── */}
      <Modal
        opened={Boolean(deleteTarget)}
        onClose={() => setDeleteTarget(null)}
        title="Remove article"
        size="md"
      >
        <Stack gap="md">
          <Text size="sm">
            Remove <strong>{deleteTarget?.title}</strong> from this template? The
            remaining articles are renumbered automatically.
          </Text>
          <Group justify="flex-end">
            <Button variant="default" onClick={() => setDeleteTarget(null)}>
              Cancel
            </Button>
            <Button
              color="red"
              loading={removeArticle.isPending}
              onClick={() => {
                if (deleteTarget) removeArticle.mutate(deleteTarget.id);
                setDeleteTarget(null);
              }}
            >
              Remove article
            </Button>
          </Group>
        </Stack>
      </Modal>

      {/* ── Document details modal ─────────────────────────────────────── */}
      <DocumentDetailsModal
        opened={detailsOpen}
        onClose={() => setDetailsOpen(false)}
        initial={{
          name: template.name,
          description: template.description ?? "",
          documentTitle: template.documentTitle,
          whereasClauses: template.whereasClauses,
        }}
        saving={updateTemplate.isPending}
        onSave={(values) => {
          updateTemplate.mutate(values);
          setDetailsOpen(false);
        }}
      />
    </PageContainer>
  );
}

interface ArticleEditorModalProps {
  initial: ArticleDraft;
  saving: boolean;
  onClose: () => void;
  onSave: (values: { title: string; body: string }) => void;
}

/**
 * Rich add/edit article editor: placeholder buttons insert at the text cursor
 * of whichever field (title or body) was focused last, with a live preview of
 * the numbered clauses exactly as the renderer lays them out.
 */
function ArticleEditorModal({
  initial,
  saving,
  onClose,
  onSave,
}: ArticleEditorModalProps) {
  const [title, setTitle] = useState(initial.title);
  const [body, setBody] = useState(initial.body);

  const titleRef = useRef<HTMLInputElement>(null);
  const bodyRef = useRef<HTMLTextAreaElement>(null);
  // Placeholders drop into whichever field held the cursor last (body default).
  const lastFocused = useRef<"title" | "body">("body");

  const insertAtCursor = (snippet: string) => {
    const isTitle = lastFocused.current === "title";
    const el = isTitle ? titleRef.current : bodyRef.current;
    const value = isTitle ? title : body;
    const start = el?.selectionStart ?? value.length;
    const end = el?.selectionEnd ?? start;
    const next = value.slice(0, start) + snippet + value.slice(end);
    if (isTitle) setTitle(next);
    else setBody(next);
    // Refocus and place the caret right after the inserted snippet once the
    // controlled re-render has flushed.
    requestAnimationFrame(() => {
      if (!el) return;
      el.focus();
      const caret = start + snippet.length;
      el.setSelectionRange(caret, caret);
    });
  };

  /**
   * Insert a structured line (clause / sub-clause / bullet) on a fresh line
   * below the one the caret is on. Clause lines get their outline number typed
   * in automatically ("3. ", "3.1. ", …) and every numbered line in the body is
   * renumbered so the text always matches the preview.
   */
  const insertStructuredLine = (kind: "clause" | "sub" | "bullet") => {
    const el = bodyRef.current;
    lastFocused.current = "body";
    const caret = el?.selectionStart ?? body.length;
    // Structured lines never split a sentence — insert after the caret's line.
    const lineEnd = body.indexOf("\n", caret);
    const insertAt = lineEnd === -1 ? body.length : lineEnd;
    const before = body.slice(0, insertAt);
    const after = body.slice(insertAt); // "" or starts with "\n"

    let prefix: string;
    if (kind === "bullet") {
      prefix = "- ";
    } else {
      // New clause always starts a fresh top-level number. Sub-clause nests
      // one level under a clause (1 → 1.1) but adds a SIBLING when the caret
      // is already on a sub-clause (1.1 → 1.2 → 1.3, not ever-deeper) — a
      // third level is reached by typing its number (e.g. "1.1.1 ") directly.
      const above = parseArticleBody(before);
      const lastDepth = above.paragraph
        ? 1
        : (above.clauses[above.clauses.length - 1]?.depth ?? 0);
      const depth =
        kind === "sub"
          ? lastDepth <= 1
            ? Math.min(lastDepth + 1, MAX_CLAUSE_DEPTH)
            : lastDepth
          : 1;
      // Digits are placeholders — renumberBody assigns the real value.
      prefix = `${Array.from({ length: depth }, () => "1").join(".")}. `;
    }

    const beforeLines = before.length > 0 ? before.split("\n") : [];
    const afterLines =
      after.length > 0 ? after.slice(1).split("\n") : [];
    const insertedIdx = beforeLines.length;
    const joined = [...beforeLines, prefix, ...afterLines].join("\n");
    const next = kind === "bullet" ? joined : renumberBody(joined);
    setBody(next);

    // Caret lands at the end of the inserted line, ready for typing.
    const caretTarget = next
      .split("\n")
      .slice(0, insertedIdx + 1)
      .join("\n").length;
    requestAnimationFrame(() => {
      const field = bodyRef.current;
      if (!field) return;
      field.focus();
      field.setSelectionRange(caretTarget, caretTarget);
    });
  };

  const parsed = useMemo(() => parseArticleBody(body), [body]);
  const clauseCount = parsed.paragraph ? 1 : parsed.clauses.length;
  const unknown = useMemo(
    () => unknownTokens(`${title}\n${body}`),
    [title, body],
  );
  const canSave = title.trim().length >= 2 && body.trim().length >= 2;

  return (
    <Modal
      opened
      onClose={onClose}
      title={initial.id ? "Edit article" : "Add article"}
      size="min(1120px, 95vw)"
    >
      <div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
        {/* ── Editor ─────────────────────────────────────────────────── */}
        <Stack gap="sm">
          <TextInput
            ref={titleRef}
            label="Article title"
            placeholder="e.g. Obligations of the Client"
            value={title}
            onChange={(event) => setTitle(event.currentTarget.value)}
            onFocus={() => (lastFocused.current = "title")}
            required
          />

          <Box>
            <Text size="sm" fw={500} mb={4}>
              Insert placeholder
            </Text>
            <Group gap={6} wrap="wrap">
              {QUICK_PLACEHOLDERS.map(({ token, label, icon: Icon, hint }) => (
                <Tooltip key={token} label={hint} withArrow>
                  <Button
                    variant="light"
                    color="edr-green"
                    size="compact-sm"
                    radius="md"
                    leftSection={<Icon size={13} />}
                    // Keep the field's focus/caret alive so insertion lands
                    // where the user was typing.
                    onMouseDown={(e) => e.preventDefault()}
                    onClick={() => insertAtCursor(token)}
                  >
                    {label}
                  </Button>
                </Tooltip>
              ))}
              <Menu shadow="md" width={300} position="bottom-start">
                <Menu.Target>
                  <Button
                    variant="default"
                    size="compact-sm"
                    radius="md"
                    leftSection={<Plus size={13} />}
                    rightSection={<ChevronDown size={13} />}
                    onMouseDown={(e) => e.preventDefault()}
                  >
                    More
                  </Button>
                </Menu.Target>
                <Menu.Dropdown mah={340} style={{ overflowY: "auto" }}>
                  {MORE_PLACEHOLDER_GROUPS.map((group) => (
                    <Box key={group.label}>
                      <Menu.Label>{group.label}</Menu.Label>
                      {group.items.map(({ token, label, icon: Icon, hint }) => (
                        <Menu.Item
                          key={token}
                          leftSection={<Icon size={14} />}
                          onClick={() => insertAtCursor(token)}
                        >
                          <Text size="sm">{label}</Text>
                          <Text size="xs" c="dimmed" title={hint}>
                            {token}
                          </Text>
                        </Menu.Item>
                      ))}
                    </Box>
                  ))}
                </Menu.Dropdown>
              </Menu>
            </Group>
          </Box>

          <Box>
            <Text size="sm" fw={500} mb={4}>
              Add structure
            </Text>
            <Group gap={6} wrap="wrap">
              <Tooltip
                label="New line with the next clause number typed for you (1., 2., 3., …)"
                withArrow
              >
                <Button
                  variant="light"
                  color="edr-green"
                  size="compact-sm"
                  radius="md"
                  leftSection={<ListOrdered size={13} />}
                  onMouseDown={(e) => e.preventDefault()}
                  onClick={() => insertStructuredLine("clause")}
                >
                  New clause
                </Button>
              </Tooltip>
              <Tooltip
                label="Numbered point under the current clause — 1.1, then 1.2, 1.3 on each click. For a deeper level type its number yourself (e.g. 1.1.1 )"
                withArrow
              >
                <Button
                  variant="light"
                  color="edr-green"
                  size="compact-sm"
                  radius="md"
                  leftSection={<ListTree size={13} />}
                  disabled={body.trim().length === 0}
                  onMouseDown={(e) => e.preventDefault()}
                  onClick={() => insertStructuredLine("sub")}
                >
                  Sub-clause
                </Button>
              </Tooltip>
              <Tooltip
                label="New line with a bullet (•) under the current clause"
                withArrow
              >
                <Button
                  variant="light"
                  color="edr-green"
                  size="compact-sm"
                  radius="md"
                  leftSection={<ListPlus size={13} />}
                  disabled={body.trim().length === 0}
                  onMouseDown={(e) => e.preventDefault()}
                  onClick={() => insertStructuredLine("bullet")}
                >
                  Bullet
                </Button>
              </Tooltip>
            </Group>
          </Box>

          <Textarea
            ref={bodyRef}
            label="Article body"
            description={BODY_HINT}
            value={body}
            onChange={(event) => setBody(event.currentTarget.value)}
            onFocus={() => (lastFocused.current = "body")}
            autosize
            minRows={12}
            maxRows={22}
            styles={{
              input: { fontFamily: "ui-monospace, monospace", fontSize: 13 },
            }}
            required
          />

          {unknown.length > 0 && (
            <Group gap={6} wrap="nowrap" align="flex-start">
              <AlertTriangle size={14} className="mt-0.5 shrink-0 text-red-600" />
              <Text size="xs" c="red.7">
                Unknown placeholder{unknown.length > 1 ? "s" : ""}{" "}
                {unknown.join(", ")} — the generator won't fill{" "}
                {unknown.length > 1 ? "these" : "this"}. Pick from the Insert
                placeholder buttons instead.
              </Text>
            </Group>
          )}
        </Stack>

        {/* ── Live preview ───────────────────────────────────────────── */}
        <Paper withBorder radius="md" p="md" className="self-start lg:sticky lg:top-0">
          <Group justify="space-between" mb="xs">
            <Text fw={600} size="sm">
              Live preview
            </Text>
            <Text size="xs" c="dimmed">
              {clauseCount} clause{clauseCount !== 1 ? "s" : ""}
            </Text>
          </Group>
          <ScrollArea.Autosize mah="60vh">
            {title.trim() || clauseCount > 0 ? (
              <Stack gap="xs">
                {title.trim() && (
                  <Title order={5}>
                    <HighlightedText text={title} />
                  </Title>
                )}
                {parsed.paragraph && (
                  <Text size="sm">
                    <HighlightedText text={parsed.paragraph} />
                  </Text>
                )}
                {parsed.clauses.map((clause, i) => (
                  <Box key={i} pl={(clause.depth - 1) * 20}>
                    <Text size="sm">
                      <Text component="span" fw={600} c="edr-green.7">
                        {clause.number}.{" "}
                      </Text>
                      <HighlightedText text={clause.text} />
                    </Text>
                    {clause.bullets.length > 0 && (
                      <Stack gap={2} mt={2} pl="lg">
                        {clause.bullets.map((bullet, j) => (
                          <Text key={j} size="sm" c="dimmed">
                            • <HighlightedText text={bullet} />
                          </Text>
                        ))}
                      </Stack>
                    )}
                  </Box>
                ))}
              </Stack>
            ) : (
              <Text size="sm" c="dimmed" ta="center" py="xl">
                Start typing — the article renders here exactly as it will
                appear in the contract.
              </Text>
            )}
          </ScrollArea.Autosize>
        </Paper>
      </div>

      <Group justify="flex-end" mt="md">
        <Button variant="default" onClick={onClose}>
          Cancel
        </Button>
        <Button
          color="edr-green"
          disabled={!canSave}
          loading={saving}
          onClick={() => onSave({ title: title.trim(), body: body.trim() })}
        >
          {initial.id ? "Save changes" : "Add article"}
        </Button>
      </Group>
    </Modal>
  );
}

interface DocumentDetailsModalProps {
  opened: boolean;
  onClose: () => void;
  initial: {
    name: string;
    description: string;
    documentTitle: string;
    whereasClauses: string[];
  };
  saving: boolean;
  onSave: (values: {
    name: string;
    description: string;
    documentTitle: string;
    whereasClauses: string[];
  }) => void;
}

function DocumentDetailsModal({
  opened,
  onClose,
  initial,
  saving,
  onSave,
}: DocumentDetailsModalProps) {
  const [name, setName] = useState(initial.name);
  const [description, setDescription] = useState(initial.description);
  const [documentTitle, setDocumentTitle] = useState(initial.documentTitle);
  const [whereas, setWhereas] = useState(initial.whereasClauses.join("\n\n"));

  // Re-sync local state each time the modal opens with fresh server data.
  const [lastOpened, setLastOpened] = useState(false);
  if (opened && !lastOpened) {
    setName(initial.name);
    setDescription(initial.description);
    setDocumentTitle(initial.documentTitle);
    setWhereas(initial.whereasClauses.join("\n\n"));
    setLastOpened(true);
  } else if (!opened && lastOpened) {
    setLastOpened(false);
  }

  return (
    <Modal opened={opened} onClose={onClose} title="Document details" size="xl">
      <Stack gap="sm">
        <TextInput
          label="Template name"
          value={name}
          onChange={(event) => setName(event.currentTarget.value)}
          required
        />
        <TextInput
          label="Cover page title"
          description="Printed on the contract cover, e.g. “Import Container Transport Service by Railway”."
          value={documentTitle}
          onChange={(event) => setDocumentTitle(event.currentTarget.value)}
          required
        />
        <Textarea
          label="Card description"
          description="Shown on the Templates page card only — not printed."
          value={description}
          onChange={(event) => setDescription(event.currentTarget.value)}
          autosize
          minRows={2}
        />
        <Textarea
          label="Recitals (WHEREAS clauses)"
          description="One recital per paragraph — separate recitals with a blank line."
          value={whereas}
          onChange={(event) => setWhereas(event.currentTarget.value)}
          autosize
          minRows={5}
          maxRows={12}
        />
        <Group justify="flex-end">
          <Button variant="default" onClick={onClose}>
            Cancel
          </Button>
          <Button
            color="edr-green"
            loading={saving}
            disabled={name.trim().length < 3 || documentTitle.trim().length < 3}
            onClick={() =>
              onSave({
                name: name.trim(),
                description: description.trim(),
                documentTitle: documentTitle.trim(),
                whereasClauses: whereas
                  .split(/\n\s*\n/)
                  .map((clause) => clause.trim())
                  .filter(Boolean),
              })
            }
          >
            Save details
          </Button>
        </Group>
      </Stack>
    </Modal>
  );
}
