import { useState } from 'react';
import { Link, useForm, useHttp } from '@inertiajs/react';
import { ArrowLeft, Loader2, Printer, Truck, Radar } from 'lucide-react';
import AppLayout from '@/Layouts/AppLayout';
import { useSampleOrderPdf } from '@/lib/pdf/useSampleOrderPdf';
import { useToast } from '@/Components/Toast';
import { Button } from '@/Components/ui/button';
import { Dialog, DialogContent, DialogFooter, DialogTitle } from '@/Components/ui/dialog';
import { FloatingField } from '@/Components/Proto/UI/FloatingField';
import { StatusBadge } from '@/Components/Proto/UI/StatusBadge';
import { statusTone } from '@/lib/sampleOrderStatusTones';
import { HistoryTimelinePopover } from '@/Components/MenuQuotations/QuotationDetailPage/HistoryTimelinePopover';

// Clean "doc-section" shell shared by the cards below (header bar + uppercase title).
const SECTION_CARD = 'overflow-hidden rounded-xl border border-border bg-card shadow-sm';
const SECTION_HEAD = 'border-b border-border/60 px-5 py-3';
const SECTION_TITLE = 'm-0 text-[11px] font-bold uppercase tracking-[0.05em] text-card-foreground';
const SECTION_SUB = 'mt-0.5 block text-[11px] font-medium text-muted-foreground';

