import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { Link as RouterLink, useBlocker, useNavigate, useParams } from "react-router-dom";

import {
  Alert, Box, Button, Chip, Collapse, Divider, FormControl, FormControlLabel, IconButton, InputLabel,
  MenuItem, Paper, Select, Skeleton, Slider, Stack, Switch, Tab, Tabs, TextField, Tooltip, Typography,
} from "@mui/material";
import useMediaQuery from "@mui/material/useMediaQuery";
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
import ArrowUpwardIcon from "@mui/icons-material/ArrowUpward";
import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward";
import VisibilityIcon from "@mui/icons-material/Visibility";
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
import PictureAsPdfIcon from "@mui/icons-material/PictureAsPdf";
import PublicIcon from "@mui/icons-material/Public";
import AddIcon from "@mui/icons-material/Add";
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
import DragIndicatorIcon from "@mui/icons-material/DragIndicator";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import ZoomInIcon from "@mui/icons-material/ZoomIn";
import ZoomOutIcon from "@mui/icons-material/ZoomOut";
import UndoIcon from "@mui/icons-material/Undo";
import RedoIcon from "@mui/icons-material/Redo";

import { api, getApiErrorMessage } from "../api";
import { useToast } from "../toast";
import RichTextField from "../components/RichTextField";
import AiSectionAssistant from "../components/cv/AiSectionAssistant";
import CvTemplateThumbnail from "../components/CvTemplateThumbnail";
import { useDragReorder } from "../hooks/useDragReorder";
import {
  CUSTOM_SECTION_PRESETS, CvCustomSectionSetting, CvItemOverride, CvOutline, CvOutlineSection, CvSectionSetting,
  CvTheme, CvVariant, CvVariantSettings, CvVariantVersionInfo, DEFAULT_SECTION_ORDER, SECTION_LABELS,
  cvBuilderApi, getCvPageCount, getCvPageMetrics, moveItem,
} from "../cvBuilder";
import { useDialogActions } from "../dialogs";

const FONTS = [
  "'Segoe UI', Roboto, Arial, sans-serif",
  "Arial, Helvetica, sans-serif",
  "Georgia, 'Times New Roman', serif",
  "'Helvetica Neue', Arial, sans-serif",
  "'Roboto', Arial, sans-serif",
  "'Poppins', 'Segoe UI', Arial, sans-serif",
];
const FONT_LABELS = ["Segoe UI", "Arial", "Georgia (serif)", "Helvetica Neue", "Roboto", "Poppins"];
const MIN_PREVIEW_ZOOM = 0.32;
type SaveState = "idle" | "unsaved" | "saving" | "saved" | "error";

function previewDocumentHtml(html: string, pageHeightPx: number, pageIndex = 0): string {
  const height = Math.max(1, Math.round(pageHeightPx * 100) / 100);
  const offset = Math.max(0, pageIndex) * height;
  const previewCss = `<style data-cv-preview-page>html,body{overflow:hidden!important;background:#fff!important;}.page{transform-origin:top left;}</style>`;
  // Screen media does not apply Chromium's paged-media fragmentation. Reproduce the important
  // keep-together decisions before each preview frame crops the selected physical page.
  const previewScript = `<script data-cv-preview-pagination>(()=>{const H=${height};const OFFSET=${offset};const page=document.querySelector('.page');if(!page)return;const topOf=(el)=>{let top=0,node=el;while(node&&node!==document.body){top+=node.offsetTop||0;node=node.offsetParent;}return top;};const paginate=()=>{document.querySelectorAll('[data-cv-preview-pad]').forEach((el)=>{el.style.paddingTop='';el.removeAttribute('data-cv-preview-pad');});const pushToNextPage=(el,heightOverride)=>{const current=parseFloat(getComputedStyle(el).paddingTop)||0;const top=topOf(el)+current;const blockHeight=heightOverride||Math.max(0,el.offsetHeight-current);if(blockHeight<=0||blockHeight>H-2)return;const remaining=H-(top%H);if(blockHeight>remaining+1){el.style.paddingTop=(current+remaining)+'px';el.setAttribute('data-cv-preview-pad','true');}};document.querySelectorAll('.section').forEach((section)=>{if(!section.classList.contains('section-flow')){pushToNextPage(section);return;}const title=section.querySelector(':scope > .section-title');const first=title&&title.nextElementSibling;if(title&&first){const groupedBottom=topOf(first)+first.offsetHeight;pushToNextPage(section,groupedBottom-topOf(section));}section.querySelectorAll(':scope > .entry:not(.entry-flow),:scope > .skill-groups > .skill-group,:scope > .bullets > li:not(.item-flow),:scope > .entry-flow .bullets > li:not(.item-flow)').forEach((block)=>pushToNextPage(block));});page.style.transform='translateY(-'+OFFSET+'px)';};paginate();requestAnimationFrame(()=>requestAnimationFrame(paginate));})();</script>`;
  const withCss = html.includes("</head>") ? html.replace("</head>", `${previewCss}</head>`) : `${previewCss}${html}`;
  return withCss.includes("</body>") ? withCss.replace("</body>", `${previewScript}</body>`) : `${withCss}${previewScript}`;
}

