import { useState } from 'react';
import { Link, useForm } from '@inertiajs/react';
import { ArrowLeft, Pencil, Settings } from 'lucide-react';
import { Button } from '@/Components/ui/button';
import {
    Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger,
} from '@/Components/ui/dialog';
import { StatusBadge } from '@/Components/Proto/UI/StatusBadge';
import { QuotationHistoryCell } from '@/Components/MenuQuotations/QuotationDetailPage/QuotationHistoryCell';
import { QuotationAuditCell } from '@/Components/MenuQuotations/QuotationDetailPage/QuotationAuditCell';
import { HistoryTimelinePopover } from '@/Components/MenuQuotations/QuotationDetailPage/HistoryTimelinePopover';
import { statusTone } from '@/Components/MenuQuotations/QuotationListPage/quotationStatusTones';
import CompanyTabs from '@/Components/MenuCompanies/CompanyTabs';
import { PrintActionButtons } from './PrintActionButtons';
import { QuotationLinks } from './QuotationLinks';
import { useToast } from '@/Components/Toast';
import { DP_FIELDS, dpBasis, dpRecalc } from '@/lib/quotation/dpCalc';
import { useColumnPrefs } from '@/lib/useColumnPrefs';
import { CustomizeColumnsModal } from '@/Components/Proto/Modals/CustomizeColumnsModal';

/**
 * Line-items table vocabulary. The table used to be nineteen hardcoded <th>/<td> pairs, which
 * meant every user saw every column whether their job needed it or not; it is data-driven now so
 * the ⚙ can hide and reorder them (user 2026-08-24), exactly as the list pages do.
 *
 * Two changes came with it, both asked for:
 *  · "Quotation History" and "History" were two columns whose headers said almost the same thing
 *    and whose cells were each a single icon button. They are ONE column now holding both
 *    buttons — different icon, different tooltip, same heading.
 *  · "Latest from PM & SM" is new, read per LINE from that line's own assignment log.
 */
const LINE_COLUMN_GROUPS = [
    { id: 'identity', label: 'Identity' },
    { id: 'packing', label: 'Quantity & Packing' },
    { id: 'money', label: 'Pricing' },
    { id: 'notes', label: 'Notes & History' },
];
const LINE_COLUMN_DEFS = [
    { id: 'id', label: 'ID', groupId: 'identity', required: true },
    { id: 'status', label: 'Status', groupId: 'identity' },
    { id: 'principal', label: 'Principal', groupId: 'identity' },
    { id: 'product', label: 'Product Name', groupId: 'identity', required: true },
    { id: 'qty', label: 'Qty', groupId: 'packing', num: true },
    { id: 'packId', label: 'Pack ID', groupId: 'packing' },
    { id: 'satuan', label: 'Satuan', groupId: 'packing' },
    { id: 'orderQty', label: 'Order Qty', groupId: 'packing', num: true },
    { id: 'satuanOrder', label: 'Satuan Order', groupId: 'packing' },
    { id: 'usdRate', label: 'USD Rate', groupId: 'money', num: true },
    { id: 'unitUsd', label: 'Unit USD', groupId: 'money', num: true },
    { id: 'totalUsd', label: 'Total USD', groupId: 'money', num: true },
    { id: 'unitIdr', label: 'Unit IDR', groupId: 'money', num: true },
    { id: 'totalIdr', label: 'Total IDR', groupId: 'money', num: true },
    { id: 'application', label: 'Application', groupId: 'notes' },
    { id: 'remarks', label: 'Remarks', groupId: 'notes' },
    { id: 'remarkInternal', label: 'Remark Internal', groupId: 'notes' },
    { id: 'pmsm', label: 'Latest from PM & SM', groupId: 'notes' },
    { id: 'history', label: 'History', groupId: 'notes', center: true },
];

/** Newest PM and SM entry of ONE line, taken from its own assignment log. */
function lineLatestPmSm(history) {
    const rows = (history ?? []).filter((h) => h && h.status && h.status !== '—');
    const pick = (re) => rows.filter((h) => re.test(h.status))[0] || null;
    return { pm: pick(/pm/i), sm: pick(/sm/i) };
}

