<script setup>
import { computed, onMounted, ref } from "vue";
import { useRoute, useRouter } from "vue-router";

import BaseButton from "../../../components/base/BaseButton.vue";
import BaseCheckbox from "../../../components/base/BaseCheckbox.vue";
import BaseEditor from "../../../components/base/BaseEditor.vue";
import BaseIcon from "../../../components/base/BaseIcon.vue";
import BaseIndex from "../../../components/base/BaseIndex.vue";
import BaseInputNumber from "../../../components/base/BaseInputNumber.vue";
import BaseInputText from "../../../components/base/BaseInputText.vue";
import BaseModal from "../../../components/base/BaseModal.vue";
import BaseSelect from "../../../components/base/BaseSelect.vue";
import BaseStatusBadge from "../../../components/base/BaseStatusBadge.vue";
import { useBaseToast } from "../../../composables/useBaseToast.js";
import { api, ensureCsrf } from "../../../services/api.js";
import { useOffersApi } from "../composables/useOffersApi.js";
import OfferPricingPanel from "./OfferPricingPanel.vue";

const route = useRoute();
const router = useRouter();
const toast = useBaseToast();

const {
  getOffer,
  updateOffer,
  sendOffer,
  editorMeta,
  downloadPdf,
  convertToProject,
} = useOffersApi();

const SECTION_ROLE = {
  CHAPTER: "chapter",
  PRICING_ROOT: "pricing_root",
  PRICING_CATEGORY: "pricing_category",
};