export default function CvBuilderEditor() {
  const { id } = useParams();
  const variantId = Number(id);
  const navigate = useNavigate();
  const { toast } = useToast();
  const { confirmAction } = useDialogActions();
  const compactEditor = useMediaQuery("(max-width:899.95px)");

  const [name, setName] = useState("");
  const [settings, setSettings] = useState<CvVariantSettings | null>(null);
  const [themes, setThemes] = useState<CvTheme[]>([]);
  const [outline, setOutline] = useState<CvOutline | null>(null);
  const [isPublic, setIsPublic] = useState(false);
  const [publicSlug, setPublicSlug] = useState("");
  const [tab, setTab] = useState(0);
  const [html, setHtml] = useState("");
  const [zoom, setZoom] = useState(0.62);
  const [previewing, setPreviewing] = useState(false);
  const [previewError, setPreviewError] = useState(false);
  const [previewRevision, setPreviewRevision] = useState(0);
  const [previewOverflow, setPreviewOverflow] = useState(false);
  const [pages, setPages] = useState(1);
  const [page, setPage] = useState(1);
  const [saveState, setSaveState] = useState<SaveState>("idle");
  const [exporting, setExporting] = useState(false);
  const [publishing, setPublishing] = useState(false);
  const [versions, setVersions] = useState<CvVariantVersionInfo[]>([]);
  const [loadError, setLoadError] = useState<string | null>(null);
  const [mobilePane, setMobilePane] = useState<"edit" | "preview">("edit");
  const [historyRevision, setHistoryRevision] = useState(0);

  const saveTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
  const saveRevision = useRef(0);
  const saveQueue = useRef<Promise<boolean>>(Promise.resolve(true));
  const latestSettings = useRef<CvVariantSettings | null>(null);
  const latestName = useRef("");
  const previewTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
  const previewRequest = useRef(0);
  const iframeRef = useRef<HTMLIFrameElement | null>(null);
  const scrollRef = useRef<HTMLDivElement | null>(null);
  const blockerPromptOpen = useRef(false);
  const undoStack = useRef<CvVariantSettings[]>([]);
  const redoStack = useRef<CvVariantSettings[]>([]);
  const pageMetrics = useMemo(() => getCvPageMetrics(settings?.pageSize), [settings?.pageSize]);

  useEffect(() => {
    let alive = true;
    (async () => {
      try {
        const [variant, themeList, outlineData] = await Promise.all([
          cvBuilderApi.get(variantId),
          cvBuilderApi.themes(),
          cvBuilderApi.outline().catch(() => null),
        ]);
        if (!alive) return;
        applyVariant(variant);
        setThemes(themeList);
        setOutline(outlineData);
      } catch (err) {
        if (alive) setLoadError(getApiErrorMessage(err, "Could not open this CV."));
      }
    })();
    return () => {
      alive = false;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [variantId]);

  const applyVariant = (variant: CvVariant) => {
    if (saveTimer.current) clearTimeout(saveTimer.current);
    saveRevision.current += 1;
    setName(variant.name);
    setSettings(variant.settings);
    latestName.current = variant.name;
    latestSettings.current = variant.settings;
    setIsPublic(variant.isPublic);
    setPublicSlug(variant.publicSlug);
    setSaveState("saved");
    undoStack.current = [];
    redoStack.current = [];
    setHistoryRevision((value) => value + 1);
  };

  // Debounced live preview.
  useEffect(() => {
    if (!settings) return;
    const request = ++previewRequest.current;
    setPreviewing(true);
    if (previewTimer.current) clearTimeout(previewTimer.current);
    previewTimer.current = setTimeout(async () => {
      try {
        const render = await cvBuilderApi.previewSettings(settings);
        if (previewRequest.current !== request) return;
        setHtml(render.html);
        setPreviewError(false);
      } catch {
        if (previewRequest.current !== request) return;
        setPreviewError(true);
      } finally {
        if (previewRequest.current === request) setPreviewing(false);
      }
    }, 300);
    return () => {
      if (previewTimer.current) clearTimeout(previewTimer.current);
    };
  }, [settings, previewRevision]);

  const performSave = useCallback(async (next: CvVariantSettings, nextName: string, revision: number) => {
    const save = async () => {
      if (saveRevision.current === revision) setSaveState("saving");
      try {
        await cvBuilderApi.save(variantId, { name: nextName, settings: next, source: "autosave" });
        if (saveRevision.current === revision) setSaveState("saved");
        return true;
      } catch {
        if (saveRevision.current === revision) setSaveState("error");
        return false;
      }
    };
    const queued = saveQueue.current.then(save, save);
    saveQueue.current = queued;
    return queued;
  }, [variantId]);

  const scheduleSave = useCallback(
    (next: CvVariantSettings, nextName = latestName.current) => {
      latestSettings.current = next;
      latestName.current = nextName;
      const revision = ++saveRevision.current;
      setSaveState("unsaved");
      if (saveTimer.current) clearTimeout(saveTimer.current);
      if (!nextName.trim()) return;
      saveTimer.current = setTimeout(async () => {
        await performSave(next, nextName, revision);
      }, 800);
    },
    [performSave],
  );

  const retrySave = useCallback(async () => {
    if (!latestSettings.current || !latestName.current.trim()) return false;
    if (saveTimer.current) clearTimeout(saveTimer.current);
    const revision = ++saveRevision.current;
    return performSave(latestSettings.current, latestName.current, revision);
  }, [performSave]);

  const update = (patch: Partial<CvVariantSettings>) => {
    setSettings((prev) => {
      if (!prev) return prev;
      const next = { ...prev, ...patch };
      undoStack.current = [...undoStack.current.slice(-59), prev];
      redoStack.current = [];
      setHistoryRevision((value) => value + 1);
      scheduleSave(next);
      return next;
    });
  };

  const undo = () => {
    setSettings((current) => {
      const previous = undoStack.current.pop();
      if (!current || !previous) return current;
      redoStack.current.push(current);
      setHistoryRevision((value) => value + 1);
      scheduleSave(previous);
      return previous;
    });
  };

  const redo = () => {
    setSettings((current) => {
      const next = redoStack.current.pop();
      if (!current || !next) return current;
      undoStack.current.push(current);
      setHistoryRevision((value) => value + 1);
      scheduleSave(next);
      return next;
    });
  };

  const renameVariant = (value: string) => {
    setName(value);
    latestName.current = value;
    if (settings) scheduleSave(settings, value);
  };

  const hasUnsavedChanges = saveState === "unsaved" || saveState === "saving" || saveState === "error";
  const blocker = useBlocker(hasUnsavedChanges);

  useEffect(() => {
    if (blocker.state !== "blocked") {
      blockerPromptOpen.current = false;
      return;
    }
    if (blockerPromptOpen.current) return;
    blockerPromptOpen.current = true;
    void confirmAction("This CV has unsaved changes. Leave and discard them?", {
      title: "Discard unsaved CV changes?",
      confirmLabel: "Discard and leave",
      destructive: true,
    }).then((confirmed) => {
      if (confirmed) blocker.proceed();
      else blocker.reset();
    });
  }, [blocker, confirmAction]);

  useEffect(() => {
    const warnBeforeUnload = (event: BeforeUnloadEvent) => {
      if (!hasUnsavedChanges) return;
      event.preventDefault();
      event.returnValue = "";
    };
    window.addEventListener("beforeunload", warnBeforeUnload);
    return () => window.removeEventListener("beforeunload", warnBeforeUnload);
  }, [hasUnsavedChanges]);

  useEffect(() => () => {
    if (saveTimer.current) clearTimeout(saveTimer.current);
  }, []);

  const togglePublic = async () => {
    if (hasUnsavedChanges && !(await retrySave())) {
      toast("Save the current CV before changing its public link.", "error");
      return;
    }
    setPublishing(true);
    try {
      const updated = await cvBuilderApi.setPublic(variantId, !isPublic);
      setIsPublic(updated.isPublic);
      setPublicSlug(updated.publicSlug);
      toast(updated.isPublic ? "CV is now public." : "CV is now private.", "success");
    } catch (err) {
      toast(getApiErrorMessage(err, "Could not change visibility."), "error");
    } finally {
      setPublishing(false);
    }
  };

  const copyPublicLink = () => {
    navigator.clipboard?.writeText(`${window.location.origin}/cv/${publicSlug}`);
    toast("Public link copied.", "success");
  };

  const exportPdf = async () => {
    if (hasUnsavedChanges && !(await retrySave())) {
      toast("Save the current CV before exporting it.", "error");
      return;
    }
    setExporting(true);
    try {
      const res = await api.post(cvBuilderApi.exportPdfUrl(variantId), {}, { responseType: "blob" });
      const url = URL.createObjectURL(res.data as Blob);
      const a = document.createElement("a");
      a.href = url;
      a.download = `${name || "cv"}.pdf`;
      a.click();
      URL.revokeObjectURL(url);
    } catch (err) {
      toast(getApiErrorMessage(err, "PDF export failed."), "error");
    } finally {
      setExporting(false);
    }
  };

  const duplicateVariant = async () => {
    if (hasUnsavedChanges && !(await retrySave())) {
      toast("Save the current CV before duplicating it.", "error");
      return;
    }
    try {
      const copy = await cvBuilderApi.duplicate(variantId, `${name || "Untitled CV"} copy`);
      navigate(`/career/builder/${copy.id}`);
      toast("CV duplicated. You are editing the copy.", "success");
    } catch (err) {
      toast(getApiErrorMessage(err, "Could not duplicate this CV."), "error");
    }
  };

  const loadVersions = async () => {
    try {
      setVersions(await cvBuilderApi.versions(variantId));
    } catch (err) {
      toast(getApiErrorMessage(err, "Could not load history."), "error");
    }
  };

  const restore = async (version: number) => {
    if (!(await confirmAction(`Restore version ${version}? Your current saved CV remains in the version history.`, {
      title: `Restore version ${version}`,
      confirmLabel: "Restore version",
    }))) return;
    if (hasUnsavedChanges && !(await retrySave())) {
      toast("Save the current CV before restoring an older version.", "error");
      return;
    }
    try {
      const updated = await cvBuilderApi.restore(variantId, version);
      applyVariant(updated);
      await loadVersions();
      toast(`Restored version ${version}.`, "success");
    } catch (err) {
      toast(getApiErrorMessage(err, "Restore failed."), "error");
    }
  };

  // Measure rendered content to drive page count + break indicators.
  const onIframeLoad = () => {
    const win = iframeRef.current?.contentWindow;
    const measure = () => {
      try {
        const doc = iframeRef.current?.contentDocument;
        const h = Math.max(
          pageMetrics.heightPx,
          doc?.body?.scrollHeight ?? 0,
          doc?.documentElement?.scrollHeight ?? 0,
        );
        const pageCount = getCvPageCount(h, pageMetrics.heightPx);
        setPages(pageCount);
        setPage((current) => Math.min(current, pageCount));
        const viewportWidth = doc?.documentElement?.clientWidth ?? pageMetrics.widthPx;
        const contentWidth = Math.max(doc?.body?.scrollWidth ?? 0, doc?.documentElement?.scrollWidth ?? 0);
        setPreviewOverflow(contentWidth > viewportWidth + 2);
      } catch {
        setPages(1);
        setPreviewOverflow(false);
      }
    };
    if (win) win.requestAnimationFrame(() => win.requestAnimationFrame(measure));
    else measure();
  };

  const goToPage = (p: number) => {
    const clamped = Math.min(Math.max(1, p), pages);
    setPage(clamped);
    const target = scrollRef.current?.querySelector<HTMLElement>(`[data-cv-page="${clamped}"]`);
    if (target && scrollRef.current) scrollRef.current.scrollTo({ top: Math.max(0, target.offsetTop - 12), behavior: "smooth" });
  };

  const fitPreview = () => {
    const availableWidth = Math.max(1, (scrollRef.current?.clientWidth ?? pageMetrics.widthPx) - 24);
    setZoom(Math.min(1, Math.max(MIN_PREVIEW_ZOOM, availableWidth / pageMetrics.widthPx)));
  };

  if (loadError) {
    return (
      <Box sx={{ p: 3 }}>
        <Button startIcon={<ArrowBackIcon />} onClick={() => navigate("/career/builder")}>Back to CVs</Button>
        <Alert severity="error" sx={{ mt: 2 }}>{loadError}</Alert>
      </Box>
    );
  }
  if (!settings) return <EditorSkeleton />;

  const canUndo = historyRevision >= 0 && undoStack.current.length > 0;
  const canRedo = historyRevision >= 0 && redoStack.current.length > 0;

  return (
    <Stack spacing={1.5} sx={{ minWidth: 0 }}>
      <Paper component="header" sx={{ px: { xs: 1, sm: 1.5 }, py: 1, borderRadius: 3, border: "1px solid", borderColor: "divider" }}>
        <Stack direction="row" alignItems="center" gap={1} flexWrap="wrap">
          <Tooltip title="Back to CVs"><IconButton size="small" aria-label="Back to CVs" onClick={() => navigate("/career/builder")}><ArrowBackIcon fontSize="small" /></IconButton></Tooltip>
          <TextField variant="standard" value={name} onChange={(e) => renameVariant(e.target.value)}
            error={!name.trim()} helperText={!name.trim() ? "Enter a name before saving." : undefined}
            sx={{ minWidth: { xs: 150, sm: 220 }, flex: "1 1 240px", maxWidth: 420 }}
            slotProps={{ input: { style: { fontWeight: 800, fontSize: "1.05rem" } }, htmlInput: { "aria-label": "CV name" } }} />
          <SaveBadge state={saveState} canRetry={!!name.trim()} onRetry={() => void retrySave()} />
          <Divider orientation="vertical" flexItem sx={{ display: { xs: "none", sm: "block" } }} />
          <Tooltip title="Undo"><span><IconButton size="small" aria-label="Undo" disabled={!canUndo} onClick={undo}><UndoIcon fontSize="small" /></IconButton></span></Tooltip>
          <Tooltip title="Redo"><span><IconButton size="small" aria-label="Redo" disabled={!canRedo} onClick={redo}><RedoIcon fontSize="small" /></IconButton></span></Tooltip>
          {compactEditor && <Stack direction="row" sx={{ border: "1px solid", borderColor: "divider", borderRadius: 2, p: 0.25 }}>
            <Button size="small" variant={mobilePane === "edit" ? "contained" : "text"} onClick={() => setMobilePane("edit")}>Edit</Button>
            <Button size="small" variant={mobilePane === "preview" ? "contained" : "text"} onClick={() => setMobilePane("preview")}>Preview</Button>
          </Stack>}
          <Box sx={{ flex: { sm: 1 } }} />
          <Button size="small" variant="text" startIcon={<ContentCopyIcon />} disabled={!name.trim()} onClick={() => void duplicateVariant()}>Duplicate</Button>
          <Button size="small" variant={isPublic ? "contained" : "outlined"} startIcon={<PublicIcon />} disabled={publishing || exporting || !name.trim()} onClick={togglePublic}>
            {publishing ? "Updating…" : isPublic ? "Public" : "Private"}
          </Button>
          {isPublic && <Button size="small" startIcon={<ContentCopyIcon />} onClick={copyPublicLink}>Copy link</Button>}
          <Button size="small" variant="contained" startIcon={<PictureAsPdfIcon />} disabled={exporting || publishing || !name.trim()} onClick={exportPdf}>{exporting ? "Exporting…" : "Download PDF"}</Button>
        </Stack>
      </Paper>

      <Box sx={{ display: "grid", gridTemplateColumns: { xs: "minmax(0, 1fr)", md: "minmax(360px, 480px) minmax(0, 1fr)" }, gap: 1.5, alignItems: "start" }}>
      <Paper sx={{ display: compactEditor && mobilePane !== "edit" ? "none" : "block", p: 2, borderRadius: 3, position: { md: "sticky" }, top: 12, minWidth: 0, maxHeight: { md: "calc(100vh - 104px)" }, overflowY: { md: "auto" }, border: "1px solid", borderColor: "divider" }}>

        <Tabs value={tab} onChange={(_, v) => { setTab(v); if (v === 4) loadVersions(); }} variant="scrollable" scrollButtons="auto" allowScrollButtonsMobile sx={{ mb: 1.5, minHeight: 38 }}>
          <Tab label="Content" />
          <Tab label="Template" />
          <Tab label="Design" />
          <Tab label="Layout" />
          <Tab label="History" />
        </Tabs>

        {tab === 0 && <ContentTab settings={settings} update={update} outline={outline} />}
        {tab === 1 && <CustomizeTab mode="template" settings={settings} update={update} themes={themes} />}
        {tab === 2 && <CustomizeTab mode="design" settings={settings} update={update} themes={themes} />}
        {tab === 3 && <CustomizeTab mode="layout" settings={settings} update={update} themes={themes} />}
        {tab === 4 && <HistoryTab versions={versions} onRestore={restore} />}
      </Paper>

      <Paper sx={{ display: compactEditor && mobilePane !== "preview" ? "none" : "block", p: 1.5, borderRadius: 3, bgcolor: "action.hover", border: "1px solid", borderColor: "divider", minWidth: 0 }}>
        <Stack direction="row" alignItems="center" spacing={1} sx={{ mb: 1, px: 1, flexWrap: "wrap" }}>
          <Typography variant="caption" sx={{ fontWeight: 700 }}>Live preview</Typography>
          {previewing && <Chip size="small" label="updating…" variant="outlined" />}
          {previewError && <Chip size="small" label="Preview unavailable" color="error" variant="outlined" />}
          {previewError && <Button size="small" onClick={() => setPreviewRevision((revision) => revision + 1)}>Retry preview</Button>}
          <Box sx={{ flex: 1 }} />
          {pages >= 3 && <Chip size="small" color="warning" variant="outlined" label={`${pages}-page CV`} />}
          <Stack direction="row" alignItems="center" spacing={0.5}>
            <Button size="small" disabled={page <= 1} onClick={() => goToPage(page - 1)}>Prev</Button>
            <Typography variant="caption">Page {page} of {pages}</Typography>
            <Button size="small" disabled={page >= pages} onClick={() => goToPage(page + 1)}>Next</Button>
          </Stack>
          <Divider orientation="vertical" flexItem sx={{ mx: 0.5 }} />
          <IconButton size="small" aria-label="Zoom out" onClick={() => setZoom((z) => Math.max(MIN_PREVIEW_ZOOM, +(z - 0.1).toFixed(2)))}><ZoomOutIcon fontSize="small" /></IconButton>
          <Slider size="small" value={zoom} min={MIN_PREVIEW_ZOOM} max={1} step={0.02} onChange={(_, v) => setZoom(v as number)} sx={{ width: 90 }} aria-label="Zoom" />
          <IconButton size="small" aria-label="Zoom in" onClick={() => setZoom((z) => Math.min(1, +(z + 0.1).toFixed(2)))}><ZoomInIcon fontSize="small" /></IconButton>
          <Typography variant="caption" sx={{ minWidth: 34, textAlign: "right" }}>{Math.round(zoom * 100)}%</Typography>
          <Button size="small" onClick={fitPreview}>Fit</Button>
        </Stack>
        {previewOverflow && <Alert severity="warning" sx={{ mb: 1 }}>The preview reported horizontal overflow. Shorten an unbroken value or retry after the latest render.</Alert>}
        {pages >= 3 && <Alert severity="info" sx={{ mb: 1 }}>This CV is {pages} pages. Content remains readable, but consider hiding less relevant entries for a more focused application.</Alert>}
        <iframe ref={iframeRef} title="CV preview" srcDoc={previewDocumentHtml(html, pageMetrics.heightPx)} sandbox="allow-same-origin allow-scripts" onLoad={onIframeLoad} aria-hidden tabIndex={-1} style={{ position: "absolute", left: "-10000px", top: 0, width: `${pageMetrics.widthMm}mm`, height: `${pageMetrics.heightMm}mm`, visibility: "hidden", pointerEvents: "none" }} />
        <Box ref={scrollRef} sx={{ overflow: "auto", maxHeight: "82vh", p: { xs: 1, sm: 2 }, bgcolor: "#dfe4ea", borderRadius: 2 }}>
          <Stack spacing={3} alignItems="center">
            {Array.from({ length: pages }).map((_, index) => (
              <Box key={index} data-cv-page={index + 1} sx={{ flex: "0 0 auto" }}>
                <Typography variant="caption" sx={{ display: "block", mb: 0.75, color: "#475569", fontWeight: 700 }}>Page {index + 1}</Typography>
                <Box sx={{ position: "relative", width: `calc(${pageMetrics.widthMm}mm * ${zoom})`, height: `calc(${pageMetrics.heightMm}mm * ${zoom})`, bgcolor: "#fff", boxShadow: "0 10px 28px rgba(15,23,42,.18)", overflow: "hidden" }}>
                  <iframe
                    title={`CV preview page ${index + 1}`}
                    srcDoc={previewDocumentHtml(html, pageMetrics.heightPx, index)}
                    sandbox="allow-same-origin allow-scripts"
                    style={{ width: `${pageMetrics.widthMm}mm`, height: `${pageMetrics.heightMm}mm`, border: "none", transform: `scale(${zoom})`, transformOrigin: "top left", background: "#fff", display: "block" }}
                  />
                </Box>
              </Box>
            ))}
          </Stack>
        </Box>
      </Paper>
      </Box>
    </Stack>
  );
}

function EditorSkeleton() {
  return (
    <Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", md: "minmax(360px, 460px) 1fr" }, gap: 2 }}>
      <Paper sx={{ p: 2, borderRadius: 4 }}>
        <Skeleton width="60%" height={32} />
        <Skeleton width="100%" height={40} sx={{ mt: 1 }} />
        <Skeleton variant="rounded" height={44} sx={{ mt: 2 }} />
        {[0, 1, 2, 3, 4].map((i) => <Skeleton key={i} variant="rounded" height={40} sx={{ mt: 1 }} />)}
      </Paper>
      <Paper sx={{ p: 1.5, borderRadius: 4, bgcolor: "action.hover", border: "1px solid", borderColor: "divider", display: "flex", justifyContent: "center" }}>
        <Skeleton variant="rounded" width="70%" height={620} />
      </Paper>
    </Box>
  );
}

function SaveBadge({ state, canRetry, onRetry }: { state: SaveState; canRetry: boolean; onRetry: () => void }) {
  const map: Record<SaveState, { label: string; color: "default" | "warning" | "success" | "error" }> = {
    idle: { label: "", color: "default" },
    unsaved: { label: "Unsaved", color: "warning" },
    saving: { label: "Saving…", color: "warning" },
    saved: { label: "Saved", color: "success" },
    error: { label: "Save failed", color: "error" },
  };
  const m = map[state];
  if (!m.label) return null;
  return (
    <Stack spacing={0.5} alignItems="flex-end" aria-live="polite">
      <Chip size="small" label={m.label} color={m.color} variant="outlined" />
      {(state === "unsaved" || state === "error") ? (
        <Button size="small" disabled={!canRetry} onClick={onRetry}>Save now</Button>
      ) : null}
    </Stack>
  );
}

// ---------- Content tab ----------

function ContentTab({ settings, update, outline }: {
  settings: CvVariantSettings;
  update: (p: Partial<CvVariantSettings>) => void;
  outline: CvOutline | null;
}) {
  const { confirmAction } = useDialogActions();
  const [sectionToAdd, setSectionToAdd] = useState("free-text");
  // Full section list = configured order (once touched) else default, always including every known key.
  const sectionRows: CvSectionSetting[] = useMemo(() => {
    const base: CvSectionSetting[] = settings.sections.length ? [...settings.sections] : DEFAULT_SECTION_ORDER.map((key) => ({ key }));
    const have = new Set(base.map((s) => s.key));
    for (const key of DEFAULT_SECTION_ORDER) if (!have.has(key)) base.push({ key });
    for (const section of outline?.sections ?? []) if (!have.has(section.key)) {
      base.push({ key: section.key, title: section.title });
      have.add(section.key);
    }
    for (const custom of settings.customSections) {
      const key = `custom:${custom.key}`;
      if (!have.has(key)) {
        base.push({ key, hidden: custom.hidden });
        have.add(key);
      }
    }
    return base;
  }, [outline?.sections, settings.customSections, settings.sections]);

  const writeSections = (rows: CvSectionSetting[]) => update({ sections: rows });
  const sectionDrag = useDragReorder((from, to) => writeSections(moveItem(sectionRows, from, to)));

  const patchSection = (key: string, patch: Partial<CvSectionSetting>) =>
    writeSections(sectionRows.map((r) => (r.key === key ? { ...r, ...patch } : r)));

  const outlineByKey = useMemo(() => {
    const m: Record<string, CvOutlineSection> = {};
    outline?.sections.forEach((s) => { m[s.key] = s; });
    return m;
  }, [outline]);

  const customBySectionKey = useMemo(() => Object.fromEntries(
    settings.customSections.map((section) => [`custom:${section.key}`, section]),
  ), [settings.customSections]);

  const addCustom = () => {
    const preset = CUSTOM_SECTION_PRESETS.find((item) => item.key === sectionToAdd);
    if (preset && settings.customSections.some((item) => item.presetKey === preset.key)) return;
    const key = `c${Date.now().toString(36)}`;
    update({
      customSections: [...settings.customSections, {
        key,
        title: preset?.title ?? "New section",
        items: [],
        presetKey: preset?.key ?? null,
        contentType: preset?.contentType ?? "paragraphs",
      }],
      sections: [...sectionRows, { key: `custom:${key}` }],
    });
  };
  const updateCustom = (key: string, patch: Partial<CvCustomSectionSetting>) =>
    update({ customSections: settings.customSections.map((c) => (c.key === key ? { ...c, ...patch } : c)) });
  const removeCustom = async (section: CvCustomSectionSetting) => {
    if (!(await confirmAction(`Delete the custom section "${section.title || "Untitled"}" and all of its entries?`, {
      title: "Delete custom section",
      confirmLabel: "Delete section",
      destructive: true,
    }))) return;
    update({
      customSections: settings.customSections.filter((c) => c.key !== section.key),
      sections: sectionRows.filter((row) => row.key !== `custom:${section.key}`),
    });
  };
  return (
    <Stack spacing={2}>
      <Alert severity="info" action={<Button component={RouterLink} to="/career/profile" size="small">Edit master profile</Button>}>
        Contact details and career history are shared from your master profile. CV-specific headings, wording, order and visibility stay in this version.
      </Alert>
      <TextField label="Headline override" size="small" fullWidth value={settings.headline ?? ""}
        onChange={(e) => update({ headline: e.target.value || null })}
        helperText="Blank uses the headline from your master profile." />

      <Box>
        <Typography variant="subtitle2" sx={{ fontWeight: 800, mb: 0.5 }}>Sections</Typography>
        <Typography variant="caption" color="text.secondary">Drag to reorder, or use the arrows. Content comes from your master profile.</Typography>
        <Stack spacing={0.5} sx={{ mt: 1 }}>
          {sectionRows.map((row, i) => (
            <SectionRow
              key={row.key}
              row={row}
              index={i}
              total={sectionRows.length}
              dragProps={sectionDrag.getItemProps(i)}
              dragging={sectionDrag.dragIndex === i}
              over={sectionDrag.overIndex === i && sectionDrag.dragIndex !== i}
              outlineSection={outlineByKey[row.key]}
              customSection={customBySectionKey[row.key]}
              settings={settings}
              onMove={(d) => writeSections(moveItem(sectionRows, i, i + d))}
              onPatch={(p) => patchSection(row.key, p)}
              onRenameCustom={(title) => {
                const custom = customBySectionKey[row.key];
                if (custom) updateCustom(custom.key, { title });
              }}
              onUpdateCustom={(patch) => {
                const custom = customBySectionKey[row.key];
                if (custom) updateCustom(custom.key, patch);
              }}
              onRemoveCustom={() => {
                const custom = customBySectionKey[row.key];
                if (custom) void removeCustom(custom);
              }}
              onUpdateSettings={update}
            />
          ))}
        </Stack>
      </Box>

      <Paper variant="outlined" sx={{ p: 1.5, borderStyle: "dashed" }}>
        <Typography variant="subtitle2" sx={{ fontWeight: 800 }}>Add a section</Typography>
        <Typography variant="caption" color="text.secondary">Preset sections use the same editable, reorderable card as every other section.</Typography>
        <Stack direction={{ xs: "column", sm: "row" }} spacing={1} sx={{ mt: 1 }}>
          <TextField select size="small" fullWidth label="Section type" value={sectionToAdd} onChange={(event) => setSectionToAdd(event.target.value)}>
            <MenuItem value="free-text">Custom free-text section</MenuItem>
            {CUSTOM_SECTION_PRESETS.map((preset) => <MenuItem key={preset.key} value={preset.key} disabled={settings.customSections.some((item) => item.presetKey === preset.key)}>{preset.title}</MenuItem>)}
          </TextField>
          <Button variant="contained" startIcon={<AddIcon />} onClick={addCustom}>Add section</Button>
        </Stack>
      </Paper>
    </Stack>
  );
}

function SectionRow({
  row, index, total, dragProps, dragging, over, outlineSection, customSection, settings, onMove, onPatch, onRenameCustom, onUpdateCustom, onRemoveCustom, onUpdateSettings,
}: {
  row: CvSectionSetting;
  index: number;
  total: number;
  dragProps: React.HTMLAttributes<HTMLDivElement> & { draggable: boolean };
  dragging: boolean;
  over: boolean;
  outlineSection?: CvOutlineSection;
  customSection?: CvCustomSectionSetting;
  settings: CvVariantSettings;
  onMove: (delta: number) => void;
  onPatch: (patch: Partial<CvSectionSetting>) => void;
  onRenameCustom: (title: string) => void;
  onUpdateCustom: (patch: Partial<CvCustomSectionSetting>) => void;
  onRemoveCustom: () => void;
  onUpdateSettings: (p: Partial<CvVariantSettings>) => void;
}) {
  const { confirmAction } = useDialogActions();
  const [expanded, setExpanded] = useState(false);
  const sectionLabel = customSection?.title ?? row.title ?? SECTION_LABELS[row.key] ?? row.key;
  const sourceItems = outlineSection?.kind === "tags" ? outlineSection.tags : outlineSection?.bullets ?? [];
  const editableItems = row.items ?? sourceItems;
  const customText = customSection?.items.join("\n\n") ?? "";

  return (
    <Paper
      variant="outlined"
      {...dragProps}
      sx={{
        opacity: dragging ? 0.4 : row.hidden ? 0.58 : 1,
        borderTop: over ? "2px solid" : undefined, borderTopColor: over ? "primary.main" : undefined,
        transition: "opacity 150ms ease, border-color 120ms ease, box-shadow 150ms ease",
        overflow: "hidden", "&:hover": { boxShadow: 1 },
      }}
    >
      <Stack direction="row" alignItems="center" gap={0.5} sx={{ px: 0.75, py: 0.5 }}>
        <DragIndicatorIcon sx={{ fontSize: 18, color: "text.disabled", cursor: "grab" }} aria-hidden />
        <Stack>
          <IconButton size="small" aria-label={`Move ${sectionLabel} section up`} disabled={index === 0} onClick={() => onMove(-1)}><ArrowUpwardIcon sx={{ fontSize: 15 }} /></IconButton>
          <IconButton size="small" aria-label={`Move ${sectionLabel} section down`} disabled={index === total - 1} onClick={() => onMove(1)}><ArrowDownwardIcon sx={{ fontSize: 15 }} /></IconButton>
        </Stack>
        <TextField variant="standard" fullWidth value={sectionLabel}
          onChange={(e) => customSection ? onRenameCustom(e.target.value) : onPatch({ title: e.target.value })}
          slotProps={{ input: { disableUnderline: true }, htmlInput: { "aria-label": `Section name for ${row.key}` } }} />
        {customSection && <Chip size="small" label="Custom" variant="outlined" sx={{ height: 20 }} />}
        <IconButton size="small" aria-label={`${row.hidden ? "Show" : "Hide"} ${sectionLabel} section`} onClick={() => onPatch({ hidden: !row.hidden })}>
          {row.hidden ? <VisibilityOffIcon fontSize="small" /> : <VisibilityIcon fontSize="small" />}
        </IconButton>
        <IconButton size="small" aria-label={`Remove ${sectionLabel} section from this CV`} onClick={() => {
          if (customSection) onRemoveCustom();
          else void confirmAction(`Remove ${sectionLabel} from this CV? Your master career data will be preserved.`, { title: "Remove section", confirmLabel: "Remove from CV", destructive: true }).then((confirmed) => { if (confirmed) onPatch({ hidden: true }); });
        }}><DeleteOutlineIcon fontSize="small" /></IconButton>
        <IconButton size="small" aria-label={`${expanded ? "Collapse" : "Expand"} ${sectionLabel}`} aria-expanded={expanded} onClick={() => setExpanded((value) => !value)}
          sx={{ transform: expanded ? "rotate(180deg)" : "none", transition: "transform 150ms" }}>
          <ExpandMoreIcon fontSize="small" />
        </IconButton>
      </Stack>
      <Collapse in={expanded} unmountOnExit>
        <Box sx={{ px: 1.5, py: 1.25, borderTop: "1px solid", borderColor: "divider", bgcolor: "action.hover" }}>
          {customSection ? (
            <Stack spacing={1}>
              <TextField select size="small" label="Content format" value={customSection.contentType ?? "bullets"} onChange={(event) => onUpdateCustom({ contentType: event.target.value as CvCustomSectionSetting["contentType"] })}>
                <MenuItem value="paragraphs">Paragraphs</MenuItem><MenuItem value="bullets">Bullet list</MenuItem><MenuItem value="entries">Multiple entries</MenuItem>
              </TextField>
              {customSection.contentType === "entries" ? <Stack spacing={1}>
                {customSection.items.map((item, itemIndex) => <Stack key={`${customSection.key}-${itemIndex}`} direction="row" alignItems="flex-start" spacing={0.5}>
                  <TextField fullWidth size="small" multiline minRows={2} label={`Entry ${itemIndex + 1}`} value={item} onChange={(event) => onUpdateCustom({ items: customSection.items.map((current, index) => index === itemIndex ? event.target.value : current) })} />
                  <IconButton size="small" aria-label={`Move custom entry ${itemIndex + 1} up`} disabled={itemIndex === 0} onClick={() => onUpdateCustom({ items: moveItem(customSection.items, itemIndex, itemIndex - 1) })}><ArrowUpwardIcon fontSize="small" /></IconButton>
                  <IconButton size="small" aria-label={`Move custom entry ${itemIndex + 1} down`} disabled={itemIndex === customSection.items.length - 1} onClick={() => onUpdateCustom({ items: moveItem(customSection.items, itemIndex, itemIndex + 1) })}><ArrowDownwardIcon fontSize="small" /></IconButton>
                  <IconButton size="small" aria-label={`Delete custom entry ${itemIndex + 1}`} onClick={() => void confirmAction("Delete this custom section entry?", { title: "Delete custom entry", confirmLabel: "Delete entry", destructive: true }).then((confirmed) => { if (confirmed) onUpdateCustom({ items: customSection.items.filter((_, index) => index !== itemIndex) }); })}><DeleteOutlineIcon fontSize="small" /></IconButton>
                </Stack>)}
                <Button size="small" startIcon={<AddIcon />} sx={{ alignSelf: "flex-start" }} onClick={() => onUpdateCustom({ items: [...customSection.items, ""] })}>Add entry</Button>
              </Stack> : <RichTextField label={customSection.contentType === "paragraphs" ? "Paragraphs (separate with a blank line)" : "Bullet points (one per line)"} minRows={4} value={customSection.contentType === "paragraphs" ? customText : customSection.items.join("\n")}
                onChange={(value) => onUpdateCustom({ items: customSection.contentType === "paragraphs" ? value.split(/\n\s*\n/) : value.split("\n") })} />}
              <AiSectionAssistant sectionName={sectionLabel} text={customText} context="This is a custom CV section." onApply={(value) => onUpdateCustom({ items: customSection.contentType === "paragraphs" ? value.split(/\n\s*\n/) : value.split("\n") })} />
            </Stack>
          ) : outlineSection?.kind === "entries" ? (
            <EntryEditor section={outlineSection} row={row} settings={settings} onPatch={onPatch} onUpdateSettings={onUpdateSettings} />
          ) : (
            <Stack spacing={1}>
              <RichTextField label={outlineSection?.kind === "tags" ? "Items (one per line)" : "Content (one item per line)"} minRows={3} value={editableItems.join("\n")} onChange={(value) => onPatch({ items: value.split("\n") })} />
              {row.items && <Button size="small" sx={{ alignSelf: "flex-start" }} onClick={() => onPatch({ items: undefined })}>Reset to master profile</Button>}
              <AiSectionAssistant sectionName={sectionLabel} text={editableItems.join("\n")} context="Return one item per line when suggesting a list." onApply={(value) => onPatch({ items: value.split("\n") })} />
            </Stack>
          )}
        </Box>
      </Collapse>
    </Paper>
  );
}

function EntryEditor({ section, row, settings, onPatch, onUpdateSettings }: {
  section: CvOutlineSection;
  row: CvSectionSetting;
  settings: CvVariantSettings;
  onPatch: (patch: Partial<CvSectionSetting>) => void;
  onUpdateSettings: (p: Partial<CvVariantSettings>) => void;
}) {
  // Entry order: itemOrder if set, else master order; always covering every entry key.
  const orderedKeys: string[] = useMemo(() => {
    const master = section.entries.map((e) => e.key ?? "").filter(Boolean);
    if (!row.itemOrder?.length) return master;
    const set = new Set(row.itemOrder);
    return [...row.itemOrder.filter((k) => master.includes(k)), ...master.filter((k) => !set.has(k))];
  }, [row.itemOrder, section.entries]);

  const entriesByKey = useMemo(() => {
    const m: Record<string, (typeof section.entries)[number]> = {};
    section.entries.forEach((e) => { if (e.key) m[e.key] = e; });
    return m;
  }, [section.entries]);

  const drag = useDragReorder((from, to) => onPatch({ itemOrder: moveItem(orderedKeys, from, to) }));

  const setOverride = (key: string, patch: Partial<CvItemOverride>) => {
    const overrides = { ...settings.overrides, [key]: { ...settings.overrides[key], ...patch } };
    onUpdateSettings({ overrides });
  };

  return (
    <Stack spacing={0.75}>
      {orderedKeys.map((key, i) => {
        const entry = entriesByKey[key];
        if (!entry) return null;
        const ov = settings.overrides[key] ?? {};
        const hidden = !!ov.hidden;
        const entryLabel = ov.title || entry.title || "Untitled";
        return (
          <Paper key={key} variant="outlined" {...drag.getItemProps(i)}
            sx={{
              p: 0.75, opacity: drag.dragIndex === i ? 0.4 : hidden ? 0.5 : 1, cursor: "grab",
              borderTop: drag.overIndex === i && drag.dragIndex !== i ? "2px solid" : undefined,
              borderTopColor: "primary.main", transition: "opacity 150ms ease",
            }}>
            <Stack direction="row" alignItems="center" gap={0.5}>
              <DragIndicatorIcon sx={{ fontSize: 16, color: "text.disabled" }} aria-hidden />
              <Typography variant="body2" sx={{ fontWeight: 700, flex: 1 }}>{ov.title || entry.title || "Untitled"}</Typography>
              <IconButton size="small" aria-label={`Move ${entryLabel} entry up`} disabled={i === 0} onClick={() => onPatch({ itemOrder: moveItem(orderedKeys, i, i - 1) })}><ArrowUpwardIcon sx={{ fontSize: 14 }} /></IconButton>
              <IconButton size="small" aria-label={`Move ${entryLabel} entry down`} disabled={i === orderedKeys.length - 1} onClick={() => onPatch({ itemOrder: moveItem(orderedKeys, i, i + 1) })}><ArrowDownwardIcon sx={{ fontSize: 14 }} /></IconButton>
              <IconButton size="small" aria-label={`${hidden ? "Show" : "Hide"} ${entryLabel} entry`} onClick={() => setOverride(key, { hidden: !hidden })}>
                {hidden ? <VisibilityOffIcon sx={{ fontSize: 16 }} /> : <VisibilityIcon sx={{ fontSize: 16 }} />}
              </IconButton>
            </Stack>
            {!hidden && (
              <Stack spacing={0.75} sx={{ mt: 0.75 }}>
                <TextField size="small" fullWidth label="Title" value={ov.title ?? entry.title ?? ""}
                  onChange={(e) => setOverride(key, { title: e.target.value })} />
                <TextField size="small" fullWidth label="Subtitle" value={ov.subtitle ?? entry.subtitle ?? ""}
                  onChange={(e) => setOverride(key, { subtitle: e.target.value })} />
                <RichTextField label="Bullet points (one per line)" minRows={2}
                  value={(ov.bullets ?? entry.bullets).join("\n")}
                  onChange={(v) => setOverride(key, { bullets: v.split("\n") })} />
                <AiSectionAssistant sectionName={`${section.title}: ${entryLabel}`} text={(ov.bullets ?? entry.bullets).join("\n")} context={`Entry title: ${ov.title ?? entry.title ?? ""}. Subtitle: ${ov.subtitle ?? entry.subtitle ?? ""}. Return one bullet per line.`} onApply={(value) => setOverride(key, { bullets: value.split("\n") })} />
                {ov.bullets && (
                  <Button size="small" onClick={() => setOverride(key, { bullets: undefined })}>Reset to master bullets</Button>
                )}
              </Stack>
            )}
          </Paper>
        );
      })}
    </Stack>
  );
}

// ---------- Customize tab ----------

function CustomizeTab({ mode, settings, update, themes }: {
  mode: "template" | "design" | "layout";
  settings: CvVariantSettings;
  update: (p: Partial<CvVariantSettings>) => void;
  themes: CvTheme[];
}) {
  const activeTheme = themes.find((theme) => theme.id === settings.themeId);
  const supports = (setting: string) => !activeTheme?.supportedSettings?.length || activeTheme.supportedSettings.includes(setting);
  const sidebarSections = settings.sidebarSections ?? ["contact", "skills", "languages"];
  const toggleSidebarSection = (key: string) => update({
    sidebarSections: sidebarSections.includes(key)
      ? sidebarSections.filter((item) => item !== key)
      : [...sidebarSections, key],
  });
  return (
    <Stack spacing={2}>
      {mode === "template" && <Box>
        <Typography variant="subtitle2" sx={{ fontWeight: 800, mb: 1 }}>Theme</Typography>
        <Typography variant="caption" color="text.secondary" sx={{ display: "block", mb: 1.25 }}>Templates change presentation only. Your content and hidden-section choices stay intact.</Typography>
        <Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 1 }}>
          {themes.map((t) => {
            const active = t.id === settings.themeId;
            const locked = t.available === false;
            return (
              <Paper key={t.id} variant="outlined" role="button" tabIndex={locked ? -1 : 0} aria-disabled={locked}
                onClick={() => { if (!locked) update({ themeId: t.id }); }}
                onKeyDown={(e) => { if (!locked && (e.key === "Enter" || e.key === " ")) { e.preventDefault(); update({ themeId: t.id }); } }}
                sx={{ p: 1, cursor: locked ? "not-allowed" : "pointer", opacity: locked ? 0.6 : 1, outline: "none", borderColor: active ? "primary.main" : undefined, borderWidth: active ? 2 : 1, "&:focus-visible": { boxShadow: 3 } }}>
                <Box sx={{ mb: 1 }}><CvTemplateThumbnail theme={t} height={110} /></Box>
                <Typography variant="body2" sx={{ fontWeight: 700 }}>{t.name}</Typography>
                <Typography variant="caption" color="text.secondary" sx={{ display: "block" }}>{t.category}</Typography>
                <Typography variant="caption" color="text.secondary" sx={{ display: "block", mt: 0.25, lineHeight: 1.3 }}>{t.description}</Typography>
                <Stack direction="row" spacing={0.5} sx={{ mt: 0.5 }}>
                  {t.atsFriendly && <Chip size="small" label="ATS-friendly" color="success" variant="outlined" sx={{ height: 18, fontSize: 10 }} />}
                  {t.requiresPro && <Chip size="small" label={locked ? "Pro" : "Pro unlocked"} color="secondary" variant="outlined" sx={{ height: 18, fontSize: 10 }} />}
                </Stack>
              </Paper>
            );
          })}
        </Box>
      </Box>}

      {mode === "design" && <>
        {supports("accent") && <><Typography variant="subtitle2" sx={{ fontWeight: 800 }}>Colour</Typography>
        <Stack direction="row" spacing={0.75} flexWrap="wrap" useFlexGap>
          {["#3157d5", "#0f766e", "#9f1239", "#7c3aed", "#b45309", "#334155"].map((color) => (
            <IconButton key={color} aria-label={`Use accent ${color}`} onClick={() => update({ accentColor: color })} sx={{ width: 34, height: 34, bgcolor: color, border: settings.accentColor === color ? "3px solid" : "1px solid", borderColor: settings.accentColor === color ? "text.primary" : "divider", "&:hover": { bgcolor: color } }} />
          ))}
          <Box component="label" sx={{ width: 34, height: 34, borderRadius: "50%", bgcolor: settings.accentColor ?? "#3157d5", border: "2px dashed", borderColor: "text.secondary", display: "grid", placeItems: "center", cursor: "pointer", overflow: "hidden", position: "relative" }}>
            <input type="color" aria-label="Custom accent colour" value={settings.accentColor ?? "#3157d5"} onChange={(e) => update({ accentColor: e.target.value })} style={{ position: "absolute", inset: 0, width: "100%", height: "100%", opacity: 0, cursor: "pointer" }} />
          </Box>
          {settings.accentColor && <Button size="small" onClick={() => update({ accentColor: null })}>Theme default</Button>}
        </Stack></>}

        {supports("typography") && <><Typography variant="subtitle2" sx={{ fontWeight: 800, mt: 0.5 }}>Typography</Typography>
        <Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 1 }}>
          <FormControl size="small" fullWidth><InputLabel>Heading font</InputLabel><Select label="Heading font" value={settings.headingFont ?? ""} onChange={(e) => update({ headingFont: e.target.value || null })}><MenuItem value="">Theme default</MenuItem>{FONTS.map((f, i) => <MenuItem key={f} value={f}>{FONT_LABELS[i]}</MenuItem>)}</Select></FormControl>
          <FormControl size="small" fullWidth><InputLabel>Body font</InputLabel><Select label="Body font" value={settings.bodyFont ?? ""} onChange={(e) => update({ bodyFont: e.target.value || null })}><MenuItem value="">Theme default</MenuItem>{FONTS.map((f, i) => <MenuItem key={f} value={f}>{FONT_LABELS[i]}</MenuItem>)}</Select></FormControl>
        </Box>
        <ControlSlider label="Body size" value={settings.baseFontSizePt ?? 10} min={7} max={13} step={0.25} suffix="pt" onChange={(value) => update({ baseFontSizePt: value })} />
        <ControlSlider label="Heading size" value={settings.headingSizePt ?? 12} min={9} max={20} step={0.5} suffix="pt" onChange={(value) => update({ headingSizePt: value })} />
        <ControlSlider label="Line height" value={settings.lineHeight ?? 1.42} min={1.1} max={1.8} step={0.02} onChange={(value) => update({ lineHeight: value })} /></>}
        {supports("heading") && <FormControl size="small" fullWidth><InputLabel>Heading treatment</InputLabel><Select label="Heading treatment" value={settings.headingStyle ?? ""} onChange={(e) => update({ headingStyle: (e.target.value || null) as CvVariantSettings["headingStyle"] })}><MenuItem value="">Template default</MenuItem><MenuItem value="caps-rule">Uppercase divider</MenuItem><MenuItem value="underline">Underline</MenuItem><MenuItem value="plain">Plain</MenuItem><MenuItem value="bar">Accent bar</MenuItem></Select></FormControl>}
        {supports("header") && <FormControl size="small" fullWidth><InputLabel>Header treatment</InputLabel><Select label="Header treatment" value={settings.headerStyle ?? ""} onChange={(e) => update({ headerStyle: (e.target.value || null) as CvVariantSettings["headerStyle"] })}><MenuItem value="">Template default</MenuItem><MenuItem value="plain">Plain</MenuItem><MenuItem value="band">Colour band</MenuItem><MenuItem value="centered">Centred</MenuItem><MenuItem value="kicker">Editorial</MenuItem></Select></FormControl>}
        {supports("skills") && <><FormControl size="small" fullWidth><InputLabel>Skills presentation</InputLabel><Select inputProps={{ "aria-label": "Skills presentation" }} label="Skills presentation" value={settings.skillsStyle ?? "tags"} onChange={(e) => update({ skillsStyle: e.target.value as CvVariantSettings["skillsStyle"] })}><MenuItem value="tags">Tags</MenuItem><MenuItem value="text">Compact text</MenuItem><MenuItem value="bullets">Bullet list</MenuItem><MenuItem value="grouped">Grouped skills</MenuItem></Select></FormControl>
        {settings.skillsStyle === "grouped" && <TextField label="Skill groups" helperText="One group per line, for example: Backend: C#, .NET, Python" multiline minRows={3} value={(settings.skillGroups ?? []).map((group) => `${group.name}: ${group.items.join(", ")}`).join("\n")} onChange={(event) => update({ skillGroups: event.target.value.split("\n").filter(Boolean).map((line) => { const [name, ...items] = line.split(":"); return { name: name.trim(), items: items.join(":").split(",").map((item) => item.trim()).filter(Boolean) }; }) })} />}</>}
      </>}

      {mode === "layout" && <>
        <Typography variant="subtitle2" sx={{ fontWeight: 800 }}>Document</Typography>
        <Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 1 }}>
          {supports("page") && <>
          <FormControl size="small" fullWidth><InputLabel>Page size</InputLabel><Select inputProps={{ "aria-label": "Page size" }} label="Page size" value={settings.pageSize ?? "a4"} onChange={(e) => update({ pageSize: e.target.value })}><MenuItem value="a4">A4</MenuItem><MenuItem value="letter">US Letter</MenuItem></Select></FormControl>
          <FormControl size="small" fullWidth><InputLabel>Density</InputLabel><Select inputProps={{ "aria-label": "Density" }} label="Density" value={settings.density ?? "balanced"} onChange={(e) => update({ density: e.target.value })}><MenuItem value="compact">Compact</MenuItem><MenuItem value="balanced">Balanced</MenuItem><MenuItem value="roomy">Roomy</MenuItem></Select></FormControl>
          </>}
          <FormControl size="small" fullWidth><InputLabel>Language</InputLabel><Select inputProps={{ "aria-label": "Language" }} label="Language" value={settings.language ?? "en"} onChange={(e) => update({ language: e.target.value })}><MenuItem value="en">English</MenuItem><MenuItem value="no">Norwegian</MenuItem></Select></FormControl>
          <FormControl size="small" fullWidth><InputLabel>Date format</InputLabel><Select inputProps={{ "aria-label": "Date format" }} label="Date format" value={settings.dateFormat ?? "short"} onChange={(e) => update({ dateFormat: e.target.value })}><MenuItem value="long">January 2020</MenuItem><MenuItem value="short">Jan 2020</MenuItem><MenuItem value="numeric">01/2020</MenuItem><MenuItem value="year">2020</MenuItem></Select></FormControl>
        </Box>
        {supports("layout") && <FormControl size="small" fullWidth><InputLabel>Columns</InputLabel><Select inputProps={{ "aria-label": "Columns" }} label="Columns" value={settings.layout ?? ""} onChange={(e) => update({ layout: (e.target.value || null) as CvVariantSettings["layout"] })}><MenuItem value="">Template default</MenuItem><MenuItem value="single">One column</MenuItem><MenuItem value="header-band">One column with header band</MenuItem><MenuItem value="sidebar-left">Left sidebar</MenuItem><MenuItem value="sidebar-right">Right sidebar</MenuItem></Select></FormControl>}
        {supports("spacing") && <>
        <ControlSlider label="Page margins" value={settings.pageMarginMm ?? 16} min={8} max={28} step={1} suffix="mm" onChange={(value) => update({ pageMarginMm: value })} />
        <ControlSlider label="Section spacing" value={settings.sectionGapMm ?? 6} min={2} max={14} step={0.5} suffix="mm" onChange={(value) => update({ sectionGapMm: value })} />
        <ControlSlider label="Entry spacing" value={settings.entryGapMm ?? 4.5} min={1} max={10} step={0.5} suffix="mm" onChange={(value) => update({ entryGapMm: value })} />
        </>}
        {supports("sidebar") && (settings.layout === "sidebar-left" || settings.layout === "sidebar-right") && <Paper variant="outlined" sx={{ p: 1.5 }}>
          <Typography variant="body2" sx={{ fontWeight: 700 }}>Sidebar content</Typography>
          <ControlSlider label="Sidebar width" value={settings.sidebarWidthMm ?? 62} min={45} max={85} step={1} suffix="mm" onChange={(value) => update({ sidebarWidthMm: value })} />
          <Stack>{["contact", "skills", "languages", "certifications", "projects", "interests"].map((key) => <FormControlLabel key={key} control={<Switch size="small" checked={sidebarSections.includes(key)} onChange={() => toggleSidebarSection(key)} />} label={SECTION_LABELS[key] ?? "Contact details"} />)}</Stack>
        </Paper>}
        <Divider />
        {supports("photo") && <FormControlLabel control={<Switch checked={settings.showPhoto} onChange={(e) => update({ showPhoto: e.target.checked })} />} label="Show profile photo" />}
        {supports("icons") && <FormControlLabel control={<Switch checked={settings.showIcons} onChange={(e) => update({ showIcons: e.target.checked })} />} label="Contact icons" />}
      </>}
    </Stack>
  );
}