// Surat Jalan screen (legacy samplerequestsuratjalan.php): serves status 6
// (Generate) AND status 7 (manual AWB entry after generating).
//
// The right column keeps legacy's own shape — two jobs, two cards:
//
//   • Sample DO — the 6→7 transition (legacy's bottom comment + Generate Surat Jalan).
//   • Shipment  — legacy's header shipping block, then its #api booking block.
//
// ⚠️ THERE IS NO "manual vs API" MODE, and an earlier pass was wrong to invent one. Legacy has
// ONE shipment record, split by WHO OWNS EACH FIELD:
//     courier-owned, readonly  — BookingID, AWB Date, Service Code, Shipping Cost, Total Weight
//                                (legacy :752, :792, :798, :805, :824)
//     human-owned,   editable  — Vendor, AWB, Insurance, Packing   (:758, :781, :811, :817)
// …plus a booking block that HIDES ITSELF the moment an AWB exists (:663-671 drops the vendor
// picker and Generate AWB together). That single condition is the entire state model — do not
// add a toggle on top of it.
//
// The courier-owned values arrive from Book AWB, or from Track → "Copy to shipping form"
// (legacy's getDataAWB, :567-585). That is why they are readonly rather than typed.
//
// ⚠️ Saving must stay possible at status 7. Until 2026-08-24 the shipping fields could only be
// submitted as step 1 of Generate, and Generate is disabled at 7 — so the one state this screen
// exists to serve could not be saved. Pinned by tests/js/suratJalanShipping.test.jsx.
//
// Both PDFs (delivery note, cover letters) and the REX AWB label render client-side.
export default function SampleOrderSuratJalanDetail({ sampleOrder = {}, lines = [], vendors = [] }) {
    const q = sampleOrder;
    const canGenerate = q.statusId === 6 && lines.length > 0;

    const awbForm = useForm({
        Vendor: q.vendorId ? String(q.vendorId) : '',
        AWB: q.awb || '',
        TanggalAWBInput: q.tanggalAwb || '',
        ServiceCode: q.serviceCode || '',
        Tarif: q.tarif || 0,
        Asuransi: q.asuransi || 0,
        Packing: q.packing || 0,
        TotalWeight: q.totalWeight || 0,
    });
    const genForm = useForm({ comment: '' });
    const [genOpen, setGenOpen] = useState(false);
    const pdf = useSampleOrderPdf();
    const { show: showToast } = useToast();

    // ── Shipping fields can now be saved on their own ────────────────────────────────────
    // Before this, awbForm was only ever submitted as step 1 of Generate, and Generate is
    // disabled at status 7 — so an AWB typed AFTER generating (the whole reason this screen
    // serves status 7) could not be saved at all. Legacy had btn-updateheaderawb for exactly
    // this and it worked at any status.
    const canSaveShipping = q.statusId === 6 || q.statusId === 7;
    const saveShipping = () => {
        awbForm.transform((data) => ({ ...data, Vendor: data.Vendor === '' ? null : Number(data.Vendor) }));
        awbForm.post(route('sample-orders.surat-jalan.awb', q.id), {
            preserveScroll: true,
            onError: () => showToast('Please check the form and try again.', 'error'),
        });
    };

    // ── Contact postcode (legacy btn-updateheadercp) ──────────────────────────────────────
    // Its own form because it writes a DIFFERENT table — companycp, shared master data — and
    // must stay saveable whether or not a booking is in progress.
    const cpForm = useForm({ CompanyCPKodePos: q.companyCPKodePos || '' });
    const saveCompanyCp = () => {
        cpForm.post(route('sample-orders.surat-jalan.company-cp', q.id), {
            preserveScroll: true,
            onError: () => showToast('Please check the form and try again.', 'error'),
        });
    };

    // ── REX courier panel (legacy #api block + btn-rates + btn-saveAWB) ───────────────────
    const hasAwb = !!q.awb;
    const courier = useForm({
        destZip: q.companyCPKodePos || '',
        serviceCode: '',
        weight: 1000,           // legacy default (samplerequestsuratjalan.php:950)
        length: 0, width: 0, height: 0,
        price: 1000,            // legacy default (:979)
        note: '',
        insurance: false,
        packing: false,
    });
    const [rates, setRates] = useState([]);
    const [awbOpen, setAwbOpen] = useState(false);
    const ratesHttp = useHttp({});
    const trackHttp = useHttp({});
    const [tracking, setTracking] = useState(null);
    const [trackOpen, setTrackOpen] = useState(false);

    // Volumetric weight, legacy formula: round(L*W*H / 6), floored at 1000 g (:1263-1276).
    const recalcVolume = () => {
        const l = parseFloat(courier.data.length), w = parseFloat(courier.data.width), h = parseFloat(courier.data.height);
        if ([l, w, h].some(Number.isNaN)) return;
        courier.setData('weight', Math.max(1000, Math.round((l * w * h) / 6)));
    };

    const checkRates = () => {
        setRates([]);
        courier.setData('serviceCode', '');
        // .transform() and .post() must be SEPARATE statements — chaining them throws.
        ratesHttp.transform(() => ({ destZip: courier.data.destZip, weight: Number(courier.data.weight) }));
        ratesHttp.post(route('sample-orders.surat-jalan.rates', q.id), {
            onSuccess: (data) => {
                if (!data?.ok) return showToast(data?.message || 'Gagal cek ongkir.', 'error');
                setRates(data.services || []);
                if (!(data.services || []).length) showToast('Tidak ada layanan untuk kode pos itu.', 'info');
            },
            onError: () => showToast('Please check the form and try again.', 'error'),
            onHttpException: () => showToast('Could not check rates.', 'error'),
            onNetworkError: () => showToast('Could not check rates.', 'error'),
        }).catch(() => {});
    };

    const submitAwb = (e) => {
        e.preventDefault();
        courier.post(route('sample-orders.surat-jalan.awb.generate', q.id), {
            preserveScroll: true,
            onSuccess: () => setAwbOpen(false),
            onError: () => { setAwbOpen(false); showToast('Please check the form and try again.', 'error'); },
        });
    };

    const openTracking = () => {
        setTrackOpen(true);
        setTracking(null);
        trackHttp.get(route('sample-orders.surat-jalan.track', q.id), {
            onSuccess: (data) => {
                if (!data?.ok) { setTrackOpen(false); return showToast(data?.message || 'Gagal melacak AWB.', 'error'); }
                setTracking(data);
            },
            onError: () => { setTrackOpen(false); showToast('Could not track the AWB.', 'error'); },
            onHttpException: () => { setTrackOpen(false); showToast('Could not track the AWB.', 'error'); },
            onNetworkError: () => { setTrackOpen(false); showToast('Could not track the AWB.', 'error'); },
        }).catch(() => {});
    };

    // Legacy getDataAWB() pushed the courier's own numbers back into the header form
    // (samplerequestsuratjalan.php:567-585). Same idea, but the user asks for it explicitly
    // rather than it firing as a side effect of saving.
    const applyTrackingAutofill = () => {
        const a = tracking?.autofill;
        if (!a) return;
        if (a.serviceCode) awbForm.setData('ServiceCode', a.serviceCode);
        if (a.tarif != null) awbForm.setData('Tarif', a.tarif);
        if (a.totalWeight != null) awbForm.setData('TotalWeight', a.totalWeight);
        if (a.tanggalAwb) awbForm.setData('TanggalAWBInput', a.tanggalAwb);
        setTrackOpen(false);
        showToast('Data AWB disalin ke form. Klik Save Shipping untuk menyimpan.', 'info');
    };

    // Check the required comment BEFORE opening the confirm dialog — mirrors the
    // server rule so the user sees "The Comment field is required." immediately.
    const requestGenerate = () => {
        if (!genForm.data.comment.trim()) {
            genForm.setError('comment', 'The Comment field is required.');
            return;
        }
        genForm.clearErrors('comment');
        setGenOpen(true);
    };
    const submitGenerate = (e) => {
        e.preventDefault();
        // Legacy pops the new Surat Jalan PDF out on generate (reportsuratjalan.php window.open).
        // Open the tab NOW, during the click gesture, so the browser doesn't popup-block it; then
        // render the delivery-note PDF client-side into that tab from the print URL the controller
        // flashes (or close it if generate failed).
        const win = window.open('', '_blank');
        // Step 1: persist the shipping fields (the old "Update AWB" — now implicit),
        // step 2: generate, which reads the freshly saved header values.
        awbForm.transform((data) => ({ ...data, Vendor: data.Vendor === '' ? null : Number(data.Vendor) }));
        awbForm.post(route('sample-orders.surat-jalan.awb', q.id), {
            preserveScroll: true,
            onSuccess: () => {
                genForm.post(route('sample-orders.surat-jalan.generate', q.id), {
                    preserveScroll: true,
                    onSuccess: (page) => {
                        setGenOpen(false);
                        const url = page?.props?.flash?.printSuratJalanUrl;
                        if (win) {
                            if (url) pdf.fillDeliveryNote(win, url);
                            else win.close();
                        }
                    },
                    onError: () => {
                        if (win) win.close();
                        showToast('Please check the form and try again.', 'error');
                    },
                });
            },
            onError: () => {
                setGenOpen(false);
                if (win) win.close();
                showToast('Please check the form and try again.', 'error');
            },
        });
    };

    const infoFields = {
        'Company': q.company,
        'Company Category': q.companyCategory,
        'Company Address': q.companyAddress,
        'Company Telephone': q.companyTelephone,
        'Company CP': q.companyCPName,
        'CP Address': q.companyCPAddress,
        'CP Telephone': q.companyCPTelephone,
        'Division': q.division,
        'Industry': q.industry,
        'Delivery': q.delivery,
        'Date': q.tanggal,
        'Sales': q.sales,
        'Creator': q.creator,
        'Sample Order By': q.sampleOrderBy,
        // Legacy printed '-' for the zero date (:888-892); the '—' fallback below covers it.
        'Date SO By': q.tanggalSOBy,
        'Project': q.project,
        'Comment': q.comment,
        // NOT here, deliberately:
        //   Booking ID       — shipping data, lives in the Shipment card as legacy had it (:752).
        //   CP Postal Code   — editable in the Shipment card, because it is the one field on this
        //                      screen a person changes to make Check Rates work (legacy
        //                      btn-updateheadercp). A read-only copy here would show a stale value
        //                      next to the editable one.
        // Listing either in both places is how this screen started showing values twice.
    };

    const numberField = (label, key) => (
        <FloatingField
            label={label}
            value={awbForm.data[key]}
            onChange={(e) => awbForm.setData(key, e.target.value)}
        />
    );

    return (
        <section className="flex min-w-0 flex-col gap-4.5">
            <header className="flex items-center justify-between gap-4">
                <p className="m-0 flex items-center gap-2 text-xs font-semibold text-muted-foreground">
                    <Link href={route('sample-orders.surat-jalan')} className="text-muted-foreground no-underline hover:text-primary">Sample DO</Link>
                    <span aria-hidden="true">›</span>
                    <span className="text-foreground">Sample Order #{q.id}</span>
                </p>
                <div className="flex flex-wrap items-center gap-2">
                    {/* Left of the back/action buttons — ui-conventions.md "History on detail pages". */}
                    {q.history?.entries?.length > 0 && <HistoryTimelinePopover entries={q.history.entries} />}
                    {/* Track + AWB label appear only once an AWB exists — legacy hid both the
                        same way (samplerequestsuratjalan.php:656-672, :783). */}
                    {hasAwb && (
                        <button type="button" onClick={openTracking} disabled={trackHttp.processing}
                            className="inline-flex h-9 items-center gap-1.5 rounded-lg border border-input bg-card px-3.5 text-xs font-bold text-foreground transition-colors hover:border-primary hover:text-primary cursor-pointer disabled:cursor-not-allowed disabled:opacity-60">
                            {trackHttp.processing ? <Loader2 className="size-3.5 animate-spin" /> : <Radar className="size-3.5" />} Track
                        </button>
                    )}
                    {hasAwb && (
                        <button type="button" onClick={() => pdf.printAwbLabel(q.id)} disabled={!!pdf.busy}
                            className="inline-flex h-9 items-center gap-1.5 rounded-lg border border-input bg-card px-3.5 text-xs font-bold text-foreground transition-colors hover:border-primary hover:text-primary cursor-pointer disabled:cursor-not-allowed disabled:opacity-60">
                            {pdf.busy === 'awb' ? <Loader2 className="size-3.5 animate-spin" /> : <Printer className="size-3.5" />} AWB Label
                        </button>
                    )}
                    {/* Cover-letter PDFs (order-level, not form-dependent) — inline print in a new tab. */}
                    <button type="button" onClick={() => pdf.printCoverLetter(q.id, 'en')} disabled={!!pdf.busy}
                        className="inline-flex h-9 items-center gap-1.5 rounded-lg border border-input bg-card px-3.5 text-xs font-bold text-foreground transition-colors hover:border-primary hover:text-primary cursor-pointer disabled:cursor-not-allowed disabled:opacity-60">
                        {pdf.busy === 'cl-en' ? <Loader2 className="size-3.5 animate-spin" /> : <Printer className="size-3.5" />} Cover Letter (EN)
                    </button>
                    <button type="button" onClick={() => pdf.printCoverLetter(q.id, 'id')} disabled={!!pdf.busy}
                        className="inline-flex h-9 items-center gap-1.5 rounded-lg border border-input bg-card px-3.5 text-xs font-bold text-foreground transition-colors hover:border-primary hover:text-primary cursor-pointer disabled:cursor-not-allowed disabled:opacity-60">
                        {pdf.busy === 'cl-id' ? <Loader2 className="size-3.5 animate-spin" /> : <Printer className="size-3.5" />} Cover Letter (ID)
                    </button>
                    <Link href={route('sample-orders.surat-jalan')}
                        className="inline-flex h-9 items-center justify-center gap-1.5 rounded-lg border border-input bg-card px-4 text-xs font-bold text-foreground transition-colors hover:border-primary hover:text-primary">
                        <ArrowLeft className="size-3.5" /> Back to List
                    </Link>
                </div>
            </header>

            {/* Title + status */}
            <div className="flex flex-wrap items-center gap-2.5">
                <h1 className="m-0 text-2xl font-extrabold leading-none tracking-tight text-card-foreground">
                    Sample Order <span className="text-primary">#{q.id}</span>
                </h1>
                {q.status && <StatusBadge tone={statusTone(q.status)}>{q.status}</StatusBadge>}
                {q.snk && (
                    <span className="inline-flex items-center rounded-full border border-border/70 bg-muted/60 px-2.5 py-0.5 text-[10px] font-bold uppercase tracking-wide text-muted-foreground" title="Terms & Conditions">
                        {q.snk}
                    </span>
                )}
            </div>

            {/* Two-column: LEFT = Order Info + Sample Request Details · RIGHT = Shipping & DO
                Generation stretched to the same height (comment soaks up the spare space) */}
            <div className="grid grid-cols-1 gap-4 min-[1100px]:grid-cols-[1fr_360px]">
                <div className="flex min-w-0 flex-col gap-4">
                    <article className={SECTION_CARD}>
                        <header className={SECTION_HEAD}>
                            <h2 className={SECTION_TITLE}>Order Information</h2>
                        </header>
                        <dl className="grid grid-cols-1 gap-x-10 p-5 sm:grid-cols-2">
                            {Object.entries(infoFields).map(([label, value]) => (
                                <div key={label} className="grid grid-cols-[minmax(0,120px)_1fr] items-baseline gap-2 border-b border-dashed border-border/70 py-[7px]">
                                    <dt className="m-0 text-[11px] font-medium text-muted-foreground">{label}</dt>
                                    <dd className="m-0 min-w-0 truncate text-xs font-semibold text-foreground" title={value || undefined}>{value || '—'}</dd>
                                </div>
                            ))}
                        </dl>
                    </article>


            {/* Lines awaiting surat jalan (status 3) */}
            <article className={SECTION_CARD}>
                <header className={SECTION_HEAD}>
                    <h2 className={SECTION_TITLE}>Sample Request Detail List</h2>
                    <small className={SECTION_SUB}>Lines with Packing/Prepare status that will be included in the Sample DO.</small>
                </header>
                {lines.length === 0 ? (
                    <p className="px-5 py-6 text-xs text-muted-foreground">
                        No lines with Packing/Prepare status{q.statusId === 7 ? ' — Sample DO already generated.' : '.'}
                    </p>
                ) : (
                    <div className="overflow-x-auto">
                        <table className="w-full min-w-[920px] border-collapse [&_tbody_td]:whitespace-nowrap [&_tbody_td]:border-b [&_tbody_td]:border-border/50 [&_tbody_td]:p-[14px_12px] [&_tbody_td]:text-[11px] [&_tbody_td]:text-card-foreground [&_tbody_tr:last-child_td]:border-b-0 [&_tbody_tr:hover_td]:bg-secondary/60 [&_thead_th]:border-b [&_thead_th]:border-border [&_thead_th]:whitespace-nowrap [&_thead_th]:p-[10px_12px] [&_thead_th]:text-left [&_thead_th]:text-[11px] [&_thead_th]:font-semibold [&_thead_th]:uppercase [&_thead_th]:tracking-wide [&_thead_th]:text-muted-foreground">
                            <thead>
                                <tr>
                                    <th>ID</th>
                                    <th>Status</th>
                                    <th>Principal</th>
                                    <th>Original Product</th>
                                    <th>Product Name</th>
                                    <th className="!text-right">Qty</th>
                                    <th>Unit</th>
                                    <th>Lot</th>
                                    <th>SJ Remarks</th>
                                </tr>
                            </thead>
                            <tbody>
                                {lines.map((l) => (
                                    <tr key={l.id}>
                                        <td className="font-bold tabular-nums text-primary">#{l.id}</td>
                                        <td>{l.status ? <StatusBadge tone={statusTone(l.status)}>{l.status}</StatusBadge> : '—'}</td>
                                        <td>{l.principalName || '—'}</td>
                                        <td>{l.barang || '—'}</td>
                                        <td className="font-semibold text-foreground">{l.productName || '—'}</td>
                                        <td className="text-right tabular-nums">{l.qty || '—'}</td>
                                        <td>{l.satuan || '—'}</td>
                                        <td>{l.lotNumber || '—'}</td>
                                        <td className="max-w-[220px] truncate" title={l.remarks || undefined}>{l.remarks || '—'}</td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    </div>
                )}
            </article>
                </div>

                {/* RIGHT COLUMN — legacy's own shape, two jobs, two cards.
                    Sample DO is the 6→7 workflow (legacy's bottom comment + Generate Surat
                    Jalan). Shipment is the header shipping block + the #api booking block.

                    ⚠️ NO "manual vs API" mode switch. An earlier pass invented one; legacy has
                    no such choice. It has ONE shipment record whose fields are split by WHO
                    OWNS THEM — the courier fills BookingID / AWB Date / Service / Cost / Weight
                    (all five readonly at :752,:792,:798,:805,:824), a person fills Vendor, AWB,
                    Insurance and Packing (:758,:781,:811,:817) — plus a booking block that
                    HIDES ITSELF once an AWB exists (:663-671). That single condition, not a
                    toggle, is the whole state model. */}
                <div className="flex min-w-0 flex-col gap-4">

                {/* ── Sample DO — the 6→7 workflow, and nothing else ──────────────────────── */}
                <article className={SECTION_CARD}>
                    <header className={SECTION_HEAD}>
                        <h2 className={SECTION_TITLE}>Sample DO</h2>
                        <small className={SECTION_SUB}>
                            {q.statusId === 7
                                ? 'Already generated — reprint it from View Sample DO.'
                                : 'Moves the order to Surat Jalan and copies its packed lines onto a numbered DO.'}
                        </small>
                    </header>
                    <div className="flex flex-col gap-3.5 p-5">
                        <FloatingField
                            as="textarea"
                            label="Comment *"
                            className="[&_textarea]:min-h-16"
                            value={genForm.data.comment}
                            onChange={(e) => {
                                genForm.setData('comment', e.target.value);
                                if (genForm.errors.comment) genForm.clearErrors('comment');
                            }}
                        />
                        {genForm.errors.comment && <p className="m-0 text-[11px] font-semibold text-danger">{genForm.errors.comment}</p>}
                        {!canGenerate && (
                            <p className="m-0 text-[11px] font-medium text-muted-foreground italic">
                                Only available for Packing/Prepare orders with lines ready to ship.
                            </p>
                        )}
                        <Button type="button" disabled={!canGenerate} onClick={requestGenerate}
                            className="h-9 w-full rounded-lg bg-linear-to-br from-violet-500 to-primary text-[13px] font-bold text-white shadow-sm transition-[filter] hover:brightness-105">
                            Generate Sample DO
                        </Button>
                    </div>
                </article>

                {/* ── Shipment — the record, then the booking block while it is still empty ── */}
                <article className={SECTION_CARD}>
                    <header className={SECTION_HEAD}>
                        <h2 className={SECTION_TITLE}>Shipment</h2>
                        <small className={SECTION_SUB}>
                            {hasAwb
                                ? 'Booked. Track it and print the label from the buttons above.'
                                : 'Book with REX below, or type an AWB you already have.'}
                        </small>
                    </header>

                    <div className="flex flex-col gap-3.5 p-5">
                        {/* The record. Vendor / AWB / Insurance / Packing are yours; the rest is
                            the courier's and stays read-only exactly as legacy had it — those
                            values arrive from booking, or from Track → "Copy to shipping form". */}
                        {/* Destination postcode — ONE field on this screen, two jobs. It is the
                            contact's stored postcode (saved to companycp, legacy
                            btn-updateheadercp) AND the destination Check Rates quotes against,
                            so the courier block no longer carries a second copy. Only 33 of 805
                            contacts have one stored, which is why legacy put an editor here. */}
                        <div className="flex items-end gap-2">
                            <FloatingField className="flex-1" label="Destination Postal Code"
                                value={cpForm.data.CompanyCPKodePos}
                                onChange={(e) => {
                                    cpForm.setData('CompanyCPKodePos', e.target.value);
                                    courier.setData('destZip', e.target.value);
                                }} />
                            <Button type="button" variant="outline" onClick={saveCompanyCp}
                                disabled={cpForm.processing}
                                className="h-9 shrink-0 rounded-lg text-[13px] font-bold">
                                {cpForm.processing
                                    ? <><Loader2 className="mr-2 size-3.5 animate-spin" />Saving…</>
                                    : 'Save to Contact'}
                            </Button>
                        </div>
                        {cpForm.errors.CompanyCPKodePos && (
                            <p className="m-0 text-[11px] font-semibold text-danger">{cpForm.errors.CompanyCPKodePos}</p>
                        )}

                        <div className="grid grid-cols-2 gap-3">
                            <FloatingField as="select" label="Vendor" value={awbForm.data.Vendor}
                                onChange={(e) => awbForm.setData('Vendor', e.target.value)}>
                                <option value="">Select Vendor</option>
                                {vendors.map((v) => <option key={v.id} value={v.id}>{v.name}</option>)}
                            </FloatingField>
                            <FloatingField label="AWB" value={awbForm.data.AWB}
                                onChange={(e) => awbForm.setData('AWB', e.target.value)} />
                        </div>
                        <div className="grid grid-cols-2 gap-3">
                            <FloatingField label="Booking ID" value={q.bookingId || ''} readOnly />
                            <FloatingField type="date" label="AWB Date" value={awbForm.data.TanggalAWBInput} readOnly />
                        </div>
                        <div className="grid grid-cols-2 gap-3">
                            {/* Legacy's composed "Jenis Pengiriman" — "Rex (EXP)", not a bare
                                code (:798-799). Bound to the display-only `serviceLabel` prop,
                                NOT to awbForm.ServiceCode: that field is submitted on save, and
                                writing the composed string into it is exactly how legacy left
                                both "REG" and "Rex (EXP)" in suratjalan.ServiceCode. */}
                            <FloatingField label="Jenis Pengiriman" value={q.serviceLabel || ''} readOnly />
                            <FloatingField label="Shipping Cost (Rp)" value={awbForm.data.Tarif} readOnly />
                        </div>
                        <div className="grid grid-cols-2 gap-3">
                            {numberField('Insurance (Rp)', 'Asuransi')}
                            {numberField('Packing (Rp)', 'Packing')}
                        </div>
                        <FloatingField label="Total Weight (gram)" value={awbForm.data.TotalWeight} readOnly />

                        {Object.entries(awbForm.errors).map(([k, msg]) => (
                            <p key={k} className="m-0 text-[11px] font-semibold text-danger">{msg}</p>
                        ))}

                        {/* legacy btn-updateheaderawb. Enabled at 6 AND 7 — status 7 is the state
                            this screen exists to serve, and it had no save path at all until
                            2026-08-24 (tests/js/suratJalanShipping.test.jsx pins it). */}
                        <Button type="button" variant="outline" disabled={!canSaveShipping || awbForm.processing}
                            onClick={saveShipping} className="h-9 w-full rounded-lg text-[13px] font-bold">
                            {awbForm.processing
                                ? <><Loader2 className="mr-2 size-3.5 animate-spin" />Saving…</>
                                : 'Save Shipping'}
                        </Button>

                        {/* ── Book with REX — legacy's #api block, hidden once an AWB exists
                            (:663-671 hides the vendor picker and Generate AWB together, and the
                            block only ever appeared via that picker). Re-booking would bill a
                            second uncancellable consignment. */}
                        {!hasAwb && (
                            <>
                                <div className="mt-1 flex items-center gap-2">
                                    <span className="text-[11px] font-bold uppercase tracking-[0.05em] text-muted-foreground">Book with REX</span>
                                    <span className="h-px flex-1 bg-border" />
                                </div>

                                {/* No postcode field here — "Destination Postal Code" above is the
                                    single source, and it feeds courier.destZip on change. Two
                                    boxes for one value is what made the old layout unreadable. */}
                                <FloatingField label="Shipping Weight (gram)" value={courier.data.weight}
                                    onChange={(e) => courier.setData('weight', e.target.value)} />
                                <div className="grid grid-cols-3 gap-3">
                                    {['length', 'width', 'height'].map((k) => (
                                        <FloatingField key={k} label={k[0].toUpperCase() + k.slice(1) + ' (cm)'}
                                            value={courier.data[k]} onBlur={recalcVolume}
                                            onChange={(e) => courier.setData(k, e.target.value)} />
                                    ))}
                                </div>
                                {/* "Add …" BUYS the service. The rupiah REX then charges lands in
                                    the Insurance/Packing fields above — same words, so the verb
                                    is what keeps them apart. */}
                                <div className="flex flex-wrap items-center gap-4">
                                    {[['insurance', 'Add insurance'], ['packing', 'Add packing']].map(([k, label]) => (
                                        <label key={k} className="flex cursor-pointer items-center gap-2 text-xs font-medium text-foreground">
                                            <input type="checkbox" className="size-3.5 accent-[var(--color-primary)]"
                                                checked={courier.data[k]} onChange={(e) => courier.setData(k, e.target.checked)} />
                                            {label}
                                        </label>
                                    ))}
                                </div>
                                <div className="grid grid-cols-2 gap-3">
                                    <FloatingField label="Goods Value (Rp)" value={courier.data.price}
                                        onChange={(e) => courier.setData('price', e.target.value)} />
                                    <FloatingField label="Note" value={courier.data.note}
                                        onChange={(e) => courier.setData('note', e.target.value)} />
                                </div>

                                <Button type="button" variant="outline" onClick={checkRates} disabled={ratesHttp.processing}
                                    className="h-9 w-full rounded-lg text-[13px] font-bold">
                                    {ratesHttp.processing
                                        ? <><Loader2 className="mr-2 size-3.5 animate-spin" />Checking…</>
                                        : <><Truck className="mr-2 size-3.5" />Check Rates</>}
                                </Button>

                                {rates.length > 0 && (
                                    <FloatingField as="select" label="Jenis Pengiriman" value={courier.data.serviceCode}
                                        onChange={(e) => courier.setData('serviceCode', e.target.value)}>
                                        <option value="">Pilih Jenis Pengiriman</option>
                                        {rates.map((r) => <option key={r.code} value={r.code}>{r.label}</option>)}
                                    </FloatingField>
                                )}

                                {Object.entries(courier.errors).map(([k, msg]) => (
                                    <p key={k} className="m-0 text-[11px] font-semibold text-danger">{msg}</p>
                                ))}

                                <Button type="button" disabled={!courier.data.serviceCode || courier.processing}
                                    onClick={() => setAwbOpen(true)}
                                    className="h-9 w-full rounded-lg bg-linear-to-br from-violet-500 to-primary text-[13px] font-bold text-white shadow-sm transition-[filter] hover:brightness-105">
                                    Book AWB
                                </Button>
                            </>
                        )}
                    </div>
                </article>
                </div>
            </div>

            {/* Booking is irreversible and billable, so it gets its own confirmation. */}
            <Dialog open={awbOpen} onOpenChange={(open) => { if (!open) setAwbOpen(false); }}>
                <DialogContent className="max-w-md">
                    <DialogTitle>Book this shipment with REX?</DialogTitle>
                    <p className="m-0 text-xs leading-relaxed text-muted-foreground">
                        This creates a <strong>real, billable consignment</strong> for sample order #{q.id} —
                        service <strong>{courier.data.serviceCode}</strong>, {courier.data.weight} gram to {courier.data.destZip}.
                        REX has no cancel endpoint, so it cannot be undone from this app.
                    </p>
                    <form onSubmit={submitAwb}>
                        <DialogFooter>
                            <Button type="submit" disabled={courier.processing}>
                                {courier.processing
                                    ? <><Loader2 className="mr-2 size-3.5 animate-spin" />Booking…</>
                                    : 'Book AWB'}
                            </Button>
                            <Button type="button" variant="outline" onClick={() => setAwbOpen(false)}>Close</Button>
                        </DialogFooter>
                    </form>
                </DialogContent>
            </Dialog>

            {/* Tracking history (legacy trackingawb.php modal). */}
            <Dialog open={trackOpen} onOpenChange={(open) => { if (!open) setTrackOpen(false); }}>
                <DialogContent className="max-w-lg">
                    <DialogTitle>Track History</DialogTitle>
                    {!tracking ? (
                        <p className="m-0 flex items-center gap-2 text-xs text-muted-foreground">
                            <Loader2 className="size-3.5 animate-spin" /> Menghubungi REX…
                        </p>
                    ) : (
                        <div className="flex flex-col gap-2">
                            <dl className="grid grid-cols-1 gap-0.5">
                                {[['AWB', tracking.awb], ['Pengiriman', tracking.serviceCode],
                                  ['Final Status', tracking.finalStatus], ['Asal', tracking.origin],
                                  ['Tujuan', tracking.destination]].map(([label, value]) => (
                                    <div key={label} className="grid grid-cols-[minmax(0,110px)_1fr] items-baseline gap-2">
                                        <dt className="m-0 text-[11px] font-medium text-muted-foreground">{label}</dt>
                                        <dd className="m-0 text-xs font-semibold text-foreground">{value || '—'}</dd>
                                    </div>
                                ))}
                            </dl>
                            <div className="max-h-64 overflow-y-auto">
                                <table className="w-full border-collapse [&_tbody_td]:border-b [&_tbody_td]:border-border/50 [&_tbody_td]:p-[8px_10px] [&_tbody_td]:text-[11px] [&_thead_th]:border-b [&_thead_th]:border-border [&_thead_th]:p-[8px_10px] [&_thead_th]:text-left [&_thead_th]:text-[11px] [&_thead_th]:font-semibold [&_thead_th]:uppercase [&_thead_th]:text-muted-foreground">
                                    <thead><tr><th>Status</th><th>Date &amp; Time</th><th>Receiver</th></tr></thead>
                                    <tbody>
                                        {(tracking.history || []).length === 0 ? (
                                            <tr><td colSpan={3} className="text-muted-foreground">Belum ada riwayat.</td></tr>
                                        ) : tracking.history.map((h, i) => (
                                            <tr key={i}>
                                                <td>{h.status}</td>
                                                <td className="whitespace-nowrap">{h.dateTime || '—'}</td>
                                                <td>{h.receiver || '—'}</td>
                                            </tr>
                                        ))}
                                    </tbody>
                                </table>
                            </div>
                        </div>
                    )}
                    <DialogFooter>
                        {tracking?.autofill && (
                            <Button type="button" onClick={applyTrackingAutofill}>Copy to shipping form</Button>
                        )}
                        <Button type="button" variant="outline" onClick={() => setTrackOpen(false)}>Close</Button>
                    </DialogFooter>
                </DialogContent>
            </Dialog>

            <Dialog open={genOpen} onOpenChange={(open) => { if (!open) setGenOpen(false); }}>
                <DialogContent className="max-w-md">
                    <DialogTitle>Generate Sample DO — Sample Order #{q.id}?</DialogTitle>
                    <p className="m-0 text-xs leading-relaxed text-muted-foreground">
                        {lines.length} line(s) will be added to a new Sample DO (auto-numbered); the order status becomes <strong>Surat Jalan</strong>.
                        The shipping fields above are saved automatically before generating.
                    </p>
                    <form onSubmit={submitGenerate}>
                        <DialogFooter>
                            <Button type="submit" disabled={genForm.processing}>Generate</Button>
                            <Button type="button" variant="outline" onClick={() => setGenOpen(false)}>Close</Button>
                        </DialogFooter>
                    </form>
                </DialogContent>
            </Dialog>
        </section>
    );
}

SampleOrderSuratJalanDetail.layout = [AppLayout];
