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, Dialog, DialogActions, DialogContent, DialogTitle, 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 AutoFixHighIcon from "@mui/icons-material/AutoFixHigh";
import TranslateIcon from "@mui/icons-material/Translate";
import SpellcheckIcon from "@mui/icons-material/Spellcheck";

import { api, getApiErrorMessage } from "../api";
import { useToast } from "../toast";
import { useAccountPlan } from "../accountPlan";
import RichTextField from "../components/RichTextField";
import AiSectionAssistant from "../components/cv/AiSectionAssistant";
import ProFeatureNotice from "../components/ProFeatureNotice";
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, normalizeCvLanguage,
} from "../cvBuilder";
import { useDialogActions } from "../dialogs";
import { useI18n } from "../i18n/I18nProvider";

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, pageMarginMm: number, pageIndex = 0): string {
  const height = Math.max(1, Math.round(pageHeightPx * 100) / 100);
  const margin = Math.max(0, Math.round(pageMarginMm * (96 / 25.4) * 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{position:relative;transform-origin:top left;}.page::after{content:"";position:absolute;z-index:20;pointer-events:none;inset:0;background:repeating-linear-gradient(to bottom,var(--cv-paper,#fff) 0 ${margin}px,transparent ${margin}px calc(${height}px - ${margin}px),var(--cv-paper,#fff) calc(${height}px - ${margin}px) ${height}px);}</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 M=${margin};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;let top=topOf(el)+current;const blockHeight=heightOverride||Math.max(0,el.offsetHeight-current);const usable=H-(M*2);if(blockHeight<=0||blockHeight>usable-2)return;let pageTop=Math.floor(top/H)*H;let contentTop=pageTop+M;let contentBottom=pageTop+H-M;let extra=0;if(top<contentTop){extra=contentTop-top;top=contentTop;}if(top+blockHeight>contentBottom+1){extra+=(pageTop+H+M)-top;}if(extra>0){el.style.paddingTop=(current+extra)+'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),:scope > .paragraphs > p').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 { t } = useI18n();
  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]);
  const pageMarginMm = useMemo(() => {
    const themeMargin = themes.find((theme) => theme.id === settings?.themeId)?.pageMarginMm;
    return settings?.pageMarginMm ?? themeMargin ?? 16;
  }, [settings?.pageMarginMm, settings?.themeId, themes]);

  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, t("cvEditorOpenFailed")));
      }
    })();
    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(t("cvEditorDiscardMessage"), {
      title: t("cvEditorDiscardTitle"),
      confirmLabel: t("cvEditorDiscardLeave"),
      destructive: true,
    }).then((confirmed) => {
      if (confirmed) blocker.proceed();
      else blocker.reset();
    });
  }, [blocker, confirmAction, t]);

  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(t("cvEditorSaveBeforeVisibility"), "error");
      return;
    }
    setPublishing(true);
    try {
      const updated = await cvBuilderApi.setPublic(variantId, !isPublic);
      setIsPublic(updated.isPublic);
      setPublicSlug(updated.publicSlug);
      toast(t(updated.isPublic ? "cvEditorNowPublic" : "cvEditorNowPrivate"), "success");
    } catch (err) {
      toast(getApiErrorMessage(err, t("cvEditorVisibilityFailed")), "error");
    } finally {
      setPublishing(false);
    }
  };

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

  const exportPdf = async () => {
    if (hasUnsavedChanges && !(await retrySave())) {
      toast(t("cvEditorSaveBeforeExport"), "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, t("cvEditorPdfFailed")), "error");
    } finally {
      setExporting(false);
    }
  };

  const duplicateVariant = async () => {
    if (hasUnsavedChanges && !(await retrySave())) {
      toast(t("cvEditorSaveBeforeDuplicate"), "error");
      return;
    }
    try {
      const copy = await cvBuilderApi.duplicate(variantId, t("cvEditorCopyName", { name: name || t("cvEditorUntitledCv") }));
      navigate(`/career/builder/${copy.id}`);
      toast(t("cvEditorDuplicated"), "success");
    } catch (err) {
      toast(getApiErrorMessage(err, t("cvEditorDuplicateFailed")), "error");
    }
  };

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

  const restore = async (version: number) => {
    if (!(await confirmAction(t("cvEditorRestoreMessage", { version }), {
      title: t("cvEditorRestoreTitle", { version }),
      confirmLabel: t("cvEditorRestoreVersion"),
    }))) return;
    if (hasUnsavedChanges && !(await retrySave())) {
      toast(t("cvEditorSaveBeforeRestore"), "error");
      return;
    }
    try {
      const updated = await cvBuilderApi.restore(variantId, version);
      applyVariant(updated);
      await loadVersions();
      toast(t("cvEditorRestoredVersion", { version }), "success");
    } catch (err) {
      toast(getApiErrorMessage(err, t("cvEditorRestoreFailed")), "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")}>{t("cvEditorBack")}</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={t("cvEditorBack")}><IconButton size="small" aria-label={t("cvEditorBack")} 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() ? t("cvEditorNameRequired") : undefined}
            sx={{ minWidth: { xs: 150, sm: 220 }, flex: "1 1 240px", maxWidth: 420 }}
            slotProps={{ input: { style: { fontWeight: 800, fontSize: "1.05rem" } }, htmlInput: { "aria-label": t("cvEditorName") } }} />
          <SaveBadge state={saveState} canRetry={!!name.trim()} onRetry={() => void retrySave()} />
          <Divider orientation="vertical" flexItem sx={{ display: { xs: "none", sm: "block" } }} />
          <Tooltip title={t("cvEditorUndo")}><span><IconButton size="small" aria-label={t("cvEditorUndo")} disabled={!canUndo} onClick={undo}><UndoIcon fontSize="small" /></IconButton></span></Tooltip>
          <Tooltip title={t("cvEditorRedo")}><span><IconButton size="small" aria-label={t("cvEditorRedo")} 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")}>{t("cvEditorEdit")}</Button>
            <Button size="small" variant={mobilePane === "preview" ? "contained" : "text"} onClick={() => setMobilePane("preview")}>{t("cvEditorPreview")}</Button>
          </Stack>}
          <Box sx={{ flex: { sm: 1 } }} />
          <Button size="small" variant="text" startIcon={<ContentCopyIcon />} disabled={!name.trim()} onClick={() => void duplicateVariant()}>{t("cvEditorDuplicate")}</Button>
          <Button size="small" variant={isPublic ? "contained" : "outlined"} startIcon={<PublicIcon />} disabled={publishing || exporting || !name.trim()} onClick={togglePublic}>
            {publishing ? t("cvEditorUpdating") : isPublic ? t("cvEditorPublic") : t("cvEditorPrivate")}
          </Button>
          {isPublic && <Button size="small" startIcon={<ContentCopyIcon />} onClick={copyPublicLink}>{t("cvEditorCopyLink")}</Button>}
          <Button size="small" variant="contained" startIcon={<PictureAsPdfIcon />} disabled={exporting || publishing || !name.trim()} onClick={exportPdf}>{exporting ? t("cvEditorExporting") : t("cvEditorDownloadPdf")}</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={t("cvEditorTemplate")} />
          <Tab label={t("cvEditorContent")} />
          <Tab label={t("cvEditorCustomize")} />
          <Tab label={t("cvEditorAiTools")} />
          <Tab label={t("cvEditorHistory")} />
        </Tabs>

        {tab === 0 && <CustomizeTab mode="template" settings={settings} update={update} themes={themes} />}
        {tab === 1 && <ContentTab settings={settings} update={update} outline={outline} />}
        {tab === 2 && <CustomizeTab mode="customize" settings={settings} update={update} themes={themes} />}
        {tab === 3 && <DocumentAiTab settings={settings} outline={outline} update={update} />}
        {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 }}>{t("cvEditorLivePreview")}</Typography>
          {previewing && <Chip size="small" label={t("cvEditorUpdatingPreview")} variant="outlined" />}
          {previewError && <Chip size="small" label={t("cvEditorPreviewUnavailable")} color="error" variant="outlined" />}
          {previewError && <Button size="small" onClick={() => setPreviewRevision((revision) => revision + 1)}>{t("cvEditorRetryPreview")}</Button>}
          <Box sx={{ flex: 1 }} />
          {pages >= 3 && <Chip size="small" color="warning" variant="outlined" label={t("cvEditorPageCount", { count: pages })} />}
          <Stack direction="row" alignItems="center" spacing={0.5}>
            <Button size="small" disabled={page <= 1} onClick={() => goToPage(page - 1)}>{t("cvEditorPrevious")}</Button>
            <Typography variant="caption">{t("cvEditorPageOf", { page, pages })}</Typography>
            <Button size="small" disabled={page >= pages} onClick={() => goToPage(page + 1)}>{t("cvEditorNext")}</Button>
          </Stack>
          <Divider orientation="vertical" flexItem sx={{ mx: 0.5 }} />
          <IconButton size="small" aria-label={t("cvEditorZoomOut")} 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={t("cvEditorZoom")} />
          <IconButton size="small" aria-label={t("cvEditorZoomIn")} 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}>{t("cvEditorFit")}</Button>
        </Stack>
        {previewOverflow && <Alert severity="warning" sx={{ mb: 1 }}>{t("cvEditorOverflowWarning")}</Alert>}
        {pages >= 3 && <Alert severity="info" sx={{ mb: 1 }}>{t("cvEditorLongDocument", { pages })}</Alert>}
        <iframe ref={iframeRef} title={t("cvEditorPreviewTitle")} srcDoc={previewDocumentHtml(html, pageMetrics.heightPx, pageMarginMm)} 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 }}>{t("cvEditorPage", { 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={t("cvEditorPreviewPage", { page: index + 1 })}
                    srcDoc={previewDocumentHtml(html, pageMetrics.heightPx, pageMarginMm, 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 { t } = useI18n();
  const map: Record<SaveState, { label: string; color: "default" | "warning" | "success" | "error" }> = {
    idle: { label: "", color: "default" },
    unsaved: { label: t("cvEditorUnsaved"), color: "warning" },
    saving: { label: t("cvEditorSaving"), color: "warning" },
    saved: { label: t("cvEditorSaved"), color: "success" },
    error: { label: t("cvEditorSaveFailed"), 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}>{t("cvEditorSaveNow")}</Button>
      ) : null}
    </Stack>
  );
}

// ---------- Whole-document AI ----------

type DocumentAiBlock = {
  id: string;
  title?: string;
  subtitle?: string;
  items?: string[];
};

function documentAiBlocks(settings: CvVariantSettings, outline: CvOutline | null): DocumentAiBlock[] {
  const blocks: DocumentAiBlock[] = [];
  const configured = new Map(settings.sections.map((section) => [section.key, section]));
  if ((settings.headline ?? outline?.headline)?.trim()) {
    blocks.push({ id: "headline", items: [(settings.headline ?? outline?.headline ?? "").trim()] });
  }
  for (const section of outline?.sections ?? []) {
    const sectionSetting = configured.get(section.key);
    if (sectionSetting?.hidden) continue;
    const title = sectionSetting?.title ?? section.title;
    if (section.kind === "entries") {
      const entryKeys = Array.from(new Set(section.entries.map((entry, index) => entry.key ?? `${index}`)));
      const storedOrder = Array.from(new Set(sectionSetting?.itemOrder ?? []));
      const orderedKeys = storedOrder.length
        ? [...storedOrder.filter((key) => entryKeys.includes(key)), ...entryKeys.filter((key) => !storedOrder.includes(key))]
        : entryKeys;
      const byKey = new Map(section.entries.map((entry, index) => [entry.key ?? `${index}`, entry]));
      for (const key of orderedKeys) {
        const entry = byKey.get(key);
        if (!entry) continue;
        const override = settings.overrides[key] ?? {};
        if (override.hidden) continue;
        blocks.push({
          id: `entry:${section.key}:${key}`,
          title: override.title ?? entry.title,
          subtitle: override.subtitle ?? entry.subtitle,
          items: override.bullets ?? entry.bullets,
        });
      }
    } else {
      blocks.push({ id: `section:${section.key}`, title, items: sectionSetting?.items ?? (section.kind === "tags" ? section.tags : section.bullets) });
    }
  }
  for (const section of settings.customSections ?? []) {
    if (!section.hidden) blocks.push({ id: `custom:${section.key}`, title: section.title, items: section.items });
  }
  return blocks.filter((block) => block.title?.trim() || block.subtitle?.trim() || block.items?.some((item) => item.trim()));
}

function parseDocumentAiBlocks(value: string, originals: DocumentAiBlock[]): DocumentAiBlock[] | null {
  try {
    const start = value.indexOf("[");
    const end = value.lastIndexOf("]");
    if (start < 0 || end <= start) return null;
    const parsed = JSON.parse(value.slice(start, end + 1));
    if (!Array.isArray(parsed)) return null;
    const originalIds = new Set(originals.map((block) => block.id));
    const result = parsed.filter((block): block is DocumentAiBlock => block && typeof block.id === "string" && originalIds.has(block.id)).map((block) => {
      const original = originals.find((item) => item.id === block.id);
      return {
        id: block.id,
        title: typeof block.title === "string" ? block.title : original?.title,
        subtitle: typeof block.subtitle === "string" ? block.subtitle : original?.subtitle,
        items: Array.isArray(block.items) ? block.items.filter((item): item is string => typeof item === "string") : original?.items,
      };
    });
    return result.length === originals.length && new Set(result.map((block) => block.id)).size === originals.length ? result : null;
  } catch {
    return null;
  }
}

function applyDocumentAiBlocks(settings: CvVariantSettings, blocks: DocumentAiBlock[]): CvVariantSettings {
  const byId = new Map(blocks.map((block) => [block.id, block]));
  const headline = byId.get("headline")?.items?.[0];
  const sections = settings.sections.map((section) => {
    const block = byId.get(`section:${section.key}`);
    return block ? { ...section, title: block.title, items: block.items } : section;
  });
  const configuredKeys = new Set(sections.map((section) => section.key));
  for (const block of blocks) {
    if (!block.id.startsWith("section:")) continue;
    const key = block.id.slice("section:".length);
    if (!configuredKeys.has(key)) sections.push({ key, title: block.title, items: block.items });
  }
  const overrides = { ...settings.overrides };
  for (const block of blocks) {
    if (!block.id.startsWith("entry:")) continue;
    const key = block.id.split(":").slice(2).join(":");
    overrides[key] = { ...overrides[key], title: block.title, subtitle: block.subtitle, bullets: block.items };
  }
  const customSections = settings.customSections.map((section) => {
    const block = byId.get(`custom:${section.key}`);
    return block ? { ...section, title: block.title, items: block.items ?? [] } : section;
  });
  return { ...settings, ...(headline !== undefined ? { headline } : {}), sections, overrides, customSections };
}

function DocumentAiTab({ settings, outline, update }: {
  settings: CvVariantSettings;
  outline: CvOutline | null;
  update: (patch: Partial<CvVariantSettings>) => void;
}) {
  const { t } = useI18n();
  const { toast } = useToast();
  const { canUseAi } = useAccountPlan();
  const blocks = useMemo(() => documentAiBlocks(settings, outline), [settings, outline]);
  const [targetLanguage, setTargetLanguage] = useState(normalizeCvLanguage(settings.language));
  const [busy, setBusy] = useState<string | null>(null);
  const [suggestion, setSuggestion] = useState<DocumentAiBlock[] | null>(null);

  useEffect(() => setTargetLanguage(normalizeCvLanguage(settings.language)), [settings.language]);

  const run = async (action: string) => {
    if (!blocks.length) return;
    setBusy(action);
    setSuggestion(null);
    try {
      const response = await cvBuilderApi.aiAssist({
        action,
        text: JSON.stringify(blocks),
        language: targetLanguage,
        context: "This is a complete CV represented as JSON blocks. Return only a JSON array with every original id exactly once and the same title, subtitle and items fields. Preserve all facts, employers, dates, qualifications and technologies. Never add unsupported claims.",
      });
      const parsed = parseDocumentAiBlocks(response.result, blocks);
      if (!parsed) throw new Error(t("cvDocumentAiInvalid"));
      setSuggestion(parsed);
    } catch (error) {
      toast(getApiErrorMessage(error, t("cvAiFailed")), "error");
    } finally {
      setBusy(null);
    }
  };

  const actions = [
    { key: "document-translate", title: t("cvDocumentAiTranslate"), description: t("cvDocumentAiTranslateHelp"), icon: <TranslateIcon /> },
    { key: "document-improve", title: t("cvDocumentAiImprove"), description: t("cvDocumentAiImproveHelp"), icon: <AutoFixHighIcon /> },
    { key: "document-grammar", title: t("cvDocumentAiGrammar"), description: t("cvDocumentAiGrammarHelp"), icon: <SpellcheckIcon /> },
  ];
  const changed = suggestion?.filter((block) => JSON.stringify(block) !== JSON.stringify(blocks.find((original) => original.id === block.id))) ?? [];

  return (
    <Stack spacing={2}>
      <Box>
        <Typography variant="h6" sx={{ fontWeight: 850 }}>{t("cvDocumentAiTitle")}</Typography>
        <Typography variant="body2" color="text.secondary">{t("cvDocumentAiHelp")}</Typography>
      </Box>
      {!canUseAi && <ProFeatureNotice featureKey="cv-writing-ai" title={t("cvDocumentAiProTitle")}>{t("cvAiProBody")}</ProFeatureNotice>}
      <TextField select size="small" label={t("cvAiLanguage")} value={targetLanguage} onChange={(event) => setTargetLanguage(event.target.value)} sx={{ maxWidth: 220 }}>
        <MenuItem value="en">{t("languageEnglish")}</MenuItem>
        <MenuItem value="nb-NO">{t("languageNorwegianBokmal")}</MenuItem>
      </TextField>
      <Stack spacing={1}>
        {actions.map((action) => (
          <Paper key={action.key} variant="outlined" sx={{ p: 1.5 }}>
            <Stack direction="row" spacing={1.5} alignItems="flex-start">
              <Box sx={{ color: "primary.main", pt: 0.25 }}>{action.icon}</Box>
              <Box sx={{ flex: 1 }}>
                <Typography variant="subtitle2" sx={{ fontWeight: 800 }}>{action.title}</Typography>
                <Typography variant="body2" color="text.secondary">{action.description}</Typography>
              </Box>
              <Button variant="outlined" size="small" disabled={!canUseAi || !!busy || !blocks.length} onClick={() => void run(action.key)}>
                {busy === action.key ? t("cvAiWorking") : t("cvDocumentAiReview")}
              </Button>
            </Stack>
          </Paper>
        ))}
      </Stack>
      {suggestion && (
        <Paper variant="outlined" sx={{ p: 1.5, borderColor: "primary.main" }}>
          <Typography variant="subtitle2" sx={{ fontWeight: 800 }}>{t("cvDocumentAiReviewTitle", { count: changed.length })}</Typography>
          <Typography variant="body2" color="text.secondary" sx={{ mb: 1.5 }}>{t("cvDocumentAiReviewHelp")}</Typography>
          {changed.length === 0 ? <Alert severity="info">{t("cvDocumentAiNoChanges")}</Alert> : (
            <Stack spacing={1} sx={{ maxHeight: 360, overflowY: "auto" }}>
              {changed.map((block) => {
                const original = blocks.find((item) => item.id === block.id);
                return <Paper key={block.id} variant="outlined" sx={{ p: 1 }}><Typography variant="caption" sx={{ fontWeight: 800 }}>{block.title ?? original?.title ?? block.id}</Typography><Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", sm: "1fr 1fr" }, gap: 1, mt: 0.5 }}><Typography variant="body2" sx={{ whiteSpace: "pre-wrap", color: "text.secondary" }}>{[original?.subtitle, ...(original?.items ?? [])].filter(Boolean).join("\n")}</Typography><Typography variant="body2" sx={{ whiteSpace: "pre-wrap" }}>{[block.subtitle, ...(block.items ?? [])].filter(Boolean).join("\n")}</Typography></Box></Paper>;
              })}
            </Stack>
          )}
          <Alert severity="info" sx={{ mt: 1.5 }}>{t("cvAiCheckFacts")}</Alert>
          <Stack direction="row" spacing={1} sx={{ mt: 1.5 }}>
            <Button variant="contained" disabled={!changed.length} onClick={() => { const next = applyDocumentAiBlocks(settings, suggestion); update(next); setSuggestion(null); toast(t("cvDocumentAiApplied"), "success"); }}>{t("cvAiApply")}</Button>
            <Button onClick={() => setSuggestion(null)}>{t("cvAiReject")}</Button>
          </Stack>
        </Paper>
      )}
    </Stack>
  );
}

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

type Translate = ReturnType<typeof useI18n>["t"];

function sectionName(key: string, fallback: string, t: Translate) {
  const labels: Record<string, string> = {
    summary: t("cvSectionSummary"), experience: t("cvSectionExperience"), education: t("cvSectionEducation"),
    projects: t("cvSectionProjects"), skills: t("cvSectionSkills"), certifications: t("cvSectionCertifications"),
    languages: t("cvSectionLanguages"), interests: t("cvSectionInterests"), awards: t("cvSectionAwards"),
    publications: t("cvSectionPublications"), organisations: t("cvSectionOrganisations"), references: t("cvSectionReferences"),
  };
  return labels[key] ?? fallback;
}

function presetLabel(key: string, fallback: string, t: Translate) {
  const labels: Record<string, string> = {
    courses: t("cvSectionCourses"), volunteer: t("cvSectionVolunteer"),
    "additional-experience": t("cvSectionAdditionalExperience"), declaration: t("cvSectionDeclaration"),
  };
  return labels[key] ?? fallback;
}

function ContentTab({ settings, update, outline }: {
  settings: CvVariantSettings;
  update: (p: Partial<CvVariantSettings>) => void;
  outline: CvOutline | null;
}) {
  const { t } = useI18n();
  const { confirmAction } = useDialogActions();
  const [addContentOpen, setAddContentOpen] = useState(false);
  // Full section list = configured order (once touched) else default, always including every known key.
  const sectionRows: CvSectionSetting[] = useMemo(() => {
    const populated = new Set((outline?.sections ?? []).map((section) => section.key));
    const base: CvSectionSetting[] = settings.sections.length ? [...settings.sections] : DEFAULT_SECTION_ORDER.map((key) => ({ key, hidden: !populated.has(key) }));
    const have = new Set(base.map((s) => s.key));
    for (const key of DEFAULT_SECTION_ORDER) if (!have.has(key)) base.push({ key, hidden: !populated.has(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 = (type: string) => {
    const preset = CUSTOM_SECTION_PRESETS.find((item) => item.key === type);
    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 ?? t("cvEditorNewSection"),
        items: [],
        presetKey: preset?.key ?? null,
        contentType: preset?.contentType ?? "paragraphs",
      }],
      sections: [...sectionRows, { key: `custom:${key}` }],
    });
    setAddContentOpen(false);
  };
  const addBuiltIn = (key: string) => {
    patchSection(key, { hidden: false });
    setAddContentOpen(false);
  };
  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(t("cvEditorDeleteCustomMessage", { title: section.title || t("cvEditorUntitled") }), {
      title: t("cvEditorDeleteCustomTitle"),
      confirmLabel: t("cvEditorDeleteSection"),
      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">{t("cvEditorEditMasterProfile")}</Button>}>
        {t("cvEditorMasterProfileHelp")}
      </Alert>
      <TextField label={t("cvEditorHeadlineOverride")} size="small" fullWidth value={settings.headline ?? ""}
        onChange={(e) => update({ headline: e.target.value || null })}
        helperText={t("cvEditorHeadlineHelp")} />

      <Box>
        <Typography variant="subtitle2" sx={{ fontWeight: 800, mb: 0.5 }}>{t("cvEditorSections")}</Typography>
        <Typography variant="caption" color="text.secondary">{t("cvEditorSectionsHelp")}</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>

      <Button variant="contained" startIcon={<AddIcon />} onClick={() => setAddContentOpen(true)} sx={{ alignSelf: "center", minWidth: 190 }}>{t("cvEditorAddContent")}</Button>
      <Dialog open={addContentOpen} onClose={() => setAddContentOpen(false)} fullWidth maxWidth="md" aria-labelledby="cv-add-content-title">
        <DialogTitle id="cv-add-content-title" sx={{ fontWeight: 850 }}>{t("cvEditorAddContent")}</DialogTitle>
        <DialogContent dividers>
          <Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>{t("cvEditorAddContentHelp")}</Typography>
          <Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", sm: "repeat(2, minmax(0, 1fr))", md: "repeat(3, minmax(0, 1fr))" }, gap: 1 }}>
            {DEFAULT_SECTION_ORDER.map((key) => {
              const row = sectionRows.find((section) => section.key === key);
              const added = !!row && !row.hidden;
              const label = sectionName(key, SECTION_LABELS[key] ?? key, t);
              return <AddContentCard key={key} title={label} description={t("cvEditorAddBuiltInHelp", { section: label })} added={added} onAdd={() => addBuiltIn(key)} />;
            })}
            {CUSTOM_SECTION_PRESETS.map((preset) => {
              const added = settings.customSections.some((item) => item.presetKey === preset.key);
              const label = presetLabel(preset.key, preset.title, t);
              return <AddContentCard key={preset.key} title={label} description={t("cvEditorAddPresetHelp", { section: label })} added={added} onAdd={() => addCustom(preset.key)} />;
            })}
            <AddContentCard title={t("cvEditorCustom")} description={t("cvEditorAddCustomHelp")} added={false} onAdd={() => addCustom("free-text")} />
          </Box>
        </DialogContent>
        <DialogActions><Button onClick={() => setAddContentOpen(false)}>{t("cancel")}</Button></DialogActions>
      </Dialog>
    </Stack>
  );
}

function AddContentCard({ title, description, added, onAdd }: { title: string; description: string; added: boolean; onAdd: () => void }) {
  const { t } = useI18n();
  return (
    <Paper variant="outlined" sx={{ p: 1.25, minHeight: 112, display: "flex", flexDirection: "column", alignItems: "flex-start", bgcolor: added ? "action.disabledBackground" : "background.paper" }}>
      <Typography variant="subtitle2" sx={{ fontWeight: 800 }}>{title}</Typography>
      <Typography variant="caption" color="text.secondary" sx={{ flex: 1, mt: 0.25 }}>{description}</Typography>
      <Button size="small" startIcon={!added ? <AddIcon /> : undefined} disabled={added} onClick={onAdd} sx={{ mt: 0.75, ml: -0.75 }}>
        {added ? t("cvEditorContentAdded") : t("cvEditorAdd")}
      </Button>
    </Paper>
  );
}

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 { t } = useI18n();
  const { confirmAction } = useDialogActions();
  const [expanded, setExpanded] = useState(false);
  const sectionLabel = customSection?.title ?? row.title ?? sectionName(row.key, SECTION_LABELS[row.key] ?? row.key, t);
  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={t("cvEditorMoveSectionUp", { section: sectionLabel })} disabled={index === 0} onClick={() => onMove(-1)}><ArrowUpwardIcon sx={{ fontSize: 15 }} /></IconButton>
          <IconButton size="small" aria-label={t("cvEditorMoveSectionDown", { section: sectionLabel })} 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": t("cvEditorSectionName", { section: row.key }) } }} />
        {customSection && <Chip size="small" label={t("cvEditorCustom")} variant="outlined" sx={{ height: 20 }} />}
        <IconButton size="small" aria-label={t(row.hidden ? "cvEditorShowSection" : "cvEditorHideSection", { section: sectionLabel })} onClick={() => onPatch({ hidden: !row.hidden })}>
          {row.hidden ? <VisibilityOffIcon fontSize="small" /> : <VisibilityIcon fontSize="small" />}
        </IconButton>
        <IconButton size="small" aria-label={t("cvEditorRemoveSection", { section: sectionLabel })} onClick={() => {
          if (customSection) onRemoveCustom();
          else void confirmAction(t("cvEditorRemoveSectionMessage", { section: sectionLabel }), { title: t("cvEditorRemoveSectionTitle"), confirmLabel: t("cvEditorRemoveFromCv"), destructive: true }).then((confirmed) => { if (confirmed) onPatch({ hidden: true }); });
        }}><DeleteOutlineIcon fontSize="small" /></IconButton>
        <IconButton size="small" aria-label={t(expanded ? "cvEditorCollapseSection" : "cvEditorExpandSection", { section: 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" }}>
          <Paper variant="outlined" sx={{ p: 1, mb: 1.25, bgcolor: "background.paper" }}>
            <Stack direction={{ xs: "column", sm: "row" }} spacing={1} alignItems={{ sm: "center" }}>
              <Box sx={{ flex: 1 }}>
                <Typography variant="caption" sx={{ fontWeight: 800 }}>{t("cvEditorSectionLayout")}</Typography>
                <Typography variant="caption" color="text.secondary" sx={{ display: "block" }}>{t("cvEditorSectionLayoutHelp")}</Typography>
              </Box>
              <TextField select size="small" label={t("cvEditorPresentation")} value={row.presentation ?? "rows"} onChange={(event) => onPatch({ presentation: event.target.value as CvSectionSetting["presentation"] })} sx={{ minWidth: 145 }}>
                <MenuItem value="rows">{t("cvEditorRows")}</MenuItem>
                <MenuItem value="grid">{t("cvEditorGrid")}</MenuItem>
                <MenuItem value="compact">{t("cvEditorCompact")}</MenuItem>
                {(outlineSection?.kind === "tags" || (!outlineSection && customSection?.contentType !== "entries")) && <MenuItem value="bubble">{t("cvEditorBubble")}</MenuItem>}
              </TextField>
              {row.presentation === "grid" && <Stack direction="row" spacing={0.5} role="group" aria-label={t("cvEditorColumns")}>
                {[1, 2].map((columns) => <Button key={columns} size="small" variant={(row.columns ?? 1) === columns ? "contained" : "outlined"} onClick={() => onPatch({ columns: columns as 1 | 2 })}>{columns}</Button>)}
              </Stack>}
            </Stack>
          </Paper>
          {customSection ? (
            <Stack spacing={1}>
              <TextField select size="small" label={t("cvEditorContentFormat")} value={customSection.contentType ?? "bullets"} onChange={(event) => onUpdateCustom({ contentType: event.target.value as CvCustomSectionSetting["contentType"] })}>
                <MenuItem value="paragraphs">{t("cvEditorParagraphs")}</MenuItem><MenuItem value="bullets">{t("cvEditorBulletList")}</MenuItem><MenuItem value="entries">{t("cvEditorMultipleEntries")}</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={t("cvEditorEntry", { number: itemIndex + 1 })} value={item} onChange={(event) => onUpdateCustom({ items: customSection.items.map((current, index) => index === itemIndex ? event.target.value : current) })} />
                  <IconButton size="small" aria-label={t("cvEditorMoveEntryUp", { number: itemIndex + 1 })} disabled={itemIndex === 0} onClick={() => onUpdateCustom({ items: moveItem(customSection.items, itemIndex, itemIndex - 1) })}><ArrowUpwardIcon fontSize="small" /></IconButton>
                  <IconButton size="small" aria-label={t("cvEditorMoveEntryDown", { number: itemIndex + 1 })} disabled={itemIndex === customSection.items.length - 1} onClick={() => onUpdateCustom({ items: moveItem(customSection.items, itemIndex, itemIndex + 1) })}><ArrowDownwardIcon fontSize="small" /></IconButton>
                  <IconButton size="small" aria-label={t("cvEditorDeleteEntryNumber", { number: itemIndex + 1 })} onClick={() => void confirmAction(t("cvEditorDeleteEntryMessage"), { title: t("cvEditorDeleteEntryTitle"), confirmLabel: t("cvEditorDeleteEntry"), 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, ""] })}>{t("cvEditorAddEntry")}</Button>
              </Stack> : <RichTextField label={customSection.contentType === "paragraphs" ? t("cvEditorParagraphsHelp") : t("cvEditorBulletsHelp")} 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} documentLanguage={settings.language} 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" ? t("cvEditorItemsHelp") : t("cvEditorContentItemsHelp")} 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 })}>{t("cvEditorResetMasterProfile")}</Button>}
              <AiSectionAssistant sectionName={sectionLabel} text={editableItems.join("\n")} documentLanguage={settings.language} 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;
}) {
  const { t } = useI18n();
  // Entry order: itemOrder if set, else master order; always covering every entry key.
  const orderedKeys: string[] = useMemo(() => {
    const master = Array.from(new Set(section.entries.map((e) => e.key ?? "").filter(Boolean)));
    if (!row.itemOrder?.length) return master;
    const storedOrder = Array.from(new Set(row.itemOrder));
    const set = new Set(storedOrder);
    return [...storedOrder.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]);

  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 || t("cvEditorUntitled");
        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 }}>{entryLabel}</Typography>
              <IconButton size="small" aria-label={t("cvEditorMoveNamedEntryUp", { entry: entryLabel })} disabled={i === 0} onClick={() => onPatch({ itemOrder: moveItem(orderedKeys, i, i - 1) })}><ArrowUpwardIcon sx={{ fontSize: 14 }} /></IconButton>
              <IconButton size="small" aria-label={t("cvEditorMoveNamedEntryDown", { entry: entryLabel })} disabled={i === orderedKeys.length - 1} onClick={() => onPatch({ itemOrder: moveItem(orderedKeys, i, i + 1) })}><ArrowDownwardIcon sx={{ fontSize: 14 }} /></IconButton>
              <IconButton size="small" aria-label={t(hidden ? "cvEditorShowEntry" : "cvEditorHideEntry", { entry: entryLabel })} 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={t("cvEditorEntryTitle")} value={ov.title ?? entry.title ?? ""}
                  onChange={(e) => setOverride(key, { title: e.target.value })} />
                <TextField size="small" fullWidth label={t("cvEditorEntrySubtitle")} value={ov.subtitle ?? entry.subtitle ?? ""}
                  onChange={(e) => setOverride(key, { subtitle: e.target.value })} />
                <RichTextField label={t("cvEditorBulletsHelp")} 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")} documentLanguage={settings.language} 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 })}>{t("cvEditorResetMasterBullets")}</Button>
                )}
              </Stack>
            )}
          </Paper>
        );
      })}
    </Stack>
  );
}

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