/** One line-item cell. Column ids come from LINE_COLUMN_DEFS. */
function renderLineCell(item, colId) {
    switch (colId) {
        case 'id': return <span className="tabular-nums">{item.id}</span>;
        case 'status': return item.statusLabel
            ? <StatusBadge tone={statusTone(item.status)}>{item.statusLabel}</StatusBadge>
            : '';
        case 'principal': return item.principalName || '';
        case 'product': return <strong className="font-semibold text-foreground">{item.productName || ''}</strong>;
        case 'qty': return item.qty || '';
        case 'packId': return item.quotationPackId || '';
        case 'satuan': return item.satuanId || '';
        case 'orderQty': return item.orderQty || '';
        case 'satuanOrder': return item.satuanOrderQty || '';
        case 'usdRate': return item.usdRate || '';
        case 'unitUsd': return item.unitUsd || '';
        case 'totalUsd': return <strong className="font-semibold text-foreground">{item.totalUsd ? '$' + item.totalUsd : ''}</strong>;
        case 'unitIdr': return <span className="text-muted-foreground">{item.unitIdr || ''}</span>;
        case 'totalIdr': return <span className="text-muted-foreground">{item.totalIdr ? 'Rp ' + item.totalIdr : ''}</span>;
        case 'application': return item.application || '';
        case 'remarks': return item.remarks || '';
        case 'remarkInternal': return item.remarkInternal || '';
        case 'pmsm': return <PmSmLineCell history={item.history} />;
        // ONE column, two buttons (user 2026-08-24). Left = the company's last 10 quotes for
        // this product (legacy "QuotationHistory"); right = this line's own audit log
        // (legacy "History"). Two headings for two icon buttons was the redundancy.
        case 'history': return (
            <span className="inline-flex items-center justify-center gap-1.5">
                <QuotationHistoryCell items={item.quotationHistory} />
                <QuotationAuditCell items={item.history} />
            </span>
        );
        default: return null;
    }
}

function PmSmLineCell({ history }) {
    const { pm, sm } = lineLatestPmSm(history);
    if (!pm && !sm) return <span className="text-muted-foreground">—</span>;
    const line = (tag, e) => e ? (
        <span key={tag} className="block truncate" title={`${tag} · ${e.status}${e.tanggal ? ' · ' + e.tanggal : ''}${e.comment ? ' — ' + e.comment : ''}`}>
            <span className="mr-1.5 rounded bg-muted px-1 py-px text-[9px] font-bold uppercase text-muted-foreground">{tag}</span>
            <span className="text-muted-foreground">{e.comment || e.status}</span>
        </span>
    ) : null;
    return <span className="block max-w-[230px] leading-tight">{line('PM', pm)}{line('SM', sm)}</span>;
}

const DOC_SECTION = 'relative rounded-xl border border-border bg-card px-4 pt-3.5 pb-3 shadow-sm';
const DOC_HEADING = 'm-0 mb-3 flex items-center gap-2 text-xs font-bold uppercase tracking-wide text-foreground';
const DOC_ICON = 'inline-grid size-6 shrink-0 place-items-center rounded-full border border-primary/50 bg-transparent text-primary';
const BACK_BTN = 'inline-flex h-9 items-center justify-center gap-1.5 rounded-lg border border-input bg-card px-4 text-center text-xs font-bold text-foreground transition-colors hover:border-primary hover:text-primary';

// Editable input — bordered shadcn-token style so it reads obviously as "you can edit this".
const EDITABLE_INPUT = 'w-full rounded-md border border-input bg-card px-2.5 py-1.5 text-xs text-foreground outline-none focus:border-primary focus:ring-1 focus:ring-primary';

// Order & Payment editable fields — the doc-section labels mapped to the request keys.
const ORDER_EDITABLE_LABELS = ['Customer PO No', 'Customer PO Date'];
const PRICING_EDITABLE_LABELS = ['Delivery Fee', 'DP Percentage', 'DP Price (USD)', 'DP Price (IDR)'];
const LABEL_TO_KEY = {
    'Customer PO No': 'CustomerPONo',
    'Customer PO Date': 'PODate',
    'Delivery Fee': 'DeliveryFee',
    'DP Percentage': 'DPPercentage',
    'DP Price (USD)': 'DPPriceUSD',
    'DP Price (IDR)': 'DPPriceIDR',
};
const ORDER_EDIT_CONFIG = {
    'Customer PO No': { type: 'text', placeholder: 'Insert PO No' },
    'Customer PO Date': { type: 'date', hint: 'Ex. 2026-02-20' },
};
const PRICING_EDIT_CONFIG = {
    'Delivery Fee': { type: 'number', placeholder: 'Insert Delivery Fee' },
    'DP Percentage': { type: 'number', placeholder: 'Insert DP Percentage' },
    'DP Price (USD)': { type: 'number', placeholder: 'Insert DP Price USD' },
    'DP Price (IDR)': { type: 'number', placeholder: 'Insert DP Price IDR' },
};

