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

import BaseButton from "../../../components/base/BaseButton.vue";
import BaseCheckbox from "../../../components/base/BaseCheckbox.vue";
import BaseDraggable from "../../../components/base/BaseDraggable.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";

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

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

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 pricingOptions = [
  { label: "One-time",    value: "one_time" },
  { label: "Weekly",      value: "weekly" },
  { label: "Monthly",     value: "monthly" },
  { label: "Hourly",      value: "hourly" },
  { label: "Custom unit", value: "custom_unit" },
];

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

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

const catalogDialog = ref(false);
const catalogRows = ref([]);
const catalogSearch = ref("");
const activePick = ref({ si: 0, ri: 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([]);

let saveTimer = null;
let dirtyDuringSave = false; // set when the model changes while a save is in-flight

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

function normalizePayload(src) {
  const m = JSON.parse(JSON.stringify(src));
  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 ?? "<p></p>",
      custom_unit_label: row.custom_unit_label ?? "",
      sort_order: ri,
    })),
  }));
  stampKeys(m);
  return m;
}

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

function scheduleSave() {
  if (!isEditable()) return;
  if (saveTimer) clearTimeout(saveTimer);
  saveTimer = setTimeout(() => { saveTimer = null; persist(); }, 700);
}

watch(
  () => (model.value ? JSON.stringify(model.value) : ""),
  () => {
    if (saving.value) dirtyDuringSave = true;
    scheduleSave();
  },
);

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

async function persist() {
  if (!model.value || !isEditable()) return;
  saving.value = true;
  dirtyDuringSave = false;
  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,
      sections: p.sections.map((s) => ({
        title: s.title,
        description_html: s.description_html,
        sort_order: s.sort_order,
        rows: s.rows.map((r) => ({
          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,
          offer_service_catalog_item_id: r.offer_service_catalog_item_id ?? null,
        })),
      })),
    });
    if (!dirtyDuringSave) {
      // Nothing changed while we were saving — safe to sync the full response
      // (picks up server-assigned IDs, computed totals, status changes).
      model.value = normalizePayload(res);
    } else {
      // The user edited the model while this save was in-flight (e.g. added a
      // section).  Overwriting would discard those edits.  Only merge the
      // server-computed fields that the client never owns.
      const fresh = normalizePayload(res);
      if (model.value) model.value.totals = fresh.totals ?? {};
    }
  } finally {
    saving.value = false;
    dirtyDuringSave = false;
  }
}

function addSection() {
  model.value?.sections.push({
    title: `Section ${(model.value.sections.length || 0) + 1}`,
    description_html: "",
    rows: [],
  });
  stampKeys(model.value);
  scheduleSave();
}

function addRow(si) {
  const sec = model.value?.sections?.[si];
  if (!sec) return;
  sec.rows.push({
    description_html: "<p>Line item</p>",
    pricing_type: "one_time",
    quantity: 1,
    unit_price: 0,
    discount_type: "none",
    discount_value: 0,
    vat_rate: vatChoices.value[0] ?? 21,
    custom_unit_label: "",
  });
  stampKeys(model.value);
  scheduleSave();
}

function removeRow(si, ri) {
  model.value?.sections?.[si]?.rows?.splice(ri, 1);
  scheduleSave();
}

function removeSection(si) {
  model.value?.sections?.splice(si, 1);
  scheduleSave();
}

async function openCatalog(si, ri) {
  activePick.value = { si, ri };
  catalogSearch.value = "";
  await loadCatalog();
  catalogDialog.value = true;
}

async function loadCatalog() {
  const res = await listCatalog({ search: catalogSearch.value || undefined, per_page: 80, include_inactive: false });
  catalogRows.value = res?.data ?? [];
}

function applyCatalogItem(item) {
  const { si, ri } = activePick.value;
  const row = model.value.sections[si].rows[ri];
  row.description_html = item.default_description?.trim() ? String(item.default_description) : `<p>${item.name}</p>`;
  row.pricing_type = item.default_pricing_type;
  row.unit_price = item.default_unit_price;
  row.quantity = item.default_quantity;
  row.vat_rate = item.default_vat_rate;
  row.custom_unit_label = item.default_custom_unit_label ?? "";
  row.offer_service_catalog_item_id = item.id;
  catalogDialog.value = false;
  scheduleSave();
}

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 onCustomerChange() {
  contactOptions.value = [];
  if (!model.value?.customer_id) { model.value.contact_id = null; scheduleSave(); 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);
  }
  scheduleSave();
}

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

const vatOptions = computed(() => vatChoices.value.map((x) => ({ label: `${x}%`, value: x })));

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

