import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { 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 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 AutoFixHighIcon from "@mui/icons-material/AutoFixHigh";
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 { api, getApiErrorMessage } from "../api";
import { useToast } from "../toast";
import RichTextField from "../components/RichTextField";
import { useDragReorder } from "../hooks/useDragReorder";
import {
  AI_ACTIONS, CvCustomSectionSetting, CvItemOverride, CvOutline, CvOutlineSection, CvSectionSetting,
  CvTheme, CvVariant, CvVariantSettings, CvVariantVersionInfo, DEFAULT_SECTION_ORDER, SECTION_LABELS,
  cvBuilderApi, getCvPageCount, getCvPageMetrics, moveItem,
} from "../cvBuilder";
import { useAccountPlan } from "../accountPlan";
import ProFeatureNotice from "../components/ProFeatureNotice";
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";

export default function CvBuilderEditor() {
  const { id } = useParams();
  const variantId = Number(id);
  const navigate = useNavigate();
  const { toast } = useToast();
  const { confirmAction } = useDialogActions();

  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 [previewHeight, setPreviewHeight] = useState(() => getCvPageMetrics("a4").heightPx);
  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 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 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");
  };

  // 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 };
      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) => {
      blockerPromptOpen.current = false;
      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 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 = () => {
    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);
      setPreviewHeight(h);
      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);
    }
  };

  const goToPage = (p: number) => {
    const clamped = Math.min(Math.max(1, p), pages);
    setPage(clamped);
    scrollRef.current?.scrollTo({ top: (clamped - 1) * pageMetrics.heightPx * zoom, 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 />;

  return (
    <Box sx={{ display: "grid", gridTemplateColumns: { xs: "1fr", md: "minmax(360px, 460px) 1fr" }, gap: 2, alignItems: "start" }}>
      <Paper sx={{ p: 2, borderRadius: 4, position: { md: "sticky" }, top: 12, minWidth: 0, maxHeight: { md: "calc(100vh - 24px)" }, overflowY: { md: "auto" } }}>
        <Stack direction="row" alignItems="center" spacing={1} sx={{ mb: 1 }}>
          <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" fullWidth value={name} onChange={(e) => renameVariant(e.target.value)}
            error={!name.trim()} helperText={!name.trim() ? "Enter a name before saving." : undefined}
            slotProps={{ input: { style: { fontWeight: 800, fontSize: "1.05rem" } }, htmlInput: { "aria-label": "CV name" } }} />
          <SaveBadge state={saveState} canRetry={!!name.trim()} onRetry={() => void retrySave()} />
        </Stack>
        <Stack direction="row" spacing={1} sx={{ mb: 1, flexWrap: "wrap", gap: 1 }}>
          <Button size="small" variant="outlined" startIcon={<PictureAsPdfIcon />} disabled={exporting || publishing || !name.trim()} onClick={exportPdf}>{exporting ? "Exporting…" : "Export PDF"}</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>}
        </Stack>

        <Tabs value={tab} onChange={(_, v) => { setTab(v); if (v === 3) loadVersions(); }} variant="scrollable" scrollButtons="auto" allowScrollButtonsMobile sx={{ mb: 1.5 }}>
          <Tab label="Content" />
          <Tab label="Customize" />
          <Tab label="AI Tools" />
          <Tab label="History" />
        </Tabs>

        {tab === 0 && <ContentTab settings={settings} update={update} outline={outline} />}
        {tab === 1 && <CustomizeTab settings={settings} update={update} themes={themes} />}
        {tab === 2 && <AiToolsTab />}
        {tab === 3 && <HistoryTab versions={versions} onRestore={restore} />}
      </Paper>

      <Paper sx={{ p: 1.5, borderRadius: 4, 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>}
        <Box ref={scrollRef} sx={{ overflow: "auto", maxHeight: "82vh", display: "flex", justifyContent: "center", p: 1 }}>
          <Box sx={{ position: "relative", width: `calc(${pageMetrics.widthMm}mm * ${zoom})`, height: `${previewHeight * zoom}px`, flex: "0 0 auto" }}>
            <iframe
              ref={iframeRef}
              title="CV preview"
              srcDoc={html}
              onLoad={onIframeLoad}
              style={{
                width: `${pageMetrics.widthMm}mm`, height: `${previewHeight}px`, border: "none",
                transform: `scale(${zoom})`, transformOrigin: "top left",
                boxShadow: "0 8px 30px rgba(0,0,0,0.24)", background: "#fff", display: "block",
              }}
            />
            {Array.from({ length: Math.max(0, pages - 1) }).map((_, i) => (
              <Box key={i} aria-hidden sx={{
                position: "absolute", left: 0, right: 0, top: `${(i + 1) * pageMetrics.heightPx * zoom}px`,
                borderTop: "2px dashed", borderColor: "error.main", opacity: 0.72, pointerEvents: "none",
              }} />
            ))}
          </Box>
        </Box>
      </Paper>
    </Box>
  );
}

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();
  // 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 custom of settings.customSections) {
      const key = `custom:${custom.key}`;
      if (!have.has(key)) {
        base.push({ key, hidden: custom.hidden });
        have.add(key);
      }
    }
    return base;
  }, [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 orderedCustomSections = useMemo(() => {
    const rank = new Map(sectionRows.map((section, index) => [section.key, index]));
    return [...settings.customSections].sort((a, b) =>
      (rank.get(`custom:${a.key}`) ?? Number.MAX_SAFE_INTEGER) - (rank.get(`custom:${b.key}`) ?? Number.MAX_SAFE_INTEGER));
  }, [sectionRows, settings.customSections]);

  const addCustom = () => {
    const key = `c${Date.now().toString(36)}`;
    update({
      customSections: [...settings.customSections, { key, title: "New section", items: [] }],
      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}`),
    });
  };
  const updateCustomItem = (key: string, index: number, value: string) => {
    const section = settings.customSections.find((item) => item.key === key);
    if (!section) return;
    updateCustom(key, { items: section.items.map((item, itemIndex) => itemIndex === index ? value : item) });
  };
  const removeCustomItem = async (section: CvCustomSectionSetting, index: number) => {
    const value = section.items[index];
    if (value.trim() && !(await confirmAction("Delete this custom section entry?", {
      title: "Delete custom entry",
      confirmLabel: "Delete entry",
      destructive: true,
    }))) return;
    updateCustom(section.key, { items: section.items.filter((_, itemIndex) => itemIndex !== index) });
  };

  return (
    <Stack spacing={2}>
      <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 });
              }}
              onUpdateSettings={update}
            />
          ))}
        </Stack>
      </Box>

      <Box>
        <Stack direction="row" alignItems="center" justifyContent="space-between">
          <Typography variant="subtitle2" sx={{ fontWeight: 800 }}>Custom sections</Typography>
          <Button size="small" startIcon={<AddIcon />} onClick={addCustom}>Add</Button>
        </Stack>
        {settings.customSections.length === 0 && (
          <Typography variant="caption" color="text.secondary" sx={{ display: "block", mt: 0.5 }}>
            Add sections unique to this CV (e.g. a portfolio note) without changing your master profile.
          </Typography>
        )}
        {settings.customSections.length > 0 && (
          <Typography variant="caption" color="text.secondary" sx={{ display: "block", mt: 0.5 }}>
            Reorder and show or hide custom sections in the section list above.
          </Typography>
        )}
        <Stack spacing={1} sx={{ mt: 1 }}>
          {orderedCustomSections.map((c) => (
            <Paper key={c.key} variant="outlined" sx={{ p: 1 }}>
              <Stack direction="row" alignItems="center" spacing={1}>
                <TextField variant="standard" fullWidth value={c.title ?? ""} placeholder="Section title"
                  error={!c.title?.trim()} helperText={!c.title?.trim() ? "Enter a section title." : undefined}
                  onChange={(e) => updateCustom(c.key, { title: e.target.value })} slotProps={{ htmlInput: { "aria-label": "Custom section title" } }} />
                <IconButton size="small" aria-label="Remove custom section" onClick={() => void removeCustom(c)}><DeleteOutlineIcon fontSize="small" /></IconButton>
              </Stack>
              <Stack spacing={1} sx={{ mt: 1 }}>
                {c.items.map((item, itemIndex) => (
                  <Stack key={`${c.key}-${itemIndex}`} direction="row" alignItems="flex-start" spacing={0.5}>
                    <TextField fullWidth size="small" multiline minRows={2} label={`Entry ${itemIndex + 1}`} value={item}
                      error={!item.trim()} helperText={!item.trim() ? "Enter content or delete this entry." : undefined}
                      onChange={(event) => updateCustomItem(c.key, itemIndex, event.target.value)} />
                    <IconButton size="small" aria-label={`Move custom entry ${itemIndex + 1} up`} disabled={itemIndex === 0}
                      onClick={() => updateCustom(c.key, { items: moveItem(c.items, itemIndex, itemIndex - 1) })}><ArrowUpwardIcon fontSize="small" /></IconButton>
                    <IconButton size="small" aria-label={`Move custom entry ${itemIndex + 1} down`} disabled={itemIndex === c.items.length - 1}
                      onClick={() => updateCustom(c.key, { items: moveItem(c.items, itemIndex, itemIndex + 1) })}><ArrowDownwardIcon fontSize="small" /></IconButton>
                    <IconButton size="small" aria-label={`Delete custom entry ${itemIndex + 1}`} onClick={() => void removeCustomItem(c, itemIndex)}><DeleteOutlineIcon fontSize="small" /></IconButton>
                  </Stack>
                ))}
                <Button size="small" startIcon={<AddIcon />} sx={{ alignSelf: "flex-start" }} onClick={() => updateCustom(c.key, { items: [...c.items, ""] })}>Add entry</Button>
              </Stack>
            </Paper>
          ))}
        </Stack>
      </Box>
    </Stack>
  );
}

function SectionRow({
  row, index, total, dragProps, dragging, over, outlineSection, customSection, settings, onMove, onPatch, onRenameCustom, 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;
  onUpdateSettings: (p: Partial<CvVariantSettings>) => void;
}) {
  const [expanded, setExpanded] = useState(false);
  const editable = outlineSection?.kind === "entries" && (outlineSection?.entries.length ?? 0) > 0;
  const sectionLabel = customSection?.title ?? row.title ?? SECTION_LABELS[row.key] ?? row.key;

  return (
    <Paper
      variant="outlined"
      {...dragProps}
      sx={{
        p: 0.5, opacity: dragging ? 0.4 : row.hidden ? 0.5 : 1,
        borderTop: over ? "2px solid" : undefined, borderTopColor: over ? "primary.main" : undefined,
        transition: "opacity 150ms ease, border-color 120ms ease", cursor: "grab",
      }}
    >
      <Stack direction="row" alignItems="center" gap={0.5}>
        <DragIndicatorIcon sx={{ fontSize: 18, color: "text.disabled" }} 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 }} />}
        {editable && (
          <IconButton size="small" aria-label={`${expanded ? "Collapse" : "Expand"} ${sectionLabel} entries`} aria-expanded={expanded} onClick={() => setExpanded((e) => !e)}
            sx={{ transform: expanded ? "rotate(180deg)" : "none", transition: "transform 150ms" }}>
            <ExpandMoreIcon fontSize="small" />
          </IconButton>
        )}
        <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>
      </Stack>
      {editable && (
        <Collapse in={expanded} unmountOnExit>
          <Box sx={{ pl: 3, pt: 1 }}>
            <EntryEditor section={outlineSection!} row={row} settings={settings} onPatch={onPatch} onUpdateSettings={onUpdateSettings} />
          </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") })} />
                {ov.bullets && (
                  <Button size="small" onClick={() => setOverride(key, { bullets: undefined })}>Reset to master bullets</Button>
                )}
              </Stack>
            )}
          </Paper>
        );
      })}
    </Stack>
  );
}

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

function CustomizeTab({ settings, update, themes }: {
  settings: CvVariantSettings;
  update: (p: Partial<CvVariantSettings>) => void;
  themes: CvTheme[];
}) {
  return (
    <Stack spacing={2}>
      <Box>
        <Typography variant="subtitle2" sx={{ fontWeight: 800, mb: 1 }}>Theme</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 } }}>
                <Stack direction="row" spacing={0.5} sx={{ mb: 0.5 }}>
                  {t.swatches.map((s, i) => <Box key={i} sx={{ width: 14, height: 14, borderRadius: "3px", bgcolor: s, border: "1px solid rgba(0,0,0,0.1)" }} />)}
                </Stack>
                <Typography variant="body2" sx={{ fontWeight: 700 }}>{t.name}</Typography>
                <Typography variant="caption" color="text.secondary" sx={{ display: "block" }}>{t.category}</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>

      <Stack direction="row" spacing={1} alignItems="center">
        <Typography variant="body2" sx={{ flex: 1 }}>Accent colour</Typography>
        <input type="color" aria-label="Accent colour" value={settings.accentColor ?? "#2563eb"} onChange={(e) => update({ accentColor: e.target.value })} />
        {settings.accentColor && <Button size="small" onClick={() => update({ accentColor: null })}>Reset</Button>}
      </Stack>

      <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>

      <FormControl size="small" fullWidth>
        <InputLabel>Density</InputLabel>
        <Select 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>Page size</InputLabel>
        <Select label="Page size" value={settings.pageSize ?? "a4"} onChange={(e) => update({ pageSize: e.target.value })}>
          <MenuItem value="a4">A4</MenuItem>
          <MenuItem value="letter">Letter</MenuItem>
        </Select>
      </FormControl>

      <Divider />
      <FormControlLabel control={<Switch checked={settings.showPhoto} onChange={(e) => update({ showPhoto: e.target.checked })} />} label="Show profile photo" />
      <FormControlLabel control={<Switch checked={settings.showIcons} onChange={(e) => update({ showIcons: e.target.checked })} />} label="Contact icons (supported themes)" />
    </Stack>
  );
}

function AiToolsTab() {
  const { toast } = useToast();
  const { canUseAi } = useAccountPlan();
  const [text, setText] = useState("");
  const [role, setRole] = useState("");
  const [result, setResult] = useState("");
  const [busy, setBusy] = useState(false);

  const run = async (action: string) => {
    if (!canUseAi) return;
    if (!text.trim()) {
      toast("Paste some text to work on first.", "info");
      return;
    }
    setBusy(true);
    try {
      const res = await cvBuilderApi.aiAssist({ action, text, role: role || undefined });
      setResult(res.result);
    } catch (err) {
      toast(getApiErrorMessage(err, "AI request failed."), "error");
    } finally {
      setBusy(false);
    }
  };

  return (
    <Stack spacing={1.5}>
      {canUseAi ? (
        <Alert severity="info" sx={{ py: 0.5 }}>AI suggestions never change your profile automatically. Copy what you like back into your CV.</Alert>
      ) : (
        <ProFeatureNotice featureKey="cv-writing-ai" title="Refine CV wording with Pro.">
          Generate optional writing suggestions while keeping all CV content editable on Free.
        </ProFeatureNotice>
      )}
      <TextField label="Text to improve" multiline minRows={4} fullWidth size="small" value={text} onChange={(e) => setText(e.target.value)}
        placeholder="Paste a summary, a bullet, or a whole section…" />
      <TextField label="Target role (optional)" size="small" fullWidth value={role} onChange={(e) => setRole(e.target.value)} />
      <Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}>
        {AI_ACTIONS.map((a) => (
          <Button key={a.key} size="small" variant="outlined" disabled={busy || !canUseAi} startIcon={<AutoFixHighIcon />} onClick={() => run(a.key)}>{canUseAi ? a.label : "Pro required"}</Button>
        ))}
      </Box>
      {result && (
        <Paper variant="outlined" sx={{ p: 1.5 }}>
          <Stack direction="row" alignItems="center" justifyContent="space-between" sx={{ mb: 0.5 }}>
            <Typography variant="subtitle2" sx={{ fontWeight: 800 }}>Suggestion</Typography>
            <Button size="small" startIcon={<ContentCopyIcon />} onClick={() => { navigator.clipboard?.writeText(result); toast("Copied.", "success"); }}>Copy</Button>
          </Stack>
          <Typography variant="body2" sx={{ whiteSpace: "pre-wrap" }}>{result}</Typography>
        </Paper>
      )}
    </Stack>
  );
}

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();
}