// DocList renders a field object as <dt>/<dd> pairs.
// Editable support (used by future capability consumers): pass `editableKeys`,
// `values`, `onEdit(key, value)` and optional per-key `editConfig`. The read-only
// consumers (Quotations/ViewHead/ViewAll detail) pass none → static <dd>.
function DocList({ fields, editableKeys, values, onEdit, editConfig = {} }) {
    if (!fields || Object.keys(fields).length === 0)
        return <p className="px-0 py-1 text-[0.82rem] text-muted-foreground">Tidak ada data.</p>;
    const editable = editableKeys instanceof Set ? editableKeys : new Set(editableKeys || []);
    return (
        <dl className="grid gap-0">
            {Object.entries(fields).map(([key, val]) => {
                const isEditable = editable.has(key);
                const cfg = editConfig[key] || {};
                // 132px, up from 110px. At the label's new 12.5px, "Company CP Telephone" needs
                // ~124px and five labels were breaking onto a second line, which added height and
                // left the column ragged. The value column gives up 22px of a ~370px card; the
                // long values there already wrap.
                return (
                    <div key={key} className="grid grid-cols-[minmax(0,132px)_1fr] items-baseline gap-2.5 border-b border-border/40 py-1.75 last:border-b-0">
                        {/* 12px, not 11px (user 2026-08-24: "kecil banget", then −0.5px). The VALUE beside it
                            is 13px, so the label still ranks below it — the separation is carried by
                            weight and colour (medium + muted vs the value's foreground), not by
                            shrinking the label until it is the smallest text on the page. */}
                        <dt className={`m-0 flex items-center gap-1 text-[12px] ${isEditable ? 'font-semibold text-foreground' : 'font-medium text-muted-foreground'}`}>
                            {key}
                            {isEditable && (
                                <span className="inline-flex text-primary" title="Editable">
                                    <Pencil aria-hidden="true" className="size-3 text-primary" strokeWidth={2.5} />
                                </span>
                            )}
                        </dt>
                        {isEditable ? (
                            <dd className="m-0 min-w-0">
                                <input
                                    type={cfg.type || 'text'}
                                    className={EDITABLE_INPUT}
                                    value={values?.[key] ?? ''}
                                    placeholder={cfg.placeholder}
                                    onChange={(e) => onEdit?.(key, e.target.value)}
                                />
                                {cfg.hint && (
                                    <small className="mt-0.5 block text-[11px] text-muted-foreground">{cfg.hint}</small>
                                )}
                            </dd>
                        ) : (
                            // 12px, matching the label (user 2026-08-24). Label and value are the
                            // same size now; what separates them is colour and weight —
                            // muted/medium against foreground/medium.
                            <dd className="m-0 wrap-break-word text-[12px] font-medium text-foreground">{val || '—'}</dd>
                        )}
                    </div>
                );
            })}
        </dl>
    );
}

/**
 * Shared MenuQuotations detail shell: header + back link, hero (id + status + an
 * optional `actions` slot), stats strip, doc-section grid, history card, and the
 * line-items table (incl. the legacy Quotation History + History columns). Read-only
 * by default; per-menu capability actions (e.g. Change Status) come in via `actions`.
 */
// Pricing fields/stat-cards hidden when `hidePricingDetails` (View Details MM).
const PRICING_HIDDEN_KEYS = ['DP Percentage', 'DP Price (USD)', 'DP Price (IDR)', 'Total (USD)', 'Total (IDR)'];