function sectionTotal(sec) {
  return (sec.rows ?? []).reduce((sum, row) => sum + rowTotal(row), 0);
}

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

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));
    await loadCustomers();
    await refresh();
    if (model.value?.customer_id) await onCustomerChange();
  } 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");
  }
});
</script>

<template>
  <BaseIndex v-if="model" 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="saving" class="text-xs text-surface-400 animate-pulse shrink-0">Saving…</span>
      </div>

      <div class="flex items-center gap-2 shrink-0">
        <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>

    <!-- ── Three-column layout ─────────────────────────────────────────── -->
    <div class="flex">

      <!-- Left: Chapter navigation ──────────────────────────────────── -->
      <nav class="hidden lg:flex flex-col w-52 shrink-0 sticky top-14 self-start max-h-[calc(100vh-3.5rem)] overflow-y-auto border-r border-surface-100 dark:border-surface-800 bg-surface-50/80 dark:bg-surface-900/50 py-5">
        <div class="px-4 mb-3 text-[10px] font-bold uppercase tracking-widest text-surface-400">Contents</div>

        <a
          href="#offer-intro"
          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 dark:hover:bg-surface-800 rounded-lg transition-colors cursor-pointer"
          @click.prevent="scrollToSection('offer-intro')"
        >
          <i class="pi pi-align-left text-[11px] text-surface-400 shrink-0" />
          <span class="truncate">Introduction</span>
        </a>

        <a
          v-for="(sec, si) in model.sections"
          :key="sec._key ?? si"
          :href="`#offer-sec-${si}`"
          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 dark:hover:bg-surface-800 rounded-lg transition-colors cursor-pointer"
          @click.prevent="scrollToSection(`offer-sec-${si}`)"
        >
          <i class="pi pi-list text-[11px] text-surface-400 shrink-0" />
          <span class="truncate">{{ sec.title || `Section ${si + 1}` }}</span>
        </a>

        <div v-if="isEditable() && !preview" class="mt-3 mx-2 border-t border-surface-100 dark:border-surface-800 pt-3">
          <button
            type="button"
            class="flex items-center gap-2 w-full px-3 py-2 text-sm text-surface-400 hover:text-primary-600 dark:hover:text-primary-400 hover:bg-white dark:hover:bg-surface-800 rounded-lg transition-colors"
            @click="addSection"
          >
            <i class="pi pi-plus text-[11px]" />
            Add section
          </button>
        </div>
      </nav>

      <!-- Center: Document canvas ────────────────────────────────────── -->
      <main class="flex-1 min-w-0 bg-surface-100 dark:bg-surface-950 py-8 px-4 lg:px-8">
        <div class="max-w-[54rem] mx-auto flex flex-col gap-5">

          <!-- Offer title card -->
          <div class="bg-white dark:bg-surface-900 rounded-2xl shadow-sm border border-surface-100 dark:border-surface-800 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>
                <h1 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>

          <!-- Introduction section -->
          <section
            id="offer-intro"
            class="bg-white dark:bg-surface-900 rounded-2xl shadow-sm border border-surface-100 dark:border-surface-800 overflow-hidden scroll-mt-16"
          >
            <div class="flex items-center px-8 md:px-12 py-4 border-b border-surface-50 dark:border-surface-800">
              <h2 class="text-base font-semibold text-surface-900 dark:text-surface-0 flex-1">Introduction</h2>
            </div>
            <div class="px-8 md:px-12 py-7">
              <div v-if="preview" class="prose dark:prose-invert max-w-none min-h-[6rem]" v-html="model.intro_html ?? ''" />
              <BaseEditor
                v-else
                v-model="model.intro_html"
                :disabled="!isEditable()"
                :minimal="true"
                editor-style="min-height: 180px"
                @update:model-value="scheduleSave"
              />
            </div>
          </section>

          <!-- Sections (chapters) -->
          <BaseDraggable
            v-model="model.sections"
            item-key="_key"
            handle=".drag-sec"
            :disabled="!isEditable() || preview"
            @end="scheduleSave"
          >
            <template #item="{ element: sec, index: si }">
              <section
                :id="`offer-sec-${si}`"
                class="bg-white dark:bg-surface-900 rounded-2xl shadow-sm border border-surface-100 dark:border-surface-800 overflow-hidden scroll-mt-16 mb-5"
              >
                <!-- Section header -->
                <div class="flex items-center gap-3 px-8 md:px-12 py-4 border-b border-surface-50 dark:border-surface-800">
                  <button
                    v-if="isEditable() && !preview"
                    type="button"
                    class="drag-sec cursor-grab text-surface-300 hover:text-surface-500 transition-colors shrink-0"
                    title="Drag to reorder"
                  >
                    <BaseIcon name="bars" class="text-sm" />
                  </button>
                  <input
                    v-model="sec.title"
                    :disabled="preview || !isEditable()"
                    class="flex-1 min-w-0 text-base font-semibold bg-transparent border-0 outline-none text-surface-900 dark:text-surface-0 placeholder-surface-300"
                    placeholder="Section title…"
                    @input="scheduleSave"
                  />
                  <button
                    v-if="isEditable() && !preview && model.sections.length > 1"
                    type="button"
                    class="p-1.5 rounded-lg text-surface-300 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-950/20 transition-colors shrink-0"
                    @click="removeSection(si)"
                  >
                    <BaseIcon name="trash" class="text-xs" />
                  </button>
                </div>

                <!-- WYSIWYG section content -->
                <div class="px-8 md:px-12 py-6" :class="sec.rows?.length ? 'border-b border-surface-50 dark:border-surface-800' : ''">
                  <div
                    v-if="preview"
                    class="prose dark:prose-invert max-w-none min-h-[2rem]"
                    v-html="sec.description_html || ''"
                  />
                  <BaseEditor
                    v-else
                    v-model="sec.description_html"
                    :disabled="!isEditable()"
                    :minimal="true"
                    :placeholder="'Write section content…'"
                    editor-style="min-height: 80px"
                    @update:model-value="scheduleSave"
                  />
                </div>

                <!-- Pricing block (only shown when rows exist or in edit mode) -->
                <template v-if="sec.rows?.length || (isEditable() && !preview)">
                  <!-- Pricing block header -->
                  <div class="flex items-center justify-between px-8 md:px-12 py-3 bg-surface-50/70 dark:bg-surface-800/40 border-t border-surface-100 dark:border-surface-700">
                    <span class="text-xs font-bold uppercase tracking-widest text-surface-400">Pricing</span>
                    <button
                      v-if="isEditable() && !preview"
                      type="button"
                      class="inline-flex items-center gap-1.5 px-3 py-1 text-xs font-medium text-primary-600 dark:text-primary-400 hover:bg-primary-50 dark:hover:bg-primary-950/30 rounded-lg transition-colors"
                      @click="addRow(si)"
                    >
                      <i class="pi pi-plus text-[10px]" />
                      Add row
                    </button>
                  </div>

                  <!-- Table column headers (only when rows exist) -->
                  <template v-if="sec.rows?.length">
                    <div
                      class="grid items-center px-8 md:px-12 py-2.5 bg-surface-50/40 dark:bg-surface-800/20 border-b border-surface-100 dark:border-surface-800 text-[11px] font-semibold uppercase tracking-wider text-surface-400"
                      :class="isEditable() && !preview
                        ? 'grid-cols-[1fr_5rem_9rem_5rem_8rem_3.5rem]'
                        : 'grid-cols-[1fr_5rem_9rem_5rem_8rem]'"
                    >
                      <div>Description</div>
                      <div class="text-right">Qty</div>
                      <div class="text-right">Unit price</div>
                      <div class="text-right">VAT</div>
                      <div class="text-right">Total</div>
                      <div v-if="isEditable() && !preview" />
                    </div>

                    <!-- Draggable rows -->
                    <BaseDraggable
                      v-model="sec.rows"
                      item-key="_key"
                      handle=".drag-row"
                      :disabled="!isEditable() || preview"
                      @end="scheduleSave"
                    >
                      <template #item="{ element: row, index: ri }">
                        <div
                          class="group grid items-start px-8 md:px-12 py-4 border-b border-surface-50 dark:border-surface-800/60 hover:bg-surface-50/50 dark:hover:bg-surface-800/20 transition-colors"
                          :class="isEditable() && !preview
                            ? 'grid-cols-[1fr_5rem_9rem_5rem_8rem_3.5rem]'
                            : 'grid-cols-[1fr_5rem_9rem_5rem_8rem]'"
                        >
                          <!-- Description -->
                          <div class="min-w-0 pr-4">
                            <div
                              v-if="preview"
                              class="prose prose-sm dark:prose-invert max-w-none"
                              v-html="row.description_html"
                            />
                            <BaseEditor
                              v-else
                              v-model="row.description_html"
                              :disabled="!isEditable()"
                              :minimal="true"
                              editor-style="min-height: 40px"
                              @update:model-value="scheduleSave"
                            />
                            <!-- Row meta: type, catalog, discount -->
                            <div v-if="!preview" class="flex flex-wrap items-center gap-1.5 mt-1.5">
                              <BaseSelect
                                v-model="row.pricing_type"
                                :options="pricingOptions"
                                option-label="label"
                                option-value="value"
                                :show-clear="false"
                                :fluid="false"
                                class="w-28"
                                :disabled="!isEditable()"
                                @change="scheduleSave"
                              />
                              <button
                                type="button"
                                class="inline-flex items-center gap-1 px-2 py-0.5 text-xs text-surface-500 hover:text-primary-600 dark:hover:text-primary-400 hover:bg-surface-100 dark:hover:bg-surface-800 rounded-md transition-colors"
                                @click="openCatalog(si, ri)"
                              >
                                <i class="pi pi-search text-[10px]" />
                                Catalog
                              </button>
                              <span class="text-surface-200 dark:text-surface-700 select-none">·</span>
                              <BaseSelect
                                v-model="row.discount_type"
                                :options="discountOptions"
                                option-label="label"
                                option-value="value"
                                :show-clear="false"
                                :fluid="false"
                                class="w-28"
                                :disabled="!isEditable()"
                                @change="scheduleSave"
                              />
                              <BaseInputNumber
                                v-if="row.discount_type !== 'none'"
                                v-model="row.discount_value"
                                :use-grouping="false"
                                :disabled="!isEditable()"
                                placeholder="Amount"
                                class="w-20"
                                @update:model-value="scheduleSave"
                              />
                              <BaseInputText
                                v-if="row.pricing_type === 'custom_unit'"
                                v-model="row.custom_unit_label"
                                placeholder="Unit label"
                                class="w-24"
                                :disabled="!isEditable()"
                                @update:model-value="scheduleSave"
                              />
                            </div>
                            <div v-else-if="row.discount_type !== 'none'" class="mt-1">
                              <span class="text-xs text-orange-600 dark:text-orange-400 bg-orange-50 dark:bg-orange-950/30 px-2 py-0.5 rounded-md font-medium">
                                Discount: {{ row.discount_type === "percent" ? `${row.discount_value}%` : formatEuro(row.discount_value) }}
                              </span>
                            </div>
                          </div>

                          <!-- Qty -->
                          <div class="text-right pt-0.5">
                            <BaseInputNumber
                              v-if="!preview"
                              v-model="row.quantity"
                              :use-grouping="false"
                              :disabled="!isEditable()"
                              class="w-full"
                              @update:model-value="scheduleSave"
                            />
                            <span v-else class="text-sm tabular-nums">{{ row.quantity }}</span>
                          </div>

                          <!-- Unit price -->
                          <div class="text-right pt-0.5">
                            <BaseInputNumber
                              v-if="!preview"
                              v-model="row.unit_price"
                              mode="decimal"
                              :use-grouping="false"
                              :max-fraction-digits="2"
                              :disabled="!isEditable()"
                              class="w-full"
                              @update:model-value="scheduleSave"
                            />
                            <span v-else class="text-sm tabular-nums">{{ formatEuro(row.unit_price) }}</span>
                          </div>

                          <!-- VAT -->
                          <div class="text-right pt-0.5">
                            <BaseSelect
                              v-if="!preview"
                              v-model="row.vat_rate"
                              :options="vatOptions"
                              option-label="label"
                              option-value="value"
                              :show-clear="false"
                              :fluid="false"
                              class="w-full"
                              :disabled="!isEditable()"
                              @change="scheduleSave"
                            />
                            <span v-else class="text-sm tabular-nums text-surface-500">{{ row.vat_rate }}%</span>
                          </div>

                          <!-- Row total -->
                          <div class="text-right pt-2">
                            <span class="text-sm font-semibold text-surface-900 dark:text-surface-0 tabular-nums">
                              {{ formatEuro(rowTotal(row)) }}
                            </span>
                          </div>

                          <!-- Row actions -->
                          <div
                            v-if="isEditable() && !preview"
                            class="flex items-start justify-end gap-0.5 pt-1 opacity-0 group-hover:opacity-100 transition-opacity"
                          >
                            <button type="button" class="drag-row cursor-grab p-1.5 rounded text-surface-300 hover:text-surface-600 hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors">
                              <BaseIcon name="bars" class="text-xs" />
                            </button>
                            <button type="button" class="p-1.5 rounded text-surface-300 hover:text-red-500 hover:bg-red-50 dark:hover:bg-red-950/20 transition-colors" @click="removeRow(si, ri)">
                              <BaseIcon name="trash" class="text-xs" />
                            </button>
                          </div>
                        </div>
                      </template>
                    </BaseDraggable>

                    <!-- Section subtotal -->
                    <div
                      class="grid items-center px-8 md:px-12 py-4 bg-surface-50/60 dark:bg-surface-800/30 border-t-2 border-surface-100 dark:border-surface-700"
                      :class="isEditable() && !preview
                        ? 'grid-cols-[1fr_5rem_9rem_5rem_8rem_3.5rem]'
                        : 'grid-cols-[1fr_5rem_9rem_5rem_8rem]'"
                    >
                      <div class="col-span-4 text-right text-xs font-semibold uppercase tracking-wide text-surface-400 pr-4">
                        Section subtotal
                      </div>
                      <div class="text-right">
                        <span class="text-sm font-bold text-surface-900 dark:text-surface-0 tabular-nums">
                          {{ formatEuro(sectionTotal(sec)) }}
                        </span>
                      </div>
                      <div v-if="isEditable() && !preview" />
                    </div>
                  </template>

                  <!-- Empty pricing state -->
                  <div v-else-if="isEditable() && !preview" class="px-8 md:px-12 py-5 text-center">
                    <button
                      type="button"
                      class="inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-primary-600 dark:text-primary-400 border border-primary-200 dark:border-primary-700 rounded-xl hover:bg-primary-50 dark:hover:bg-primary-950/30 transition-colors"
                      @click="addRow(si)"
                    >
                      <i class="pi pi-plus text-xs" />
                      Add first line item
                    </button>
                  </div>
                </template>
              </section>
            </template>
          </BaseDraggable>

          <!-- Add section button -->
          <button
            v-if="isEditable() && !preview"
            type="button"
            class="flex items-center justify-center gap-2 w-full py-5 rounded-2xl border-2 border-dashed border-surface-200 dark:border-surface-700 text-sm font-medium text-surface-400 hover:border-primary-300 dark:hover:border-primary-700 hover:text-primary-500 dark:hover:text-primary-400 transition-colors"
            @click="addSection"
          >
            <i class="pi pi-plus" />
            Add section
          </button>

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

      <!-- Right: Metadata sidebar ─────────────────────────────────────── -->
      <aside class="hidden xl:flex flex-col w-80 shrink-0 sticky top-14 self-start max-h-[calc(100vh-3.5rem)] overflow-y-auto border-l border-surface-100 dark:border-surface-800 bg-surface-0 dark:bg-surface-900">
        <div class="divide-y divide-surface-100 dark:divide-surface-800">

          <!-- 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">
              <BaseInputText
                v-model="model.subject"
                label="Subject"
                :disabled="!isEditable()"
                @update:model-value="scheduleSave"
              />
              <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="scheduleSave"
              />
              <BaseInputText
                v-model="model.valid_until"
                label="Valid until"
                type="date"
                :disabled="!isEditable()"
                @change="scheduleSave"
              />
            </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="scheduleSave"
              />
              <BaseInputNumber
                v-if="model.discount_type !== 'none'"
                v-model="model.discount_value"
                label="Amount"
                :use-grouping="false"
                :disabled="!isEditable()"
                @update:model-value="scheduleSave"
              />
            </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="scheduleSave"
              />
              <BaseInputText
                v-if="model.is_template"
                v-model="model.template_name"
                label="Template name"
                @update:model-value="scheduleSave"
              />
            </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>

    <!-- ── Catalog modal ───────────────────────────────────────────────── -->
    <BaseModal v-model:visible="catalogDialog" title="Pick from catalog" width="36rem" @show="loadCatalog">
      <div class="flex gap-2 mb-4">
        <BaseInputText v-model="catalogSearch" placeholder="Search catalog…" class="flex-1" @keyup.enter="loadCatalog" />
        <BaseButton label="Search" icon="search" variant="secondary" @click="loadCatalog" />
      </div>
      <div class="max-h-80 overflow-auto flex flex-col gap-0.5">
        <button
          v-for="item in catalogRows"
          :key="item.id"
          type="button"
          class="flex items-start gap-3 w-full text-left px-3 py-2.5 rounded-xl hover:bg-surface-100 dark:hover:bg-surface-800 transition-colors"
          @click="applyCatalogItem(item)"
        >
          <div class="flex-1 min-w-0">
            <div class="font-medium text-surface-900 dark:text-surface-0 text-sm">{{ item.name }}</div>
            <div v-if="item.default_unit_price" class="text-xs text-surface-500 mt-0.5">
              {{ formatEuro(item.default_unit_price) }} · {{ item.default_pricing_type }}
            </div>
          </div>
        </button>
        <p v-if="!catalogRows.length" class="text-center text-sm text-surface-400 py-8">No catalog items found.</p>
      </div>
    </BaseModal>

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