function ControlSlider({ label, value, min, max, step, suffix = "", onChange }: { label: string; value: number; min: number; max: number; step: number; suffix?: string; onChange: (value: number) => void }) {
  return <Box><Stack direction="row" justifyContent="space-between" alignItems="baseline"><Typography variant="body2">{label}</Typography><Typography variant="caption" color="text.secondary">{Number(value.toFixed(2))}{suffix}</Typography></Stack><Slider size="small" aria-label={label} value={value} min={min} max={max} step={step} onChange={(_, next) => onChange(next as number)} /></Box>;
}

function HistoryTab({ versions, onRestore }: { versions: CvVariantVersionInfo[]; onRestore: (v: number) => void }) {
  if (versions.length === 0) return <Typography variant="body2" color="text.secondary">No saved versions yet. Edits autosave and appear here.</Typography>;
  return (
    <Stack spacing={0.5}>
      <Typography variant="caption" color="text.secondary">{versions.length} saved version{versions.length === 1 ? "" : "s"}. Restoring keeps history — it adds a new version.</Typography>
      {versions.map((v) => (
        <Paper key={v.version} variant="outlined" sx={{ p: 1, display: "flex", alignItems: "center", gap: 1 }}>
          <Box sx={{ flex: 1 }}>
            <Typography variant="body2" sx={{ fontWeight: 700 }}>
              Version {v.version} {v.isCurrent && <Chip size="small" label="current" sx={{ ml: 0.5, height: 18 }} />}
            </Typography>
            <Typography variant="caption" color="text.secondary">{relTime(v.createdAtUtc)} · {v.source}</Typography>
          </Box>
          {!v.isCurrent && <Button size="small" onClick={() => onRestore(v.version)}>Restore</Button>}
        </Paper>
      ))}
    </Stack>
  );
}

function relTime(iso: string): string {
  const then = new Date(iso).getTime();
  const diff = Date.now() - then;
  const mins = Math.round(diff / 60000);
  if (mins < 1) return "just now";
  if (mins < 60) return `${mins}m ago`;
  const hrs = Math.round(mins / 60);
  if (hrs < 24) return `${hrs}h ago`;
  return new Date(iso).toLocaleDateString();
}