function CustomizeTab({ mode, settings, update, themes }: {
  mode: "template" | "customize";
  settings: CvVariantSettings;
  update: (p: Partial<CvVariantSettings>) => void;
  themes: CvTheme[];
}) {
  const { t } = useI18n();
  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 }}>{t("cvEditorTheme")}</Typography>
        <Typography variant="caption" color="text.secondary" sx={{ display: "block", mb: 1.25 }}>{t("cvEditorThemeHelp")}</Typography>
        <Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 1 }}>
          {themes.map((theme) => {
            const active = theme.id === settings.themeId;
            const locked = theme.available === false;
            return (
              <Paper key={theme.id} variant="outlined" role="button" tabIndex={locked ? -1 : 0} aria-disabled={locked}
                onClick={() => { if (!locked) update({ themeId: theme.id }); }}
                onKeyDown={(e) => { if (!locked && (e.key === "Enter" || e.key === " ")) { e.preventDefault(); update({ themeId: theme.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={theme} height={110} /></Box>
                <Typography variant="body2" sx={{ fontWeight: 700 }}>{theme.name}</Typography>
                <Typography variant="caption" color="text.secondary" sx={{ display: "block" }}>{theme.category}</Typography>
                <Typography variant="caption" color="text.secondary" sx={{ display: "block", mt: 0.25, lineHeight: 1.3 }}>{theme.description}</Typography>
                <Stack direction="row" spacing={0.5} sx={{ mt: 0.5 }}>
                  {theme.atsFriendly && <Chip size="small" label={t("cvEditorAtsFriendly")} color="success" variant="outlined" sx={{ height: 18, fontSize: 10 }} />}
                  {theme.requiresPro && <Chip size="small" label={locked ? t("cvEditorPro") : t("cvEditorProUnlocked")} color="secondary" variant="outlined" sx={{ height: 18, fontSize: 10 }} />}
                </Stack>
              </Paper>
            );
          })}
        </Box>
      </Box>}

      {mode === "customize" && <>
        {supports("accent") && <><Typography variant="subtitle2" sx={{ fontWeight: 800 }}>{t("cvEditorColour")}</Typography>
        <Stack direction="row" spacing={0.75} flexWrap="wrap" useFlexGap>
          {["#3157d5", "#0f766e", "#9f1239", "#7c3aed", "#b45309", "#334155"].map((color) => (
            <IconButton key={color} aria-label={t("cvEditorUseAccent", { colour: 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={t("cvEditorCustomAccent")} 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 })}>{t("cvEditorThemeDefault")}</Button>}
        </Stack></>}

        {supports("typography") && <><Typography variant="subtitle2" sx={{ fontWeight: 800, mt: 0.5 }}>{t("cvEditorTypography")}</Typography>
        <Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 1 }}>
          <FormControl size="small" fullWidth><InputLabel>{t("cvEditorHeadingFont")}</InputLabel><Select label={t("cvEditorHeadingFont")} value={settings.headingFont ?? ""} onChange={(e) => update({ headingFont: e.target.value || null })}><MenuItem value="">{t("cvEditorThemeDefault")}</MenuItem>{FONTS.map((f, i) => <MenuItem key={f} value={f}>{FONT_LABELS[i]}</MenuItem>)}</Select></FormControl>
          <FormControl size="small" fullWidth><InputLabel>{t("cvEditorBodyFont")}</InputLabel><Select label={t("cvEditorBodyFont")} value={settings.bodyFont ?? ""} onChange={(e) => update({ bodyFont: e.target.value || null })}><MenuItem value="">{t("cvEditorThemeDefault")}</MenuItem>{FONTS.map((f, i) => <MenuItem key={f} value={f}>{FONT_LABELS[i]}</MenuItem>)}</Select></FormControl>
        </Box>
        <ControlSlider label={t("cvEditorBodySize")} value={settings.baseFontSizePt ?? 10} min={7} max={13} step={0.25} suffix="pt" onChange={(value) => update({ baseFontSizePt: value })} />
        <ControlSlider label={t("cvEditorHeadingSize")} value={settings.headingSizePt ?? 12} min={9} max={20} step={0.5} suffix="pt" onChange={(value) => update({ headingSizePt: value })} />
        <ControlSlider label={t("cvEditorLineHeight")} 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>{t("cvEditorHeadingTreatment")}</InputLabel><Select label={t("cvEditorHeadingTreatment")} value={settings.headingStyle ?? ""} onChange={(e) => update({ headingStyle: (e.target.value || null) as CvVariantSettings["headingStyle"] })}><MenuItem value="">{t("cvEditorTemplateDefault")}</MenuItem><MenuItem value="caps-rule">{t("cvEditorUppercaseDivider")}</MenuItem><MenuItem value="underline">{t("richTextUnderline")}</MenuItem><MenuItem value="plain">{t("cvEditorPlain")}</MenuItem><MenuItem value="bar">{t("cvEditorAccentBar")}</MenuItem></Select></FormControl>}
        {supports("header") && <FormControl size="small" fullWidth><InputLabel>{t("cvEditorHeaderTreatment")}</InputLabel><Select label={t("cvEditorHeaderTreatment")} value={settings.headerStyle ?? ""} onChange={(e) => update({ headerStyle: (e.target.value || null) as CvVariantSettings["headerStyle"] })}><MenuItem value="">{t("cvEditorTemplateDefault")}</MenuItem><MenuItem value="plain">{t("cvEditorPlain")}</MenuItem><MenuItem value="band">{t("cvEditorColourBand")}</MenuItem><MenuItem value="centered">{t("cvEditorCentred")}</MenuItem><MenuItem value="kicker">{t("cvEditorEditorial")}</MenuItem></Select></FormControl>}
        {supports("skills") && <><FormControl size="small" fullWidth><InputLabel>{t("cvEditorSkillsPresentation")}</InputLabel><Select inputProps={{ "aria-label": t("cvEditorSkillsPresentation") }} label={t("cvEditorSkillsPresentation")} value={settings.skillsStyle ?? "tags"} onChange={(e) => update({ skillsStyle: e.target.value as CvVariantSettings["skillsStyle"] })}><MenuItem value="tags">{t("cvEditorTags")}</MenuItem><MenuItem value="text">{t("cvEditorCompactText")}</MenuItem><MenuItem value="bullets">{t("cvEditorBulletList")}</MenuItem><MenuItem value="grouped">{t("cvEditorGroupedSkills")}</MenuItem></Select></FormControl>
        {settings.skillsStyle === "grouped" && <TextField label={t("cvEditorSkillGroups")} helperText={t("cvEditorSkillGroupsHelp")} 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 === "customize" && <>
        <Typography variant="subtitle2" sx={{ fontWeight: 800 }}>{t("cvEditorDocument")}</Typography>
        <Box sx={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 1 }}>
          {supports("page") && <>
          <FormControl size="small" fullWidth><InputLabel>{t("cvEditorPageSize")}</InputLabel><Select inputProps={{ "aria-label": t("cvEditorPageSize") }} label={t("cvEditorPageSize")} value={settings.pageSize ?? "a4"} onChange={(e) => update({ pageSize: e.target.value })}><MenuItem value="a4">A4</MenuItem><MenuItem value="letter">{t("cvEditorUsLetter")}</MenuItem></Select></FormControl>
          <FormControl size="small" fullWidth><InputLabel>{t("cvEditorDensity")}</InputLabel><Select inputProps={{ "aria-label": t("cvEditorDensity") }} label={t("cvEditorDensity")} value={settings.density ?? "balanced"} onChange={(e) => update({ density: e.target.value })}><MenuItem value="compact">{t("cvEditorCompact")}</MenuItem><MenuItem value="balanced">{t("cvEditorBalanced")}</MenuItem><MenuItem value="roomy">{t("cvEditorRoomy")}</MenuItem></Select></FormControl>
          </>}
          <FormControl size="small" fullWidth><InputLabel>{t("cvEditorDocumentLanguage")}</InputLabel><Select inputProps={{ "aria-label": t("cvEditorDocumentLanguage") }} label={t("cvEditorDocumentLanguage")} value={normalizeCvLanguage(settings.language)} onChange={(e) => update({ language: e.target.value })}><MenuItem value="en">{t("languageEnglish")}</MenuItem><MenuItem value="nb-NO">{t("languageNorwegianBokmal")}</MenuItem></Select></FormControl>
          <FormControl size="small" fullWidth><InputLabel>{t("cvEditorDateFormat")}</InputLabel><Select inputProps={{ "aria-label": t("cvEditorDateFormat") }} label={t("cvEditorDateFormat")} value={settings.dateFormat ?? "short"} onChange={(e) => update({ dateFormat: e.target.value })}><MenuItem value="long">{t("cvEditorDateLongExample")}</MenuItem><MenuItem value="short">{t("cvEditorDateShortExample")}</MenuItem><MenuItem value="numeric">01/2020</MenuItem><MenuItem value="year">2020</MenuItem></Select></FormControl>
        </Box>
        {supports("layout") && <FormControl size="small" fullWidth><InputLabel>{t("cvEditorColumns")}</InputLabel><Select inputProps={{ "aria-label": t("cvEditorColumns") }} label={t("cvEditorColumns")} value={settings.layout ?? ""} onChange={(e) => update({ layout: (e.target.value || null) as CvVariantSettings["layout"] })}><MenuItem value="">{t("cvEditorTemplateDefault")}</MenuItem><MenuItem value="single">{t("cvEditorOneColumn")}</MenuItem><MenuItem value="header-band">{t("cvEditorHeaderBandColumn")}</MenuItem><MenuItem value="sidebar-left">{t("cvEditorLeftSidebar")}</MenuItem><MenuItem value="sidebar-right">{t("cvEditorRightSidebar")}</MenuItem></Select></FormControl>}
        {supports("spacing") && <>
        <ControlSlider label={t("cvEditorPageMargins")} value={settings.pageMarginMm ?? 16} min={8} max={28} step={1} suffix="mm" onChange={(value) => update({ pageMarginMm: value })} />
        <ControlSlider label={t("cvEditorSectionSpacing")} value={settings.sectionGapMm ?? 6} min={2} max={14} step={0.5} suffix="mm" onChange={(value) => update({ sectionGapMm: value })} />
        <ControlSlider label={t("cvEditorEntrySpacing")} 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 }}>{t("cvEditorSidebarContent")}</Typography>
          <ControlSlider label={t("cvEditorSidebarWidth")} 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={key === "contact" ? t("cvEditorContactDetails") : sectionName(key, SECTION_LABELS[key] ?? key, t)} />)}</Stack>
        </Paper>}
        <Divider />
        {supports("photo") && <FormControlLabel control={<Switch checked={settings.showPhoto} onChange={(e) => update({ showPhoto: e.target.checked })} />} label={t("cvEditorShowPhoto")} />}
        {supports("icons") && <FormControlLabel control={<Switch checked={settings.showIcons} onChange={(e) => update({ showIcons: e.target.checked })} />} label={t("cvEditorContactIcons")} />}
      </>}
    </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 }) {
  const { language, t } = useI18n();
  if (versions.length === 0) return <Typography variant="body2" color="text.secondary">{t("cvEditorNoVersions")}</Typography>;
  return (
    <Stack spacing={0.5}>
      <Typography variant="caption" color="text.secondary">{t("cvEditorVersionCount", { count: versions.length })}</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 }}>
              {t("cvEditorVersion", { version: v.version })} {v.isCurrent && <Chip size="small" label={t("cvEditorCurrent")} sx={{ ml: 0.5, height: 18 }} />}
            </Typography>
            <Typography variant="caption" color="text.secondary">{relTime(v.createdAtUtc, language)} · {v.source}</Typography>
          </Box>
          {!v.isCurrent && <Button size="small" onClick={() => onRestore(v.version)}>{t("cvEditorRestore")}</Button>}
        </Paper>
      ))}
    </Stack>
  );
}

function relTime(iso: string, language: "en" | "nb"): string {
  const then = new Date(iso).getTime();
  const diff = Date.now() - then;
  const mins = Math.round(diff / 60000);
  if (mins < 1) return language === "nb" ? "akkurat nå" : "just now";
  if (mins < 60) return language === "nb" ? `${mins} min siden` : `${mins}m ago`;
  const hrs = Math.round(mins / 60);
  if (hrs < 24) return language === "nb" ? `${hrs} t siden` : `${hrs}h ago`;
  return new Date(iso).toLocaleDateString(language === "nb" ? "nb-NO" : "en-GB");
}