function getCreatedOnString(q) {
    let dateStr = '';
    if (q.history?.entries && q.history.entries.length > 0) {
        // The entries are sorted descending, so the last one is the oldest (creation).
        const oldest = q.history.entries[q.history.entries.length - 1];
        if (oldest?.Tanggal && oldest.Tanggal !== '—') {
            dateStr = oldest.Tanggal;
        }
    }
    if (!dateStr) {
        dateStr = q.general?.['Quotation Date'] || q.general?.['Sample Order Date'] || q.tanggal || '';
    }
    if (!dateStr || dateStr === '—') return '';
    try {
        const parts = dateStr.trim().split(/\s+/);
        const datePart = parts[0];
        const timePart = parts[1] || '';

        let formattedDate = datePart;
        if (datePart.includes('-')) {
            const [y, m, d] = datePart.split('-');
            const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
            const monthName = months[parseInt(m, 10) - 1] || m;
            formattedDate = `${parseInt(d, 10)} ${monthName} ${y}`;
        }

        let formattedTime = '';
        if (timePart) {
            const tParts = timePart.split(':');
            if (tParts.length >= 2) {
                formattedTime = `${tParts[0]}:${tParts[1]}`;
            }
        }

        if (formattedDate && formattedTime) {
            return `Created on ${formattedDate} · ${formattedTime}`;
        } else if (formattedDate) {
            return `Created on ${formattedDate}`;
        }
    } catch (e) {
        // Ignore
    }
    return `Created on ${dateStr}`;
}