const OFFER_STATUS_BADGES = {
  draft: { label: "Draft", class: "bg-surface-100 text-surface-600 dark:bg-surface-700 dark:text-surface-300" },
  sent:  { label: "Sent",  class: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300" },
  won:   { label: "Won",   class: "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300" },
  lost:  { label: "Lost",  class: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300" },
};

const offerId = computed(() => String(route.params.id ?? ""));
const model = ref(null);
const preview = ref(false);
const saving = ref(false);
const isDirty = ref(false);
const rightPanelOpen = ref(true);
const backgroundPresets = ref([]);
const backgroundCustomUrls = ref([]);

const pricingOptions = [
  { label: "Eenmalig", value: "one_time" },
  { label: "Wekelijks", value: "weekly" },
  { label: "Maandelijks", value: "monthly" },
  { label: "Uurtarief", value: "hourly" },
  { label: "Aangepaste eenheid", value: "custom_unit" },
];

const discountOptions = [
  { label: "None",    value: "none" },
  { label: "Percent", value: "percent" },
  { label: "Fixed",   value: "fixed" },
];

const vatChoices = ref([21, 9, 0]);

const sendDialog = ref(false);
const sendChannels = ref({ link: true, pdf: false, email: false });
const emailTo = ref("");
const emailSubject = ref("");
const converting = ref(false);

const customerOptions = ref([]);
const contactOptions = ref([]);

function markDirty() {
  if (!isEditable()) {
    return;
  }
  isDirty.value = true;
}

function stampKeys(m) {
  (m?.sections ?? []).forEach((sec, si) => {
    sec._key ??= sec.id ?? `ns-${si}-${crypto.randomUUID()}`;
    (sec.children ?? []).forEach((child, ci) => {
      child._key ??= child.id ?? `nch-${si}-${ci}-${crypto.randomUUID()}`;
      (child.rows ?? []).forEach((row, ri) => {
        row._key ??= row.id ?? `nr-ch-${ci}-${ri}-${crypto.randomUUID()}`;
      });
    });
    (sec.rows ?? []).forEach((row, ri) => {
      row._key ??= row.id ?? `nr-${si}-${ri}-${crypto.randomUUID()}`;
    });
  });
}

function defaultPricingRow() {
  return {
    description_html: "",
    pricing_type: "one_time",
    quantity: 1,
    unit_price: 0,
    discount_type: "none",
    discount_value: 0,
    vat_rate: vatChoices.value[0] ?? 21,
    custom_unit_label: "",
  };
}

/** Merge legacy chapter sections into intro_html, harvest stray line rows, keep only pricing_root. */
function mergeLegacyChaptersIntoIntro(m) {
  const sections = m.sections ?? [];
  const chapters = sections.filter((s) => s.section_role !== SECTION_ROLE.PRICING_ROOT);
  if (chapters.length === 0) {
    return;
  }
  const harvested = [];
  chapters.forEach((sec) => {
    (sec.rows ?? []).forEach((r) => {
      harvested.push(JSON.parse(JSON.stringify(r)));
    });
  });
  m._harvestedPricingRows = harvested;
  const chunks = [];
  const intro = (m.intro_html ?? "").trim();
  if (intro) {
    chunks.push(intro);
  }
  for (const ch of chapters) {
    const h = (ch.description_html ?? "").trim();
    if (h) {
      chunks.push(h);
    }
  }
  m.intro_html = chunks.length
    ? chunks.join('<hr class="my-8 border-surface-200 dark:border-surface-600" />')
    : (m.intro_html ?? "");
  m.sections = sections.filter((s) => s.section_role === SECTION_ROLE.PRICING_ROOT);
}

/** Ensure a single pricing_root block; use rows harvested from removed chapter sections when present. */
function ensurePricingStructure(m) {
  let sections = m.sections ?? [];
  const harvested = m._harvestedPricingRows ?? [];
  const hasRoot = sections.some((s) => s.section_role === SECTION_ROLE.PRICING_ROOT);
  if (!hasRoot) {
    sections = [
      {
        section_role: SECTION_ROLE.PRICING_ROOT,
        title: "Prijsindicatie",
        description_html: "",
        sort_order: 0,
        children: [
          {
            section_role: SECTION_ROLE.PRICING_CATEGORY,
            title: "General",
            description_html: "",
            rows: harvested.length
              ? harvested.map((r, i) => ({ ...defaultPricingRow(), ...r, sort_order: i }))
              : [{ ...defaultPricingRow(), sort_order: 0 }],
          },
        ],
      },
    ];
  }
  sections = sections.filter((s) => s.section_role === SECTION_ROLE.PRICING_ROOT);
  sections.forEach((s, si) => {
    s.sort_order = si;
    if (s.section_role !== SECTION_ROLE.PRICING_ROOT) {
      return;
    }
    if (!s.children?.length) {
      s.children = [
        {
          section_role: SECTION_ROLE.PRICING_CATEGORY,
          title: "General",
          description_html: "",
          rows: [{ ...defaultPricingRow(), sort_order: 0 }],
        },
      ];
    }
  });
  m.sections = sections;
}

function normalizePayload(src) {
  const m = JSON.parse(JSON.stringify(src));
  mergeLegacyChaptersIntoIntro(m);
  m.sections = (m.sections ?? []).map((sec, si) => ({
    ...sec,
    title: sec.title ?? `Section ${si + 1}`,
    description_html: sec.description_html ?? "",
    sort_order: si,
    rows: (sec.rows ?? []).map((row, ri) => ({
      ...row,
      pricing_type: row.pricing_type ?? "one_time",
      discount_type: row.discount_type ?? "none",
      discount_value: Number(row.discount_value ?? 0),
      quantity: Number(row.quantity ?? 1),
      unit_price: Number(row.unit_price ?? 0),
      vat_rate: Number(row.vat_rate ?? 21),
      description_html: row.description_html ?? "",
      custom_unit_label: row.custom_unit_label ?? "",
      sort_order: ri,
    })),
    children: (sec.children ?? []).map((child, ci) => ({
      ...child,
      title: child.title ?? `Category ${ci + 1}`,
      description_html: child.description_html ?? "",
      sort_order: ci,
      section_role: child.section_role ?? SECTION_ROLE.PRICING_CATEGORY,
      rows: (child.rows ?? []).map((row, ri) => ({
        ...row,
        pricing_type: row.pricing_type ?? "one_time",
        discount_type: row.discount_type ?? "none",
        discount_value: Number(row.discount_value ?? 0),
        quantity: Number(row.quantity ?? 1),
        unit_price: Number(row.unit_price ?? 0),
        vat_rate: Number(row.vat_rate ?? 21),
        description_html: row.description_html ?? "",
        custom_unit_label: row.custom_unit_label ?? "",
        sort_order: ri,
      })),
    })),
  }));
  ensurePricingStructure(m);
  delete m._harvestedPricingRows;
  stampKeys(m);
  return m;
}

function isEditable() {
  if (!model.value) return false;
  if (model.value.is_template) return true;
  return model.value.status === "draft";
}

async function refresh() {
  const data = await getOffer(offerId.value);
  model.value = normalizePayload(data);
  isDirty.value = false;
}

async function persist() {
  if (!model.value || !isEditable()) {
    return;
  }
  saving.value = true;
  try {
    const p = normalizePayload(model.value);
    const res = await updateOffer(offerId.value, {
      subject: p.subject,
      intro_html: p.intro_html,
      customer_id: p.customer_id ?? null,
      contact_id: p.contact_id ?? null,
      valid_until: p.valid_until || null,
      discount_type: p.discount_type ?? "none",
      discount_value: p.discount_value ?? 0,
      is_template: !!p.is_template,
      template_name: p.template_name ?? null,
      document_background_url: p.is_template ? null : (p.document_background_url || null),
      sections: p.sections.map((s, si) => mapSectionToApi(s, si)),
    });
    model.value = normalizePayload(res);
    isDirty.value = false;
  } finally {
    saving.value = false;
  }
}

const canvasBackgroundUrl = computed(() => {
  if (!model.value || model.value.is_template) {
    return null;
  }
  const u = model.value.document_background_url;
  return typeof u === "string" && u.length > 0 ? u : null;
});

const backgroundGridItems = computed(() => {
  const out = [];
  for (const pr of backgroundPresets.value) {
    if (pr && pr.url) {
      out.push({ url: pr.url, label: pr.label || "Preset", kind: "preset" });
    }
  }
  for (const url of backgroundCustomUrls.value) {
    if (url) {
      out.push({ url, label: "Custom", kind: "custom" });
    }
  }
  return out;
});

function setDocumentBackground(url) {
  if (!model.value || model.value.is_template) {
    return;
  }
  model.value.document_background_url = url && url.length > 0 ? url : null;
  markDirty();
}

function mapRowToApi(r, ri) {
  return {
    description_html: r.description_html,
    pricing_type: r.pricing_type,
    custom_unit_label: r.custom_unit_label || null,
    quantity: r.quantity,
    unit_price: r.unit_price,
    discount_type: r.discount_type,
    discount_value: r.discount_value,
    vat_rate: r.vat_rate,
    sort_order: r.sort_order ?? ri,
    offer_service_catalog_item_id: r.offer_service_catalog_item_id ?? null,
  };
}

function mapSectionToApi(s, si) {
  const base = {
    title: s.title,
    description_html: s.description_html,
    sort_order: s.sort_order ?? si,
    section_role: s.section_role ?? SECTION_ROLE.CHAPTER,
  };
  if (s.section_role === SECTION_ROLE.PRICING_ROOT) {
    return {
      ...base,
      rows: [],
      children: (s.children ?? []).map((c, ci) => ({
        title: c.title,
        description_html: c.description_html,
        sort_order: c.sort_order ?? ci,
        section_role: SECTION_ROLE.PRICING_CATEGORY,
        rows: (c.rows ?? []).map((r, ri) => mapRowToApi(r, ri)),
      })),
    };
  }
  return {
    ...base,
    rows: (s.rows ?? []).map((r, ri) => mapRowToApi(r, ri)),
  };
}

const totals = computed(() => model.value?.totals ?? {});

const CUSTOMER_LIST_TOAST_KEY = "maestro.offers.customerListUnavailableToast";

async function loadCustomers() {
  try {
    await ensureCsrf();
    const { data } = await api.get("/api/customers", { params: { per_page: 100, sort_by: "name" } });
    customerOptions.value = (data?.data ?? []).map((c) => ({ label: c.name, value: c.id }));
    try { if (typeof sessionStorage !== "undefined") sessionStorage.removeItem(CUSTOMER_LIST_TOAST_KEY); } catch { /**/ }
  } catch {
    customerOptions.value = [];
    try {
      if (typeof sessionStorage !== "undefined" && !sessionStorage.getItem(CUSTOMER_LIST_TOAST_KEY)) {
        sessionStorage.setItem(CUSTOMER_LIST_TOAST_KEY, "1");
        toast.warn("You can still edit the offer. Enable the Customers module or fix permissions to pick a customer.", "Customer list unavailable", 5000);
      }
    } catch { /**/ }
  }
}

async function loadContactsForCustomer(markDirtyAfter = true) {
  contactOptions.value = [];
  if (!model.value?.customer_id) {
    model.value.contact_id = null;
    if (markDirtyAfter) {
      markDirty();
    }
    return;
  }
  try {
    await ensureCsrf();
    const { data } = await api.get(`/api/customers/${model.value.customer_id}/contacts`);
    contactOptions.value = (data?.data ?? data ?? []).map((c) => ({ label: `${c.first_name} ${c.last_name}`, value: c.id }));
  } catch {
    contactOptions.value = [];
    toast.warn("Contacts could not be loaded", null, 3500);
  }
  if (markDirtyAfter) {
    markDirty();
  }
}

async function onCustomerChange() {
  await loadContactsForCustomer(true);
}

async function onSend() {
  await persist();
  const channels = [];
  if (sendChannels.value.link) channels.push("link");
  if (sendChannels.value.pdf) channels.push("pdf");
  if (sendChannels.value.email) channels.push("email");
  try {
    const res = await sendOffer(offerId.value, {
      channels,
      email: sendChannels.value.email && emailTo.value ? { to: emailTo.value, subject: emailSubject.value || undefined } : undefined,
    });
    toast.success("Offer sent");
    if (res?.public_url) { await navigator.clipboard.writeText(res.public_url); toast.info("Public link copied to clipboard", null, 2200); }
    sendDialog.value = false;
    await refresh();
  } catch { toast.error("Send failed"); }
}

async function onPdf() {
  if (isDirty.value) {
    toast.warn("Save your changes before downloading the PDF.", "Unsaved changes", 4500);
    return;
  }
  const blob = await downloadPdf(offerId.value);
  const url = URL.createObjectURL(blob);
  const a = document.createElement("a");
  a.href = url;
  a.download = `offer-${model.value?.offer_number ?? offerId.value}.pdf`;
  a.click();
  URL.revokeObjectURL(url);
}

async function onConvert() {
  if (isDirty.value) {
    toast.warn("Save your changes first.", "Unsaved changes", 4000);
    return;
  }
  converting.value = true;
  try {
    await convertToProject(offerId.value, { name: model.value?.subject });
    toast.success("Project created from this offer");
    await refresh();
  } catch { toast.error("Conversion failed"); } finally { converting.value = false; }
}

function vatOptionLabel(rate) {
  const r = Number(rate);
  if (r === 21) {
    return "Exclusief hoog btw-tarief (21%)";
  }
  if (r === 9) {
    return "Exclusief laag btw-tarief (9%)";
  }
  if (r === 0) {
    return "Geen btw (0%)";
  }
  return `Exclusief btw (${r}%)`;
}
const vatOptions = computed(() => vatChoices.value.map((x) => ({ label: vatOptionLabel(x), value: x })));

const pricingRootSection = computed(() =>
  model.value?.sections?.find((s) => s.section_role === SECTION_ROLE.PRICING_ROOT),
);

function rowTotal(row) {
  const qty = Number(row.quantity) || 0;
  const price = Number(row.unit_price) || 0;
  let sub = qty * price;
  if (row.discount_type === "percent") sub -= sub * (Number(row.discount_value) || 0) / 100;
  else if (row.discount_type === "fixed") sub -= Number(row.discount_value) || 0;
  return Math.max(0, sub);
}

const euroFmt = new Intl.NumberFormat("nl-NL", { style: "currency", currency: "EUR" });
function formatEuro(val) { return euroFmt.format(val ?? 0); }

const floatingSurface = computed(() =>
  canvasBackgroundUrl.value
    ? "bg-white/88 dark:bg-surface-900/88 backdrop-blur-md shadow-xl ring-1 ring-black/5 dark:ring-white/10 border-surface-200/70 dark:border-surface-700/70"
    : "bg-white dark:bg-surface-900 shadow-sm border-surface-100 dark:border-surface-800",
);

function scrollToSection(id) {
  document.getElementById(id)?.scrollIntoView({ behavior: "smooth", block: "start" });
}

onMounted(async () => {
  try {
    const meta = await editorMeta();
    if (Array.isArray(meta?.vat_rate_presets)) vatChoices.value = meta.vat_rate_presets.map((x) => Number(x));
    backgroundPresets.value = Array.isArray(meta?.document_background_presets) ? meta.document_background_presets : [];
    backgroundCustomUrls.value = Array.isArray(meta?.document_background_custom_urls) ? meta.document_background_custom_urls : [];
    await loadCustomers();
    await refresh();
    if (model.value?.customer_id) {
      await loadContactsForCustomer(false);
    }
  } catch (e) {
    const detail = e?.response?.data?.message || e?.message || "";
    console.error("[OfferEditor] mount failed", e);
    toast.error(detail || "Could not load offer", "Error", 5000);
    router.push("/offers");
  }
});

function onTemplateFlagChange() {
  if (model.value?.is_template) {
    model.value.document_background_url = null;
  }
  markDirty();
}
</script>

<template>
  <BaseIndex v-if="!model" flush hide-header>
    <div
      class="flex min-h-[50vh] flex-col items-center justify-center gap-3 text-surface-500"
      role="status"
      aria-busy="true"
      aria-label="Loading offer"
    >
      <i class="pi pi-spin pi-spinner text-3xl" aria-hidden="true" />
      <span class="text-sm">Loading offer…</span>
    </div>
  </BaseIndex>

  <BaseIndex v-else flush hide-header>

    <!-- ── Top bar ─────────────────────────────────────────────────────── -->
    <div class="sticky top-0 z-20 flex items-center justify-between gap-4 px-5 h-14 border-b border-surface-100 dark:border-surface-800 bg-white/95 dark:bg-surface-900/95 backdrop-blur-sm shrink-0">
      <div class="flex items-center gap-3 min-w-0">
        <button
          type="button"
          class="inline-flex items-center justify-center w-8 h-8 rounded-lg text-surface-500 hover:text-surface-900 dark:hover:text-surface-0 hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors shrink-0"
          @click="router.push('/offers')"
        >
          <BaseIcon name="arrow-left" />
        </button>
        <div class="flex items-center gap-2 min-w-0">
          <span class="font-semibold text-surface-900 dark:text-surface-0 truncate">{{ model.subject || model.offer_number }}</span>
          <span class="text-surface-400 text-sm font-mono shrink-0 hidden sm:inline">{{ model.offer_number }}</span>
          <BaseStatusBadge :value="model.status" :value-map="OFFER_STATUS_BADGES" />
        </div>
        <span
          v-if="isDirty && isEditable()"
          class="text-xs text-amber-600 dark:text-amber-400 font-medium shrink-0"
        >Unsaved</span>
        <span v-else-if="saving" class="text-xs text-surface-400 shrink-0">Saving…</span>
      </div>

      <div class="flex items-center gap-2 shrink-0">
        <BaseButton
          v-if="isEditable()"
          label="Save"
          icon="save"
          size="small"
          :loading="saving"
          :disabled="!isDirty"
          @click="persist"
        />
        <button
          type="button"
          class="inline-flex items-center justify-center w-9 h-9 rounded-lg text-surface-500 hover:text-surface-900 dark:hover:text-surface-0 hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors shrink-0"
          :title="rightPanelOpen ? 'Hide side panel' : 'Show side panel'"
          :aria-expanded="rightPanelOpen"
          @click="rightPanelOpen = !rightPanelOpen"
        >
          <BaseIcon :name="rightPanelOpen ? 'chevron-right' : 'chevron-left'" class="text-sm" />
        </button>
        <button
          type="button"
          class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm font-medium border transition-colors"
          :class="preview
            ? 'bg-primary-50 border-primary-200 text-primary-700 dark:bg-primary-900/20 dark:border-primary-700 dark:text-primary-300'
            : 'bg-surface-50 border-surface-200 text-surface-600 hover:bg-surface-100 dark:bg-surface-800 dark:border-surface-700 dark:text-surface-300'"
          @click="preview = !preview"
        >
          <BaseIcon :name="preview ? 'eye-slash' : 'eye'" class="text-xs" />
          {{ preview ? "Edit" : "Preview" }}
        </button>
        <BaseButton
          v-if="model.status === 'draft' && !model.is_template"
          label="Send offer" icon="send" size="small"
          @click="sendDialog = true"
        />
        <BaseButton label="PDF" icon="download" variant="secondary" outlined size="small" @click="onPdf" />
        <BaseButton
          v-if="model.status === 'won' && !model.hour_project_id"
          label="Convert" icon="briefcase" variant="secondary" size="small"
          :loading="converting" @click="onConvert"
        />
      </div>
    </div>

    <!-- ── Canvas (TOC + document over shared background) + right panel ─ -->
    <div class="flex">
      <div
        class="flex-1 min-w-0 min-h-0 overflow-y-auto bg-surface-100 dark:bg-surface-950"
      >
        <div class="relative min-h-full">
          <div
            v-if="canvasBackgroundUrl"
            class="pointer-events-none absolute inset-0 z-0 min-h-full bg-cover bg-center bg-no-repeat"
            :style="{ backgroundImage: `url(${canvasBackgroundUrl})` }"
            aria-hidden="true"
          />
          <div
            class="relative z-10 flex min-h-full flex-col lg:flex-row lg:items-start"
          >
            <!-- Chapter nav: same scroll area as document so it sits over the BG -->
            <nav
              class="hidden lg:flex flex-col w-52 shrink-0 sticky top-14 z-20 self-start max-h-[calc(100vh-3.5rem)] overflow-y-auto py-6 pl-4 pr-2 lg:pl-6 rounded-r-2xl border border-surface-200/70 dark:border-surface-700/60 bg-white/80 dark:bg-surface-900/70 backdrop-blur-md shadow-lg"
            >
              <div class="px-2 mb-3 text-[10px] font-bold uppercase tracking-widest text-surface-400">Contents</div>

              <a
                href="#offer-body"
                class="flex items-center gap-2 mx-2 px-3 py-2 text-sm text-surface-600 dark:text-surface-400 hover:text-surface-900 dark:hover:text-surface-0 hover:bg-white/90 dark:hover:bg-surface-800 rounded-lg transition-colors cursor-pointer"
                @click.prevent="scrollToSection('offer-body')"
              >
                <BaseIcon name="list" class="text-[11px] text-surface-400 shrink-0" />
                <span class="truncate">Inhoud</span>
              </a>

              <a
                href="#offer-pricing"
                class="flex items-center gap-2 mx-2 px-3 py-2 text-sm text-amber-800 dark:text-amber-200 hover:bg-white/90 dark:hover:bg-surface-800 rounded-lg transition-colors cursor-pointer"
                @click.prevent="scrollToSection('offer-pricing')"
              >
                <BaseIcon name="calculator" class="text-[11px] shrink-0" />
                <span class="truncate">{{ pricingRootSection?.title || "Prijsindicatie" }}</span>
              </a>
            </nav>

            <div class="flex-1 min-w-0 flex flex-col py-8 px-4 lg:px-8">
              <div class="max-w-[54rem] mx-auto w-full flex flex-col gap-5">

          <!-- Offer title card -->
          <div :class="[floatingSurface, 'rounded-2xl border px-8 md:px-12 py-8']">
            <div class="flex items-start justify-between gap-4">
              <div class="min-w-0 flex-1">
                <div class="text-[10px] font-bold uppercase tracking-widest text-surface-400 mb-2">Proposal</div>
                <BaseInputText
                  v-if="!preview"
                  v-model="model.subject"
                  :disabled="!isEditable()"
                  placeholder="Untitled offer"
                  class="!mb-0 [&_.p-inputtext]:text-3xl [&_.p-inputtext]:font-bold [&_.p-inputtext]:leading-tight [&_.p-inputtext]:border-0 [&_.p-inputtext]:bg-transparent [&_.p-inputtext]:p-0 [&_.p-inputtext]:shadow-none"
                  @update:model-value="markDirty"
                />
                <h1
                  v-else
                  class="text-3xl font-bold text-surface-900 dark:text-surface-0 leading-tight"
                >
                  {{ model.subject || "Untitled offer" }}
                </h1>
                <div class="flex flex-wrap items-center gap-x-4 gap-y-1 mt-3 text-sm text-surface-500">
                  <span class="font-mono text-xs bg-surface-50 dark:bg-surface-800 border border-surface-100 dark:border-surface-700 px-2 py-0.5 rounded">{{ model.offer_number }}</span>
                  <span v-if="model.valid_until" class="flex items-center gap-1.5">
                    <i class="pi pi-calendar text-xs" />
                    Valid until {{ model.valid_until }}
                  </span>
                  <span v-if="model.customer_id && customerOptions.find(c => c.value === model.customer_id)" class="flex items-center gap-1.5">
                    <i class="pi pi-building text-xs" />
                    {{ customerOptions.find(c => c.value === model.customer_id)?.label }}
                  </span>
                </div>
              </div>
              <BaseStatusBadge :value="model.status" :value-map="OFFER_STATUS_BADGES" />
            </div>
          </div>

          <!-- Single body block (intro_html) -->
          <section
            id="offer-body"
            :class="[floatingSurface, 'rounded-2xl border overflow-hidden scroll-mt-16 flex flex-col min-h-[min(32rem,55vh)]']"
          >
            <div class="flex items-center px-8 md:px-12 py-4 border-b border-surface-50 dark:border-surface-800 shrink-0">
              <h2 class="text-base font-semibold text-surface-900 dark:text-surface-0 flex-1">Inhoud</h2>
            </div>
            <div class="flex-1 min-h-0 flex flex-col px-8 md:px-12 py-6">
              <div
                v-if="preview"
                class="prose dark:prose-invert max-w-none flex-1 min-h-[12rem]"
                v-html="model.intro_html ?? ''"
              />
              <BaseEditor
                v-else
                v-model="model.intro_html"
                :disabled="!isEditable()"
                class="flex-1 flex flex-col min-h-[24rem]"
                toolbar="document"
                editor-style="min-height: 24rem; flex: 1 1 auto"
                @update:model-value="markDirty"
              />
            </div>
          </section>

          <OfferPricingPanel
            v-if="pricingRootSection"
            :block="pricingRootSection"
            :preview="preview"
            :editable="isEditable()"
            :elevated="!!canvasBackgroundUrl"
            :vat-options="vatOptions"
            :pricing-options="pricingOptions"
            :format-euro="formatEuro"
            :row-total="rowTotal"
            @dirty="markDirty"
          />

          <div class="h-16" />
              </div>
            </div>
          </div>
        </div>
      </div>

      <!-- Right: Metadata sidebar (slide width) ──────────────────────── -->
      <aside
        class="hidden lg:flex flex-col shrink-0 sticky top-14 self-start max-h-[calc(100vh-3.5rem)] border-l border-surface-100 dark:border-surface-800 bg-surface-0 dark:bg-surface-900 transition-[width] duration-300 ease-in-out overflow-hidden"
        :class="rightPanelOpen ? 'w-80' : 'w-0 border-l-0'"
      >
        <div class="w-80 h-full overflow-y-auto divide-y divide-surface-100 dark:divide-surface-800">

          <!-- Page background (non-template offers only) -->
          <div v-if="!model.is_template" class="p-5">
            <div class="text-[10px] font-bold uppercase tracking-widest text-surface-400 mb-3">Page background</div>
            <p class="text-xs text-surface-500 mb-3">
              Shown on this editor, the public offer link, and the PDF. Upload more under Superadmin › Huisstijl.
            </p>
            <button
              type="button"
              class="mb-3 w-full rounded-lg border border-dashed border-surface-200 dark:border-surface-600 py-2 text-xs text-surface-500 hover:border-primary-400 hover:text-primary-600"
              @click="setDocumentBackground(null)"
            >
              No background
            </button>
            <div class="grid grid-cols-2 gap-2">
              <button
                v-for="(item, bi) in backgroundGridItems"
                :key="item.url"
                type="button"
                class="group relative aspect-video rounded-lg overflow-hidden ring-2 ring-transparent focus:outline-none focus:ring-primary-400"
                :class="model.document_background_url === item.url ? 'ring-primary-500' : ''"
                :title="item.label"
                @click="setDocumentBackground(item.url)"
              >
                <img :src="item.url" alt="" class="w-full h-full object-cover" />
                <span class="absolute bottom-0 inset-x-0 bg-black/50 text-[10px] text-white px-1 py-0.5 truncate opacity-0 group-hover:opacity-100">{{ item.label }}</span>
              </button>
            </div>
          </div>

          <!-- Offer details -->
          <div class="p-5">
            <div class="text-[10px] font-bold uppercase tracking-widest text-surface-400 mb-4">Offer details</div>
            <div class="flex flex-col gap-3">
              <BaseSelect
                v-model="model.customer_id"
                label="Customer"
                :options="customerOptions"
                option-label="label"
                option-value="value"
                placeholder="No customer"
                :disabled="!isEditable()"
                @change="onCustomerChange"
              />
              <BaseSelect
                v-model="model.contact_id"
                label="Contact"
                :options="contactOptions"
                option-label="label"
                option-value="value"
                placeholder="No contact"
                :disabled="!isEditable() || !model.customer_id"
                @change="markDirty"
              />
              <BaseInputText
                v-model="model.valid_until"
                label="Valid until"
                type="date"
                :disabled="!isEditable()"
                @change="markDirty"
              />
            </div>
          </div>

          <!-- Discount -->
          <div class="p-5">
            <div class="text-[10px] font-bold uppercase tracking-widest text-surface-400 mb-4">Discount</div>
            <div class="flex flex-col gap-3">
              <BaseSelect
                v-model="model.discount_type"
                label="Type"
                :options="discountOptions"
                option-label="label"
                option-value="value"
                :show-clear="false"
                :disabled="!isEditable()"
                @change="markDirty"
              />
              <BaseInputNumber
                v-if="model.discount_type !== 'none'"
                v-model="model.discount_value"
                label="Amount"
                :use-grouping="false"
                :disabled="!isEditable()"
                @update:model-value="markDirty"
              />
            </div>
          </div>

          <!-- Template -->
          <div class="p-5">
            <div class="text-[10px] font-bold uppercase tracking-widest text-surface-400 mb-4">Template</div>
            <div class="flex flex-col gap-3">
              <BaseCheckbox
                v-model="model.is_template"
                binary
                label="Template library"
                :disabled="model.status !== 'draft'"
                @update:model-value="onTemplateFlagChange"
              />
              <BaseInputText
                v-if="model.is_template"
                v-model="model.template_name"
                label="Template name"
                @update:model-value="markDirty"
              />
            </div>
          </div>

          <!-- Summary / Totals -->
          <div class="p-5">
            <div class="text-[10px] font-bold uppercase tracking-widest text-surface-400 mb-4">Summary</div>
            <div class="rounded-xl border border-surface-100 dark:border-surface-700 overflow-hidden">
              <dl class="text-sm divide-y divide-surface-100 dark:divide-surface-800">
                <div class="flex justify-between px-4 py-3">
                  <dt class="text-surface-500">Subtotal</dt>
                  <dd class="font-medium tabular-nums text-surface-900 dark:text-surface-0">{{ totals.lines_net_subtotal ?? "—" }}</dd>
                </div>
                <div class="flex justify-between px-4 py-3">
                  <dt class="text-surface-500">Discount</dt>
                  <dd class="font-medium tabular-nums text-surface-900 dark:text-surface-0">{{ totals.offer_discount_amount ?? "—" }}</dd>
                </div>
                <div class="flex justify-between px-4 py-3">
                  <dt class="text-surface-500">Excl. VAT</dt>
                  <dd class="font-medium tabular-nums text-surface-900 dark:text-surface-0">{{ totals.total_excl_vat ?? "—" }}</dd>
                </div>
                <div class="flex justify-between px-4 py-3">
                  <dt class="text-surface-500">VAT</dt>
                  <dd class="font-medium tabular-nums text-surface-900 dark:text-surface-0">{{ totals.vat_total ?? "—" }}</dd>
                </div>
                <div class="flex justify-between px-4 py-4 bg-surface-50 dark:bg-surface-800/50">
                  <dt class="font-bold text-base text-surface-900 dark:text-surface-0">Total</dt>
                  <dd class="font-bold text-base tabular-nums text-surface-900 dark:text-surface-0">{{ totals.grand_total ?? "—" }}</dd>
                </div>
              </dl>
            </div>
          </div>
        </div>
      </aside>
    </div>

    <!-- ── Send modal ──────────────────────────────────────────────────── -->
    <BaseModal v-model:visible="sendDialog" title="Send offer" width="32rem">
      <div class="flex flex-col gap-4">
        <BaseCheckbox v-model="sendChannels.link" binary label="Copy public link after send" />
        <BaseCheckbox v-model="sendChannels.pdf" binary label="Expose PDF shortcut (requires login)" />
        <BaseCheckbox v-model="sendChannels.email" binary label="Send via email" />
        <template v-if="sendChannels.email">
          <BaseInputText v-model="emailTo" label="To" placeholder="recipient@example.com" />
          <BaseInputText v-model="emailSubject" label="Subject" placeholder="Your offer from …" />
        </template>
      </div>
      <template #footer-right>
        <BaseButton label="Cancel" variant="secondary" outlined @click="sendDialog = false" />
        <BaseButton label="Send" icon="send" @click="onSend" />
      </template>
    </BaseModal>

  </BaseIndex>
</template>