export function QuotationDetailPage({ quotation, actions = null, backHref = '/quotations', breadcrumbCurrent = 'View Quotation', editableOrder = false, orderForm = null, dpBasis: dpBasisProp = null, printActions = null, links = null, linkContext = null, hidePricingDetails = false }) {
    const { show: showToast } = useToast();
    const q = quotation;

    const form = useForm(orderForm || {
        CustomerPONo: '', PODate: '', DeliveryFee: '', DPPercentage: '', DPPriceUSD: '', DPPriceIDR: '',
    });
    const orderValues = {
        'Customer PO No': form.data.CustomerPONo ?? '',
        'Customer PO Date': form.data.PODate ?? '',
    };
    const pricingValues = {
        'Delivery Fee': form.data.DeliveryFee ?? '',
        'DP Percentage': form.data.DPPercentage ?? '',
        'DP Price (USD)': form.data.DPPriceUSD ?? '',
        'DP Price (IDR)': form.data.DPPriceIDR ?? '',
    };
    // DP %, DP Price USD and DP Price IDR are three views of one number, so editing any of
    // them recomputes the other two (legacy DPCalc). The generic DocList renderer knows
    // nothing about this — it just reports "label X changed to Y" — so the rule is applied
    // here, on the way into the form, using the same lib the two order pages use.
    const basis = dpBasisProp ? dpBasis(dpBasisProp) : null;
    const onEditField = (label, value) => {
        const key = LABEL_TO_KEY[label];
        if (basis && DP_FIELDS.includes(key)) {
            const next = dpRecalc(key, value, basis);
            // null = not a number yet; fall through and store the raw keystroke.
            if (next !== null) {
                form.setData((d) => ({ ...d, ...next }));

                return;
            }
        }
        form.setData(key, value);
    };
    const [confirmOpen, setConfirmOpen] = useState(false);
    const saveOrder = () => form.post(route('quotations.update-order', q.id), {
        onError: () => showToast('Please check the form and try again.', 'error'),
        preserveScroll: true,
        onSuccess: () => setConfirmOpen(false),
    });

    // Update Quotation — the primary save action for the inline-editable Order/Payment
    // fields. Rendered at the bottom-left, inline with Download/Print (far left), as the
    // single standout (solid) button; same h-9 size as the other action buttons.
    const updateButton = editableOrder ? (
        <Dialog open={confirmOpen} onOpenChange={setConfirmOpen}>
            <DialogTrigger asChild>
                <Button
                    type="button"
                    disabled={!form.isDirty || form.processing}
                    className="inline-flex h-9 items-center justify-center rounded-lg bg-linear-to-br from-violet-500 to-primary px-4 text-xs font-bold text-white shadow-sm transition-[filter] hover:brightness-105 disabled:opacity-60"
                >
                    Update Quotation
                </Button>
            </DialogTrigger>
            <DialogContent>
                <DialogHeader>
                    <DialogTitle>Update this quotation?</DialogTitle>
                </DialogHeader>
                <p className="text-xs text-muted-foreground">
                    The Order &amp; Payment fields will be saved to this quotation.
                </p>
                <DialogFooter>
                    <Button
                        type="button"
                        onClick={saveOrder}
                        disabled={form.processing}
                        className="h-9 px-4 rounded-lg bg-linear-to-br from-violet-500 to-primary text-white font-bold text-xs shadow-sm transition-[filter] hover:brightness-105"
                    >
                        Yes, Update
                    </Button>
                    <Button type="button" variant="outline" onClick={() => setConfirmOpen(false)}>Keep Editing</Button>
                </DialogFooter>
            </DialogContent>
        </Dialog>
    ) : null;

    // Build section fields — fallback ke top-level fields kalau detail belum ada
    const generalFields = {
        ...(q.general || {
            'No': q.id,
            'Quotation Status': q.statusLabel || '',
            'Feedback Status': q.feedback || 'NA',
            'Quotation Date': q.tanggal || '',
            'Creator': q.creator || '',
            'Comment Internal': q.comment || '',
        })
    };
    // Feedback Status shows "NA" (not "") when empty.
    if (!generalFields['Feedback Status'] || generalFields['Feedback Status'] === '—' || generalFields['Feedback Status'] === '') {
        generalFields['Feedback Status'] = 'NA';
    }

    // Fallback for payloads that predate the 2026-08-24 regrouping. Key order mirrors the
    // server's: what the company is, then who sells to it.
    const companyFields = q.companyContact || {
        'Company Name': q.company || '',
        'Division': q.division || '',
        'Industry': q.industry || '',
        'Company Category': q.companyCategory || '',
        'Sales': q.sales || '',
    };

    const orderFields = q.order || {
        'Customer PO No': q.poNo || '',
        'Customer PO Date': q.poDate || '',
        'Delivery Date': q.deliveryDate || '',
    };

    const pricingFields = q.pricing || {};
    const termsFields = q.terms || {};

    // Ensure DP Price (IDR) renders in the pricing list even if the source omits it.
    const pricingFieldsFull = 'DP Price (IDR)' in pricingFields
        ? pricingFields
        : { ...pricingFields, 'DP Price (IDR)': '' };
    // MM hides DP %/DP USD/DP IDR/Total USD/Total IDR — leaving USD Rate + Delivery Fee.
    const pricingFieldsView = hidePricingDetails
        ? Object.fromEntries(Object.entries(pricingFieldsFull).filter(([k]) => !PRICING_HIDDEN_KEYS.includes(k)))
        : pricingFieldsFull;

    const businessFields = q.business ?? {};
    const additionalFields = q.additional ?? {};
    // ⚙ column prefs for the line-items table (user 2026-08-24). Same hook the list
    // pages use: order + visibility in localStorage, stale ids dropped, required ones
    // forced visible so the row anchors can never be hidden.
    const lineCols = useColumnPrefs('quotationDetailLineColumns_v1', LINE_COLUMN_DEFS);
    const [lineColsOpen, setLineColsOpen] = useState(false);
    const historyEntries = q.history?.entries || [];
    const lineItems = q.lineItems || [];
    const totals = q.totals || { usd: '$0', idr: 'Rp 0', dpPct: '0.00%', dpUsd: '$0', deliveryFee: '$0' };

    return (
        <section className="flex min-w-0 flex-col gap-4.5">
            <header className="flex items-center justify-between gap-4">
                <div>
                    <p className="mb-1.5 flex items-center gap-2 text-xs font-semibold text-muted-foreground">
                        <Link href={backHref} className="text-muted-foreground no-underline hover:text-primary">Quotations</Link>
                        <span aria-hidden="true">›</span>
                        <span className="text-foreground">{breadcrumbCurrent}</span>
                    </p>
                </div>
                <Link href={backHref} className={BACK_BTN}>
                    <ArrowLeft className="size-3.5" />
                    Back to List
                </Link>
            </header>

            {/* Hero — stacks on mobile so the title + "Created on" get full width and
                the action buttons drop below instead of squeezing from the side. */}
            <div className="mt-1 flex flex-col items-start gap-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4">
                <div className="flex min-w-0 flex-col gap-1">
                    <div className="flex items-center gap-3">
                        <h1 className="m-0 text-2xl font-extrabold tracking-tight text-foreground">Quotation #{q.id}</h1>
                        {q.statusLabel && (
                            <StatusBadge tone={statusTone(q.status)}>{q.statusLabel}</StatusBadge>
                        )}
                    </div>
                    {getCreatedOnString(q) && (
                        <p className="m-0 text-[13px] font-medium text-muted-foreground">{getCreatedOnString(q)}</p>
                    )}
                </div>
                {actions && (
                    <div className="flex flex-wrap items-center gap-2">
                        {actions}
                    </div>
                )}
            </div>

            {/* Stats Strip — the 4 price/total cards drop out on MM (hidePricingDetails). */}
            {(() => {
                const cards = [
                    { key: 'usd', pricing: true, iconBg: 'bg-accent', iconText: 'text-primary', symbol: '$', label: 'Subtotal (USD)', value: totals.usd },
                    { key: 'idr', pricing: true, iconBg: 'bg-accent', iconText: 'text-primary', symbol: 'Rp', label: 'Subtotal (IDR)', value: totals.idr },
                    { key: 'dpPct', pricing: true, iconBg: 'bg-accent', iconText: 'text-primary', symbol: '%', label: 'DP Percentage', value: totals.dpPct },
                    { key: 'dpUsd', pricing: true, iconBg: 'bg-accent', iconText: 'text-primary', symbol: '$', label: 'DP Price (USD)', value: totals.dpUsd },
                    { key: 'deliveryFee', pricing: false, iconBg: 'bg-accent', iconText: 'text-primary', symbol: '⛟', label: 'Delivery Fee', value: totals.deliveryFee },
                ].filter((c) => !(hidePricingDetails && c.pricing));
                return (
                    <article className="flex flex-col gap-3 rounded-2xl border border-border bg-card shadow-sm p-[14px_18px] sm:flex-row sm:items-start">
                        {/* 2 columns on mobile; auto-fit (≥150px, wraps) on larger screens —
                            long values truncate instead of overlapping. */}
                        <div className="grid min-w-0 gap-x-4 gap-y-3 [grid-template-columns:repeat(2,minmax(0,1fr))] sm:flex-1 sm:gap-x-5 sm:[grid-template-columns:repeat(auto-fit,minmax(150px,1fr))]">
                            {cards.map((c) => (
                                <div key={c.key} className="flex min-w-0 flex-col gap-0.5">
                                    <small className="block text-[11px] font-medium text-muted-foreground">{c.label}</small>
                                    <strong className="block truncate text-[15px] font-extrabold leading-[1.1] text-card-foreground" title={String(c.value ?? '')}>{c.value}</strong>
                                </div>
                            ))}
                            {/* Mobile: clock fills the empty grid cell (below DP Price). */}
                            {historyEntries.length > 0 && (
                                <div className="flex items-center sm:hidden">
                                    <HistoryTimelinePopover entries={historyEntries} />
                                </div>
                            )}
                        </div>
                        {/* Desktop: clock sits at the right with a divider. */}
                        {historyEntries.length > 0 && (
                            <div className="hidden shrink-0 self-stretch border-l border-border pl-3 sm:block">
                                <HistoryTimelinePopover entries={historyEntries} />
                            </div>
                        )}
                    </article>
                );
            })()}

            {/* Doc Sections */}
            <div className="grid grid-cols-3 gap-3.5 max-[1180px]:grid-cols-2 max-[860px]:grid-cols-1">
                <section className={DOC_SECTION} data-section-id="general">
                    <h3 className={DOC_HEADING}>
                        <span className={DOC_ICON} aria-hidden="true">
                            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                                <circle cx="12" cy="12" r="10" /><line x1="12" y1="16" x2="12" y2="12" /><line x1="12" y1="8" x2="12.01" y2="8" />
                            </svg>
                        </span>
                        General Information
                    </h3>
                    <DocList fields={generalFields} />
                </section>

                <section className={DOC_SECTION} data-section-id="company">
                    <h3 className={DOC_HEADING}>
                        <span className={DOC_ICON} aria-hidden="true">
                            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                                <path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" /><circle cx="12" cy="7" r="4" />
                            </svg>
                        </span>
                        Company &amp; Contact
                    </h3>
                    <DocList fields={companyFields} />
                </section>

                <section className={DOC_SECTION} data-section-id="order">
                    <h3 className={DOC_HEADING}>
                        <span className={DOC_ICON} aria-hidden="true">
                            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                                <rect x="3" y="4" width="18" height="18" rx="2" /><line x1="16" y1="2" x2="16" y2="6" /><line x1="8" y1="2" x2="8" y2="6" /><line x1="3" y1="10" x2="21" y2="10" />
                            </svg>
                        </span>
                        Order Information
                    </h3>
                    <DocList
                        fields={orderFields}
                        editableKeys={editableOrder ? ORDER_EDITABLE_LABELS : undefined}
                        values={orderValues}
                        editConfig={ORDER_EDIT_CONFIG}
                        onEdit={onEditField}
                    />
                </section>

                <section className={DOC_SECTION} data-section-id="pricing">
                    <h3 className={DOC_HEADING}>
                        <span className={DOC_ICON} aria-hidden="true">
                            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                                <line x1="12" y1="2" x2="12" y2="22" /><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" />
                            </svg>
                        </span>
                        Pricing Information
                    </h3>
                    <DocList
                        fields={pricingFieldsView}
                        editableKeys={editableOrder ? PRICING_EDITABLE_LABELS : undefined}
                        values={pricingValues}
                        editConfig={PRICING_EDIT_CONFIG}
                        onEdit={onEditField}
                    />
                </section>

                <section className={DOC_SECTION} data-section-id="terms">
                    <h3 className={DOC_HEADING}>
                        <span className={DOC_ICON} aria-hidden="true">
                            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                                <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
                                <polyline points="14 2 14 8 20 8" /><polyline points="9 14 11 16 15 12" />
                            </svg>
                        </span>
                        Terms &amp; Conditions
                    </h3>
                    <DocList fields={termsFields} />
                </section>

                {/* Division / Industry / Company Category moved into Company & Contact and
                    Creator / Comment Internal into General Information (user 2026-08-24), so this
                    card normally has nothing left to show. An empty card with a heading and an
                    icon is exactly the "panel that can only say empty" the form-grammar rule bans,
                    so the whole section is conditional rather than deleted — payloads that still
                    populate either group keep working. */}
                {(Object.keys(businessFields ?? {}).length > 0 || Object.keys(additionalFields ?? {}).length > 0) && (
                <section className={DOC_SECTION} data-section-id="business">
                    <h3 className={DOC_HEADING}>
                        <span className={DOC_ICON} aria-hidden="true">
                            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                                <path d="M3 21h18" /><path d="M5 21V7l7-4 7 4v14" />
                                <path d="M10 9h.01" /><path d="M14 9h.01" /><path d="M10 13h.01" /><path d="M14 13h.01" /><path d="M10 17h.01" /><path d="M14 17h.01" />
                            </svg>
                        </span>
                        Business Information
                    </h3>
                    <DocList fields={businessFields} />
                    {/* Creator / Sales / Comment Internal moved into General Information and
                        Company & Contact (user 2026-08-24), so this group is normally empty.
                        Rendering the heading anyway would leave a dashed rule under a card with
                        nothing beneath it — the same "do not render a panel that can only say
                        empty" rule the form-grammar section states. Kept conditional rather than
                        deleted because older payloads still send fields here. */}
                    {Object.keys(additionalFields ?? {}).length > 0 && (
                        <>
                            <h4 className="mt-4 mb-2 border-t border-dashed border-border pt-3 text-[11px] font-extrabold uppercase tracking-wide text-muted-foreground">Additional Information</h4>
                            <DocList fields={additionalFields} />
                        </>
                    )}
                </section>
                )}
            </div>

            {/* Line Items */}
            <article className="rounded-2xl border border-border bg-card shadow-sm">
                <header className="flex items-center justify-between gap-3 border-b border-border p-[18px_22px]">
                    <div className="flex items-center gap-2.5">
                        <span className={DOC_ICON} aria-hidden="true">
                            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                                <line x1="8" y1="6" x2="21" y2="6" /><line x1="8" y1="12" x2="21" y2="12" /><line x1="8" y1="18" x2="21" y2="18" />
                                <circle cx="4" cy="6" r="1" /><circle cx="4" cy="12" r="1" /><circle cx="4" cy="18" r="1" />
                            </svg>
                        </span>
                        <div className="[&>h2]:m-0 [&>h2]:text-sm [&>h2]:font-extrabold [&>h2]:leading-[1.2] [&>h2]:text-card-foreground [&>small]:block [&>small]:text-[11px] [&>small]:font-medium [&>small]:text-muted-foreground">
                            <h2>Quotation Details</h2>
                            <small>Line items breakdown</small>
                        </div>
                    </div>
                    {/* The GEAR, icon-only — design-system.md pins CustomizeColumnsModal to this
                        opener on every table that has one. */}
                    <button type="button" onClick={() => setLineColsOpen(true)}
                        title="Customize columns" aria-label="Customize columns"
                        className="grid size-7 shrink-0 place-items-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground">
                        <Settings className="size-3.5" strokeWidth={2.5} />
                    </button>
                </header>

                {lineItems.length === 0 ? (
                    <p className="p-6 text-[0.85rem] text-muted-foreground">Tidak ada line item.</p>
                ) : (
                    <div className="overflow-x-auto rounded-xl border border-border/70">
                        <table className="w-full min-w-575 border-collapse [&_thead_th]:whitespace-nowrap [&_thead_th]:border-b [&_thead_th]:border-border [&_thead_th]:px-3 [&_thead_th]:py-3 [&_thead_th]:text-left [&_thead_th]:text-[11px] [&_thead_th]:font-semibold [&_thead_th]:uppercase [&_thead_th]:tracking-wide [&_thead_th]:text-muted-foreground/80 [&_th.number]:text-right [&_td.number]:text-right [&_th.text-center]:text-center [&_td.text-center]:text-center [&_tbody_td]:whitespace-nowrap [&_tbody_td]:border-b [&_tbody_td]:border-border/50 [&_tbody_td]:px-3 [&_tbody_td]:py-3 [&_tbody_td]:align-top [&_tbody_td]:text-[12px] [&_tbody_td]:font-medium [&_tbody_td]:text-foreground [&_tbody_tr:last-child_td]:border-b-0 [&_tbody_tr:nth-child(even)]:bg-secondary/25 [&_tbody_tr:hover]:bg-secondary/60">
                            <thead>
                                <tr>
                                    {lineCols.visibleCols.map((c) => (
                                        <th key={c.id} className={c.num ? 'number' : (c.center ? '!text-center' : undefined)}>{c.label}</th>
                                    ))}
                                </tr>
                            </thead>
                            <tbody>
                                {lineItems.map(item => (
                                    <tr key={item.id}>
                                        {lineCols.visibleCols.map((c) => (
                                            <td key={c.id} className={c.num ? 'number tabular-nums' : (c.center ? 'text-center' : undefined)}>
                                                {renderLineCell(item, c.id)}
                                            </td>
                                        ))}
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    </div>
                )}

                <CustomizeColumnsModal
                    open={lineColsOpen} onClose={() => setLineColsOpen(false)}
                    groups={LINE_COLUMN_GROUPS} definitions={LINE_COLUMN_DEFS}
                    state={lineCols.columnState} onApply={lineCols.applyColumns} onReset={lineCols.resetColumns}
                    title="Customize columns" subtitle="Quotation Details" />
            </article>

            {/* Linked items + secondary actions: one left-aligned column — links on
                top, the Print/Download card below. Primary actions stay in the hero. */}
            {((links !== null && linkContext) || printActions || editableOrder) && (
                <div className="flex w-full flex-col gap-3.5 lg:max-w-[600px]">
                    {editableOrder && Object.keys(form.errors).length > 0 && (
                        <span className="text-[11px] font-medium text-danger">{Object.values(form.errors)[0]}</span>
                    )}
                    {links !== null && linkContext && (
                        <QuotationLinks links={links} linkContext={linkContext} />
                    )}
                    {printActions
                        ? <PrintActionButtons printActions={printActions} leadingAction={updateButton}
                            // Print Pelunasan saves the order fields as a side effect —
                            // legacy reads them out of THIS form's inputs, not the stored
                            // row, so hand over the live values. Null when the page is not
                            // editable: the hook then refuses rather than posting blanks.
                            getPelunasanPayload={editableOrder ? () => ({
                                CustomerPONo: form.data.CustomerPONo || '',
                                PODate: form.data.PODate || null,
                                DeliveryFee: Number(form.data.DeliveryFee) || 0,
                                DPPercentage: Number(form.data.DPPercentage) || 0,
                                DPPriceUSD: Number(form.data.DPPriceUSD) || 0,
                                DPPriceIDR: Number(form.data.DPPriceIDR) || 0,
                            }) : null} />
                        : (editableOrder && <div className="flex flex-wrap items-center gap-2">{updateButton}</div>)}
                </div>
            )}

            {/* Company Records — listquotationdetails{,all,head}.php embed 12 of the 14 panes
                (Complain yes; Project and Visit Report All no). Lazy per tab via companies.tabs. */}
            {q.companyId > 0 && (
                <CompanyTabs companyId={q.companyId} preset="quotation" />
            )}
        </section>
    );
}
