import { useEffect, useMemo, useRef, useState } from 'react';
import { Link, useForm, useHttp } from '@inertiajs/react';
import { ArrowRightLeft, ChevronDown, Trash2, Pencil, MapPin, Package } from 'lucide-react';
import AppLayout from '@/Layouts/AppLayout';
import { cn } from '@/lib/utils';
import { Switch } from '@/Components/ui/switch';
import { FloatingField } from '@/Components/Proto/UI/FloatingField';
import { SearchableSelect } from '@/Components/Form/SearchableSelect';
import { TutorialPanel, TutorialButton } from '@/Components/Tutorial/TutorialPanel';
import { useTutorial } from '@/lib/tutorials';
import { QuotationProductModal } from '@/Components/MenuQuotations/Quotations/QuotationProductModal';
import { RelatedDocsSection } from '@/Components/MenuQuotations/Quotations/RelatedDocsSection';
import { CustomerArList } from '@/Components/MenuQuotations/CustomerOutstanding/CustomerArList';
import { useCustomerAr } from '@/Hooks/useCustomerAr';
import { footerDefaultId, footerLists, quotationTitleDefaultId } from '@/Components/MenuQuotations/Quotations/footerDefaults';
import { CustomerArAlertModal } from '@/Components/MenuQuotations/CustomerOutstanding/CustomerArAlertModal';
import { arProblems } from '@/Components/MenuQuotations/CustomerOutstanding/arProblems';
import { useToast } from '@/Components/Toast';
import { formatIdr as fmtIdr, formatUsd as fmtUsd } from '@/lib/currencyFormat';
import { useNumberFormat } from '@/Hooks/useNumberFormat';

const SECTION_CARD = 'flex flex-col overflow-visible rounded-xl border border-border bg-card shadow-sm';
const SECTION_HEADER = 'flex items-center gap-3 border-b border-border min-h-[50px] px-6 py-2.5';
// ONE column template, shared by the PO Details HEADER and its BODY. Column 2 holds
// Is Order (header) and Delivery Date (body), so those two line up by construction at
// every card width — margins cannot do that, the tracks are fr-based. Column 3 is the
// contact/address area; the "Is Order = No" placeholder spans columns 2+3 so the slot
// under the toggle is not left as a hole (user 2026-08-05).
const PO_GRID = 'grid grid-cols-1 gap-x-4 gap-y-4 lg:grid-cols-[1.25fr_0.75fr_4.3fr]';
const COLLAPSED_BODY = 'self-start !h-auto [&>*:not(header)]:hidden [&>header]:border-b-0';

function useCollapsible(initial = false) {
    const [collapsed, setCollapsed] = useState(initial);
    return { collapsed, toggle: () => setCollapsed((v) => !v) };
}

function CollapseButton({ onClick, collapsed, label }) {
    return (
        <button type="button" aria-label={label} aria-expanded={!collapsed} onClick={onClick}
            className="inline-grid size-7.5 place-items-center justify-self-end rounded-full text-muted-foreground transition-colors hover:bg-secondary hover:text-card-foreground ml-auto">
            <ChevronDown aria-hidden="true" size={16} className={`transition-transform ${collapsed ? '' : 'rotate-180'}`} />
        </button>
    );
}

const STEP_BADGE = 'inline-grid size-8 shrink-0 place-items-center rounded-md bg-accent text-[13px] font-extrabold tracking-[-0.01em] text-primary';
const SECTION_TITLE = 'm-0 text-base font-bold leading-[1.3] tracking-[-0.005em] text-card-foreground';
const FIELD_LABEL = 'text-[11px] font-extrabold uppercase tracking-wide text-muted-foreground';
const PRIMARY_BTN = 'inline-flex h-9 items-center justify-center gap-1.5 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';
const SECONDARY_BTN = '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';
const FieldError = ({ msg }) => (msg ? <p className="mt-1 text-[11px] font-semibold text-danger">{msg}</p> : null);

/**
 * Create Quotation — v2 layout. Re-architected Section 3 (PO Details) & Section 4 (Product Items)
 * to match exact design specification mockup.
 *
 * Serves THREE routes since 2026-08-11 (quotations.create, quotations.create-for-others,
 * quotations.revise.create) — the modes were ported over from v1, which now lives at
 * /proto/quotations/create-v1 as a frozen reference (GH #380).
 *
 * ⚠️ `salesPicker` is ORTHOGONAL to `forOthers`, not a synonym. Revise sets salesPicker on its
 * own when the source quotation was raised on someone else's behalf
 * (QuotationController::reviseCreate, `UserIDInput !== UserIDSales`) with forOthers FALSE — a
 * third configuration, and the one that breaks first if these two are ever collapsed into one
 * flag. The two paths also draw from DIFFERENT user lists: for-others gets users holding an
 * active `userdivision` row (salesPicklist()), revise gets every active user.
 */
export default function QuotationsCreateV2({ companies = [], options, prefill = null, reviseFrom = null, salesPicker = false, salesUsers = [], forOthers = false }) {
    const form = useForm({
        QuotationSubjectID: prefill?.QuotationSubjectID ?? '', QuotationTypeID: prefill?.QuotationTypeID ?? '',
        // Seeded like the six footer terms below — the field never opens at the empty/0 state a
        // user then has to clear. See quotationTitleDefaultId() for why 0 counts as absent.
        QuotationTitleID: quotationTitleDefaultId(options, prefill),
        QuotationDate: prefill?.QuotationDate ?? '', Comment: prefill?.Comment ?? '',
        CompanyID: prefill?.CompanyID ?? '', CompanyCPID: prefill?.CompanyCPID ?? '', CC: prefill?.CC ?? '',
        USDRate: prefill?.USDRate ?? '',
        CustomerPONo: prefill?.CustomerPONo ?? '', PODate: prefill?.PODate ?? '', IsOrder: prefill?.IsOrder ?? false, DeliveryDate: prefill?.DeliveryDate ?? '',
        InvoiceAddressID: prefill?.InvoiceAddressID ?? '', DeliveryAddressID: prefill?.DeliveryAddressID ?? '',
        // Seeded from FOOTER_DEFAULTS so a fresh form opens with the house terms already in
        // place, instead of six blank required selects that only fill after a Company is picked.
        QuotationPriceDescID: footerDefaultId('QuotationPriceDescID', options, prefill),
        QuotationSalesTermID: footerDefaultId('QuotationSalesTermID', options, prefill),
        QuotationPaymentTermID: footerDefaultId('QuotationPaymentTermID', options, prefill),
        QuotationDeliveryTimeID: footerDefaultId('QuotationDeliveryTimeID', options, prefill),
        QuotationValidityID: footerDefaultId('QuotationValidityID', options, prefill),
        QuotationStockAvailibilityID: footerDefaultId('QuotationStockAvailibilityID', options, prefill),
        UserIDSales: prefill?.UserIDSales ?? '',
        items: prefill?.items ?? [],
    });
    const { data, setData, errors, processing } = form;
    const tutorial = useTutorial();

    const onRateChange = (raw) => setData((d) => {
        const r = parseFloat(raw) || 0;
        // Mirrors Create.jsx — see the full rationale there. A string change that is not a
        // value change (2dp blur padding) must not re-price the lines.
        if (r === (parseFloat(d.USDRate) || 0)) return { ...d, USDRate: raw };
        return {
            ...d,
            USDRate: raw,
            items: d.items.map((it) => ({ ...it, UnitPriceIDR: r > 0 ? Math.round((parseFloat(it.UnitPriceUSD) || 0) * r) : 0 })),
        };
    });
    // decimals: 2, same as Create.jsx. This page is never reached with a USDRate prefill today
    // (create() ships quotationProjectPrefill(), which carries no rate), so this is defence
    // against the two forms drifting, not a live fix.
    const usdRateField = useNumberFormat({ value: data.USDRate, onChange: onRateChange, decimals: 2 });

    const [productModalOpen, setProductModalOpen] = useState(false);
    const secHeader = useCollapsible();
    const secCustomer = useCollapsible();
    const secPo = useCollapsible();
    const secItems = useCollapsible();
    const secFooter = useCollapsible();
    const [editingLine, setEditingLine] = useState(null);

    const appList = useHttp({});
    const [applications, setApplications] = useState([]);
    const companyRef = useRef(data.CompanyID);
    companyRef.current = data.CompanyID;
    useEffect(() => {
        appList.cancel();
        setApplications([]);
        if (!data.CompanyID) return;
        const requestedId = data.CompanyID;
        appList.get(route('quotations.applications.byCompany', { company: requestedId }), {
            onSuccess: (resp) => {
                if (companyRef.current === requestedId) setApplications(Array.isArray(resp) ? resp : []);
            },
        });
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [data.CompanyID]);

    const projList = useHttp({});
    const [projects, setProjects] = useState([]);
    useEffect(() => {
        projList.cancel();
        setProjects([]);
        if (!data.CompanyID) return;
        const requestedId = data.CompanyID;
        projList.get(route('quotations.projects.byCompany', { company: requestedId }), {
            onSuccess: (resp) => {
                if (companyRef.current === requestedId) setProjects(Array.isArray(resp) ? resp : []);
            },
        });
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [data.CompanyID]);

    const { show: showToast } = useToast();
    const customerAr = useCustomerAr(data.CompanyID, 'customer-ar.list.byCompany');
    // AR guard — fires ONLY on submit (user decision 2026-08-07: the auto-popup on company
    // select was noise; the moment that matters is the one before the quotation is created).
    const [arAlert, setArAlert] = useState(false);
    const arIssue = arProblems(customerAr.data);

    const footerHistory = useHttp({});
    const skipInitialFooter = useRef(reviseFrom != null);
    useEffect(() => {
        footerHistory.cancel();
        if (!data.CompanyID) return;
        if (skipInitialFooter.current) { skipInitialFooter.current = false; return; }
        const requestedId = data.CompanyID;
        footerHistory.get(route('quotations.companies.last-footer-terms', { company: requestedId }), {
            onSuccess: (terms) => {
                if (companyRef.current !== requestedId) return;
                const lists = footerLists(options);
                setData((d) => {
                    const next = { ...d };
                    for (const [field, listRaw] of Object.entries(lists)) {
                        const list = listRaw || [];
                        const id = terms?.[field];
                        const valid = id && list.some((o) => o.id === id);
                        next[field] = valid ? String(id) : (list[0] ? String(list[0].id) : '');
                    }
                    return next;
                });
            },
        });
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [data.CompanyID]);

    // Create-for-Others drives the company list from the Sales pick instead of shipping it
    // with the page (legacy createquotationsalesadmin.php renders #InsertCompany with only
    // "Select Company" and fills it from getcompanybysales.php on every #InsertUserIDS
    // change). Every other entry point keeps using the server-supplied list verbatim.
    const [salesCompanies, setSalesCompanies] = useState([]);
    const compBySales = useHttp({});
    const salesRef = useRef(data.UserIDSales);
    salesRef.current = data.UserIDSales;
    useEffect(() => {
        if (!forOthers) return;
        compBySales.cancel();
        setSalesCompanies([]);
        if (!data.UserIDSales) return;
        const requestedSales = data.UserIDSales;
        compBySales.get(route('quotations.companies-by-sales', { sales: requestedSales }), {
            onSuccess: (resp) => {
                // Same late-response gate as the company-driven lookups above: a slow reply
                // for a sales the user already left must not repopulate the list.
                if (salesRef.current === requestedSales) setSalesCompanies(resp?.companies ?? []);
            },
        });
        // eslint-disable-next-line react-hooks/exhaustive-deps
    }, [data.UserIDSales, forOthers]);
    // Only the for-others path is state-backed; Create and Revise keep reading the server
    // prop directly, so a future partial reload of `companies` still reaches them.
    const companyList = forOthers ? salesCompanies : companies;

    const companyOptions = useMemo(
        () => companyList.map((c) => ({ id: c.id, name: c.salesName ? `${c.name} (${c.salesName})` : c.name })),
        [companyList],
    );
    const selectedCompany = useMemo(() => companyList.find((c) => c.id === data.CompanyID), [companyList, data.CompanyID]);
    const contactOptions = selectedCompany ? selectedCompany.contacts.map((c) => ({ id: c.id, name: c.name })) : [];
    const contactById = (id) => selectedCompany?.contacts.find((c) => c.id === id);

    const rate = parseFloat(data.USDRate) || 0;

    const onCompanyChange = (id) => {
        setData((d) => ({ ...d, CompanyID: id, CompanyCPID: '', CC: '', InvoiceAddressID: '', DeliveryAddressID: '' }));
    };
    const onContactChange = (id) => {
        setData('CompanyCPID', id);
    };
    // Changing the Sales invalidates the company chosen under the previous one — legacy
    // resets #InsertCompany to 0 and re-fetches the CP list on every #InsertUserIDS change.
    // Revise keeps the plain setter: there the company list is not sales-driven.
    const onSalesChange = (id) => setData((d) => (forOthers
        ? { ...d, UserIDSales: id, CompanyID: '', CompanyCPID: '', CC: '', InvoiceAddressID: '', DeliveryAddressID: '' }
        : { ...d, UserIDSales: id }));

    const subjectById = (id) => options.subjects.find((s) => String(s.id) === String(id));
    const subjectForcesOrder = !!Number(subjectById(data.QuotationSubjectID)?.isOrder);
    const clearItemQty = (items) => items.map((x) => ({ ...x, OrderQuantity: 0 }));
    // SearchableSelect emits the raw id, NOT a change event — reading e.target.value here threw
    // "Cannot read properties of undefined" and left Subject blank on every pick. (v1's handler
    // already took the id; this one still had the signature from when the field was a <select>.)
    const onSubjectChange = (id) => {
        setData((d) => {
            const wasForced = !!Number(subjectById(d.QuotationSubjectID)?.isOrder);
            const nowForced = !!Number(subjectById(id)?.isOrder);
            if (nowForced) return { ...d, QuotationSubjectID: id, IsOrder: true };
            if (wasForced) return { ...d, QuotationSubjectID: id, IsOrder: false, DeliveryDate: '', InvoiceAddressID: '', DeliveryAddressID: '', items: clearItemQty(d.items) };
            return { ...d, QuotationSubjectID: id };
        });
    };

    const openAddProduct = () => { setEditingLine(null); setProductModalOpen(true); };
    const openEditProduct = (line) => { setEditingLine(line); setProductModalOpen(true); };
    const closeProductModal = () => { setProductModalOpen(false); setEditingLine(null); };
    const upsertProduct = (line) => setData('items', editingLine
        ? data.items.map((x) => (x._id === editingLine._id ? { ...line, _id: editingLine._id } : x))
        : [...data.items, line]);
    const removeProduct = (lineId) => setData('items', data.items.filter((x) => x._id !== lineId));

    // Three entry points, three targets: Revise/Recreate, Create for Others (its company
    // gate is inverted, so it cannot share quotations.store), and plain Create.
    const submitTarget = () => {
        if (reviseFrom) return route('quotations.revise.store', reviseFrom);
        if (forOthers) return route('quotations.store-for-others');
        return route('quotations.store');
    };

    const pageTitle = reviseFrom ? 'Revise Quotation' : (forOthers ? 'Create Quotation for Others' : 'Create Quotation');

    // onError was absent here too — locked text, no onSuccess (server sends that toast).
    const postQuotation = () => form.post(submitTarget(), {
        onError: () => showToast('Please check the form and try again.', 'error'),
    });

    const submit = (e) => {
        e.preventDefault();
        // CLIENT-ONLY legacy-parity gates (createquotation.php:2557 + 2595-2598): the USD rate must
        // be > 0, and an Is-Order quotation needs BOTH the Delivery and Invoice contact persons. The
        // server keeps USDRate min:0 and both CPs nullable on purpose so revising a legacy quotation
        // that holds NULL never 422s — this create gate just refuses NEW bad submits, like legacy's
        // alert()+return (DeliveryDate + OrderQuantity already carry required_if on the server; the
        // two CPs were the asymmetric miss).
        const problems = [];
        if (!(parseFloat(data.USDRate) > 0)) problems.push('USD Rate must be greater than 0');
        if (data.IsOrder && !data.DeliveryAddressID) problems.push('Delivery Address contact person is required for an order');
        if (data.IsOrder && !data.InvoiceAddressID) problems.push('Invoice Address contact person is required for an order');
        if (problems.length) {
            showToast(problems.join('. '), 'error');
            return;
        }
        if (arIssue.hasProblem) {
            setArAlert(true);
            return;
        }
        postQuotation();
    };

    return (
        <section className={cn('flex min-w-0 flex-col gap-4.5 transition-[margin] duration-200', tutorial.marginClass)}>
            <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={route('quotations.index')} className="text-muted-foreground no-underline hover:text-primary">Quotations</Link>
                        <span aria-hidden="true">›</span>
                        <span className="text-foreground">{pageTitle}</span>
                    </p>
                    <h1 className="m-0 flex items-center gap-2 text-xl font-bold leading-[1.2] text-card-foreground">
                        {reviseFrom ? 'Revise / Recreate Quotation' : pageTitle}
                    </h1>
                </div>
                <div className="flex flex-wrap items-center justify-end gap-2">
                    {/* Hidden on user request 2026-08-10, same as v1 — see Create.jsx. */}
                    {tutorial.guide && <TutorialButton onClick={tutorial.toggle} active={tutorial.isOpen} className="hidden" />}
                    <Link href={route('quotations.index')} className={SECONDARY_BTN}>Back</Link>
                    <button className={SECONDARY_BTN} type="button" onClick={() => form.reset()}>Reset</button>
                </div>
            </header>

            <form className="flex flex-col gap-4.5" onSubmit={submit}>
                <section className="grid items-stretch gap-6 grid-cols-[minmax(0,0.82fr)_minmax(0,1.28fr)] max-[1180px]:grid-cols-1">
                    {/* 1. Quotation Header */}
                    <article className={cn(SECTION_CARD, 'h-full min-w-0', secHeader.collapsed && COLLAPSED_BODY)}>
                        <header className={SECTION_HEADER}>
                            <span className={STEP_BADGE}>1</span>
                            <h2 className={SECTION_TITLE}>Quotation Header</h2>
                        </header>
                        <div className="flex flex-1 flex-col gap-3.5 p-6">
                            <div className="grid grid-cols-2 gap-3.5 max-[760px]:grid-cols-1">
                                <div data-tut="subject">
                                    <SearchableSelect label="Subject *" placeholder="Select subject" options={options.subjects} value={data.QuotationSubjectID} onChange={onSubjectChange} />
                                    <FieldError msg={errors.QuotationSubjectID} />
                                </div>
                                <div>
                                    <SearchableSelect dataTut="type" label="Type *" placeholder="Select type" options={options.types} value={data.QuotationTypeID} onChange={(v) => setData('QuotationTypeID', v)} />
                                    <FieldError msg={errors.QuotationTypeID} />
                                </div>
                            </div>
                            {/* Row 2 is NOT a half-half split (user 2026-08-24: "date-nya dipendekin…
                                mepet aja sesuai filled"). The date track is `max-content`, so it
                                measures the FILLED control — the date text plus its picker icon — and
                                nothing is reserved beyond that. Every pixel it gives up goes to
                                Quotation Title, whose options are whole sentences (82 characters
                                today) rather than labels. The 2×2 rows above are untouched. */}
                            <div className="grid grid-cols-[max-content_minmax(0,1fr)] gap-3.5 max-[760px]:grid-cols-1">
                                <div>
                                    <FloatingField data-tut="date" label="Date *" type="date" value={data.QuotationDate} onChange={(e) => setData('QuotationDate', e.target.value)} />
                                    <FieldError msg={errors.QuotationDate} />
                                </div>
                                <div>
                                    {/* singleLine: even with Date narrowed the field is ~350px, so the
                                        longest sentences still would not fit. Pinning the control to one
                                        line keeps the row level with Date; the full value lives in the
                                        tooltip, and the dropdown never truncates. */}
                                    <SearchableSelect singleLine dataTut="title" label="Quotation Title *" placeholder="Select an option" options={options.titles} value={data.QuotationTitleID} onChange={(v) => setData('QuotationTitleID', v)} />
                                    <FieldError msg={errors.QuotationTitleID} />
                                </div>
                            </div>
                            <FloatingField data-tut="comment" as="textarea" label="Comment Internal" rows={2} value={data.Comment} onChange={(e) => setData('Comment', e.target.value)} maxLength={500} />
                        </div>
                    </article>

                    {/* 2. Customer Information */}
                    <article className={cn(SECTION_CARD, 'h-full min-w-0', secCustomer.collapsed && COLLAPSED_BODY)}>
                        <header className={SECTION_HEADER}>
                            <span className={STEP_BADGE}>2</span>
                            <h2 className={SECTION_TITLE}>Customer Information</h2>
                        </header>
                        <div className="flex-1 p-6">
                            <div className="grid items-stretch gap-8 grid-cols-2 max-[760px]:grid-cols-1">
                                <div className="[&>*+*]:mt-3.5">
                                    {/* For-others picks the OWNER first — the company list is empty
                                        until a sales is chosen, so this must sit ABOVE Company. The
                                        revise-with-picker case renders its picker after Contact
                                        instead; there the company list is not sales-driven. */}
                                    {salesPicker && forOthers && (
                                        <div>
                                            <SearchableSelect label="Sales Owner *" placeholder="Select sales" options={salesUsers} value={data.UserIDSales} onChange={onSalesChange} />
                                            <FieldError msg={errors.UserIDSales} />
                                        </div>
                                    )}
                                    <SearchableSelect dataTut="company" label="Company *" placeholder={forOthers && !data.UserIDSales ? 'Pilih sales dulu' : 'Select company'} options={companyOptions} value={data.CompanyID} onChange={onCompanyChange} />
                                    <FieldError msg={errors.CompanyID} />
                                    <div className="mt-3.5 grid grid-cols-2 gap-2.5">
                                        {/* labelClassName 11px: FloatingField's resting label is 12px
                                            while SearchableSelect's is 11px, so without this these
                                            two read BIGGER than every label around them (same fix
                                            already applied in v1). tabIndex -1: read-only mirrors. */}
                                        <FloatingField label="Division" type="text" readOnly tabIndex={-1} labelClassName="text-[11px]" value={selectedCompany?.division || ''} />
                                        <FloatingField label="Industry" type="text" readOnly tabIndex={-1} labelClassName="text-[11px]" value={selectedCompany?.industry || ''} />
                                    </div>
                                    <div className="mt-3.5 border-t border-dashed border-border pt-3">
                                        <span className="text-[10px] font-extrabold uppercase tracking-[0.025em] text-muted-foreground">Company Address</span>
                                        <p className="mt-1 min-h-[18px] italic text-xs text-muted-foreground">{selectedCompany ? (selectedCompany.address || '—') : 'No company selected'}</p>
                                    </div>
                                </div>
                                <div className="[&>*+*]:mt-3.5">
                                    <SearchableSelect dataTut="contact" label="Contact Person *" placeholder={data.CompanyID ? 'Select contact' : 'Pilih company dulu'} options={contactOptions} value={data.CompanyCPID} onChange={onContactChange} />
                                    <FieldError msg={errors.CompanyCPID} />
                                    <div className="mt-3.5 border-t border-dashed border-border pt-3">
                                        <span className="text-[10px] font-extrabold uppercase tracking-[0.025em] text-muted-foreground">Address</span>
                                        <p className="mt-1 min-h-[18px] italic text-xs text-muted-foreground">{contactById(data.CompanyCPID)?.address || 'No contact selected'}</p>
                                    </div>
                                    <FloatingField data-tut="cc" label="CC" type="text" value={data.CC} onChange={(e) => setData('CC', e.target.value)} />
                                    {salesPicker && !forOthers && (
                                        <div>
                                            <SearchableSelect label="Sales Owner *" placeholder="Select sales" options={salesUsers} value={data.UserIDSales} onChange={onSalesChange} />
                                            <FieldError msg={errors.UserIDSales} />
                                        </div>
                                    )}
                                </div>
                            </div>
                        </div>
                    </article>
                </section>

                {/* 3. PO Details — "super clean" layout (user spec 2026-08-05), translated into
                    OUR design system: 5/7 split (Order Info | Contact & Address), NO dividers —
                    grouping is carried by uppercase section labels + spacing; addresses live in
                    soft token boxes (bg-secondary, not gray-*); controls stay FloatingField /
                    SearchableSelect / shadcn Switch per the locked rules. */}
                <section className="grid items-stretch">
                    <article className={cn(SECTION_CARD, 'h-full min-w-0', secPo.collapsed && COLLAPSED_BODY)}>
                        {/* Header runs on PO_GRID too — that is what puts Is Order in the same
                            track as Delivery Date (user 2026-08-05: "is ordernya tu di header"
                            + "pas diatas delivery datenya"). */}
                        <header className={cn(SECTION_HEADER, PO_GRID, 'items-center gap-y-1')}>
                                <div className="flex items-center gap-3">
                                    <span className={STEP_BADGE}>3</span>
                                    <h2 className={SECTION_TITLE}>PO Details</h2>
                                </div>
                                {/* Clean toggle row (user 2026-08-05): sentence-case label + a small
                                    state PILL instead of the heavy bold Yes/No beside the switch. */}
                                {/* NO wrapping. The 0.75fr cell is ~116px once the sidebar is open,
                                    narrower than the toggle, and a wrapped Yes/No pill grows the
                                    whole header by a row. Spilling right instead is free — the
                                    header has nothing beside it — and keeps the LEFT edge, which
                                    is the edge that has to line up with Delivery Date. */}
                                <div className="flex items-center gap-2.5 whitespace-nowrap lg:self-stretch lg:border-l lg:border-border lg:pl-5" data-tut="is-order">
                                    {/* Header typography, not form-label typography: same bold +
                                        tight tracking as SECTION_TITLE, one size down and muted so
                                        it reads as a peer of "PO Details" without competing with
                                        it (user 2026-08-05: "dibikin lebi cocok sama section
                                        headernya"). */}
                                    <span className="shrink-0 text-sm font-bold tracking-[-0.005em] text-muted-foreground">Is Order</span>
                                    <Switch checked={data.IsOrder} disabled={subjectForcesOrder} onCheckedChange={(next) => setData((d) => ({ ...d, IsOrder: next, DeliveryDate: next ? d.DeliveryDate : '', InvoiceAddressID: next ? d.InvoiceAddressID : '', DeliveryAddressID: next ? d.DeliveryAddressID : '', items: next ? d.items : clearItemQty(d.items) }))} className="shrink-0 data-[state=checked]:bg-success" />
                                    <span className={`shrink-0 rounded-full px-2 py-0.5 text-[10px] font-bold ${data.IsOrder ? 'bg-success-bg text-success-text' : 'bg-secondary text-muted-foreground'}`}>
                                        {data.IsOrder ? 'Yes' : 'No'}
                                    </span>
                                    {subjectForcesOrder && <span className="text-[10.5px] font-medium text-muted-foreground/80">Otomatis aktif karena Subject order</span>}
                                </div>
                        </header>
                        <div className={cn(PO_GRID, 'flex-1 p-6')}>
                            {/* Explicit placement, so DOM order can stay readable when the
                                template collapses to one column on small screens. */}
                            {/* Legacy PO numbers are short codes — hard cap at 20 chars. */}
                            <FloatingField className="lg:col-start-1 lg:row-start-1" data-tut="po-number" label="PO Number" type="text" maxLength={20} value={data.CustomerPONo} onChange={(e) => setData('CustomerPONo', e.target.value)} />
                            <FloatingField className="lg:col-start-1 lg:row-start-2" label="PO Date" type="date" value={data.PODate} onChange={(e) => setData('PODate', e.target.value)} />
                            {data.IsOrder && (
                                /* Divider + gap between the PO pair and Delivery Date (user
                                   2026-08-06). row-span-2 so the rule runs the full height of
                                   PO Number + PO Date instead of ticking beside row 1 only; the
                                   header's Is Order cell carries the same border + pl so the two
                                   left edges stay on one line. */
                                <div className="lg:col-start-2 lg:row-span-2 lg:row-start-1 lg:border-l lg:border-border lg:pl-5">
                                    <FloatingField data-tut="delivery-date" label="Delivery Date *" type="date" value={data.DeliveryDate} onChange={(e) => setData('DeliveryDate', e.target.value)} />
                                    <FieldError msg={errors.DeliveryDate} />
                                </div>
                            )}

                            {/* RIGHT — back to the pre-spec look (user 2026-08-05: "mending kaya
                                tadi"): CP with its address BESIDE it, dashed rule between the two
                                groups, dashed placeholder while Is Order is off. */}
                            {data.IsOrder ? (
                                <div className="grid content-start gap-4 lg:col-start-3 lg:row-span-2 lg:row-start-1 lg:pl-2">
                                    <div className="grid grid-cols-2 items-start gap-4 max-[900px]:grid-cols-1">
                                        <SearchableSelect dataTut="delivery-contact" label="Delivery · Contact Person" placeholder={data.CompanyID ? 'Select contact' : 'Pilih company dulu'} options={contactOptions} value={data.DeliveryAddressID} onChange={(v) => setData('DeliveryAddressID', v)} />
                                        <div>
                                            <span className={cn(FIELD_LABEL, 'block mb-1')}>Delivery Address</span>
                                            <p className="flex items-start gap-1.5 text-[11px] italic leading-snug text-foreground">
                                                <MapPin aria-hidden="true" className="mt-0.5 size-3 shrink-0 text-primary" />
                                                <span>{contactById(data.DeliveryAddressID)?.address || <span className="italic text-muted-foreground">No address selected</span>}</span>
                                            </p>
                                        </div>
                                    </div>

                                    {/* No dashed rule between the groups (user 2026-08-05); no extra
                                        top padding either, so this select's top lines up with the
                                        Delivery Date box in the middle column. */}
                                    <div className="grid grid-cols-2 items-start gap-4 max-[900px]:grid-cols-1">
                                        <SearchableSelect dataTut="invoice-contact" label="Invoice · Contact Person" placeholder={data.CompanyID ? 'Select contact' : 'Pilih company dulu'} options={contactOptions} value={data.InvoiceAddressID} onChange={(v) => setData('InvoiceAddressID', v)} />
                                        <div>
                                            <span className={cn(FIELD_LABEL, 'block mb-1')}>Invoice Address</span>
                                            <p className="flex items-start gap-1.5 text-[11px] italic leading-snug text-foreground">
                                                <MapPin aria-hidden="true" className="mt-0.5 size-3 shrink-0 text-primary" />
                                                <span>{contactById(data.InvoiceAddressID)?.address || <span className="italic text-muted-foreground">No address selected</span>}</span>
                                            </p>
                                        </div>
                                    </div>
                                </div>
                            ) : (
                                /* Spans columns 2+3 — it starts right under the Is Order toggle, so
                                   the slot Delivery Date would occupy is filled instead of left as
                                   a hole (user 2026-08-05: "ungunya digedein sampe kebawah is order"). */
                                <div className="flex min-h-[110px] flex-col items-center justify-center rounded-lg border border-dashed border-border bg-accent/30 p-4 text-center lg:col-span-2 lg:col-start-2 lg:row-span-2 lg:row-start-1">
                                    <span className="text-[11px] font-semibold text-muted-foreground">Is Order = No</span>
                                    <span className="mt-1 text-[10px] italic text-muted-foreground/70">Billing &amp; Shipping details disabled</span>
                                </div>
                            )}
                        </div>
                    </article>
                </section>

                {/* 4. Product Items */}
                <section className={cn(SECTION_CARD, 'h-full min-w-0', secItems.collapsed && COLLAPSED_BODY)}>
                    <header className={cn(SECTION_HEADER, 'flex items-center justify-between')}>
                        <div className="flex items-center gap-3">
                            <span className={STEP_BADGE}>4</span>
                            <h2 className={SECTION_TITLE}>Product Items</h2>
                        </div>
                        <button className={cn(PRIMARY_BTN, 'whitespace-nowrap')} type="button" onClick={openAddProduct} data-tut="add-product">+ Add Product</button>
                    </header>
                    {/* Slimmer body while empty (user 2026-08-05) — nothing to show, so nothing to
                        reserve height for. */}
                    <div className={cn('flex-1', data.items.length ? 'p-6' : 'px-6 py-3.5')}>
                        {/* USD to IDR Rate — conversion icon INSIDE the box; while the list is empty
                            the "no products yet" hint rides on this same row instead of taking a
                            block of its own. */}
                        <div className={cn('flex flex-wrap items-center gap-3', data.items.length > 0 && 'mb-5')}>
                            <label className="flex items-center gap-2.5" data-tut="usd-rate">
                                <span className="text-xs font-bold text-muted-foreground">USD to IDR Rate</span>
                                <span className="grid grid-cols-[110px_40px] items-center">
                                    {/* pointer-events-none so clicking the icon still focuses the input. */}
                                    <span className="relative">
                                        <ArrowRightLeft aria-hidden="true" className="pointer-events-none absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-primary" />
                                        <input type="text" {...usdRateField} placeholder="15500" aria-label="USD to IDR Rate"
                                            className="h-9 w-full rounded-l-lg border border-input bg-card pl-7 pr-3 text-right text-xs font-bold text-foreground outline-none focus:border-primary focus:ring-1 focus:ring-primary" />
                                    </span>
                                    <strong className="grid h-9 place-items-center rounded-r-lg border border-l-0 border-input bg-accent/60 text-[11px] font-extrabold text-primary">IDR</strong>
                                </span>
                            </label>
                            <FieldError msg={errors.USDRate} />
                            {data.items.length === 0 && (
                                <span className="ml-auto flex items-center gap-1.5 text-xs text-muted-foreground">
                                    <Package aria-hidden="true" className="size-3.5" strokeWidth={1.8} />
                                    Belum ada produk — klik <strong className="font-semibold text-foreground">+ Add Product</strong>
                                </span>
                            )}
                        </div>

                        {errors.items && <p className="mt-2 text-[11px] font-semibold text-danger">{errors.items}</p>}

                        {/* Table when items exist */}
                        {data.items.length > 0 && (
                            <div className="overflow-x-auto rounded-lg border border-border/60">
                                <table className="w-full min-w-[920px] border-collapse [&_th]:border-b [&_th]:border-border [&_th]:p-3 [&_th]:text-left [&_td]:border-b [&_td]:border-border/40 [&_td]:p-3 [&_td]:text-left [&_th]:bg-card [&_th]:text-muted-foreground [&_th]:text-[10px] [&_th]:font-bold [&_th]:uppercase [&_th]:tracking-wide [&_tbody_tr:hover]:bg-muted/20">
                                    <thead>
                                        <tr>
                                            <th className="w-12">No.</th>
                                            <th>Item / Product</th>
                                            <th>Specification</th>
                                            <th>Unit</th>
                                            <th className="!text-right">Qty</th>
                                            <th className="!text-right">Unit Price (IDR)</th>
                                            <th className="!text-right">Discount</th>
                                            <th className="!text-right">Amount (IDR)</th>
                                            <th className="!text-right w-20">Action</th>
                                        </tr>
                                    </thead>
                                    <tbody>
                                        {data.items.map((r, idx) => {
                                            const q = parseFloat(r.OrderQuantity) || 0;
                                            const usd = parseFloat(r.UnitPriceUSD) || 0;
                                            const idr = parseFloat(r.UnitPriceIDR) || 0;
                                            const eff = idr > 0 ? idr : usd * rate;
                                            return (
                                                <tr key={r._id}>
                                                    <td className="text-xs font-semibold tabular-nums text-muted-foreground">{idx + 1}</td>
                                                    <td>
                                                        <div className="flex flex-col">
                                                            <span className="font-bold text-foreground text-xs">{r.ProductName || '—'}</span>
                                                            {r.PrincipalPrintName && <span className="text-[11px] text-muted-foreground">{r.PrincipalPrintName}</span>}
                                                        </div>
                                                    </td>
                                                    <td className="text-xs text-muted-foreground">{r.Remarks || '—'}</td>
                                                    <td className="text-xs text-muted-foreground">{r._satuanName || 'Unit'}</td>
                                                    <td className="!text-right text-xs font-semibold tabular-nums text-foreground">{q || '—'}</td>
                                                    <td className="!text-right text-xs font-semibold tabular-nums text-foreground">{idr ? fmtIdr(idr) : '—'}</td>
                                                    <td className="!text-right text-xs text-muted-foreground tabular-nums">0%</td>
                                                    <td className="!text-right text-xs font-bold tabular-nums text-primary">{fmtIdr(q * eff)}</td>
                                                    <td className="!text-right">
                                                        <div className="flex items-center justify-end gap-1">
                                                            <button type="button" className="inline-grid size-7 place-items-center rounded-lg text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground" aria-label="Edit product" onClick={() => openEditProduct(r)}>
                                                                <Pencil className="size-3.5" />
                                                            </button>
                                                            <button type="button" className="inline-grid size-7 place-items-center rounded-lg text-muted-foreground transition-colors hover:bg-secondary hover:text-destructive" aria-label="Remove product" onClick={() => removeProduct(r._id)}>
                                                                <Trash2 className="size-3.5" />
                                                            </button>
                                                        </div>
                                                    </td>
                                                </tr>
                                            );
                                        })}
                                    </tbody>
                                </table>
                            </div>
                        )}
                    </div>
                </section>

                {/* 5. Terms & Conditions */}
                <section className={cn(SECTION_CARD, 'h-full min-w-0', secFooter.collapsed && COLLAPSED_BODY)}>
                    <header className={SECTION_HEADER}>
                        <span className={STEP_BADGE}>5</span>
                        <h2 className={SECTION_TITLE}>Terms &amp; Conditions</h2>
                        <CollapseButton onClick={secFooter.toggle} collapsed={secFooter.collapsed} label="Toggle Terms & Conditions" />
                    </header>
                    <div className="flex-1 p-6">
                        <div className="grid grid-cols-3 gap-3.5 max-[1024px]:grid-cols-2 max-[760px]:grid-cols-1" data-tut="terms">
                            <div>
                                <SearchableSelect label="Price *" placeholder="Select price" options={options.priceDescs} value={data.QuotationPriceDescID} onChange={(v) => setData('QuotationPriceDescID', v)} />
                                <FieldError msg={errors.QuotationPriceDescID} />
                            </div>
                            <div>
                                <SearchableSelect label="Sales Terms *" placeholder="Select sales terms" options={options.salesTerms} value={data.QuotationSalesTermID} onChange={(v) => setData('QuotationSalesTermID', v)} />
                                <FieldError msg={errors.QuotationSalesTermID} />
                            </div>
                            <div>
                                <SearchableSelect label="Payment Terms *" placeholder="Select payment terms" options={options.paymentTerms} value={data.QuotationPaymentTermID} onChange={(v) => setData('QuotationPaymentTermID', v)} />
                                <FieldError msg={errors.QuotationPaymentTermID} />
                            </div>
                            <div>
                                <SearchableSelect label="Delivery / Lead Time *" placeholder="Select delivery time" options={options.deliveryTimes} value={data.QuotationDeliveryTimeID} onChange={(v) => setData('QuotationDeliveryTimeID', v)} />
                                <FieldError msg={errors.QuotationDeliveryTimeID} />
                            </div>
                            <div>
                                <SearchableSelect label="Validity *" placeholder="Select validity" options={options.validities} value={data.QuotationValidityID} onChange={(v) => setData('QuotationValidityID', v)} />
                                <FieldError msg={errors.QuotationValidityID} />
                            </div>
                            <div>
                                <SearchableSelect label="Stock Availability *" placeholder="Select stock availability" options={options.stockAvailabilities} value={data.QuotationStockAvailibilityID} onChange={(v) => setData('QuotationStockAvailibilityID', v)} />
                                <FieldError msg={errors.QuotationStockAvailibilityID} />
                            </div>
                        </div>
                    </div>
                </section>

                {/* Customer Outstanding (AR) */}
                {data.CompanyID ? <div data-tut="customer-ar"><CustomerArList data={customerAr.data} loading={customerAr.loading} /></div> : null}

                {/* Link Related Documents */}
                <RelatedDocsSection companyId={data.CompanyID} />

                <div className="flex items-center justify-between gap-4 pb-2">
                    <div className="flex flex-wrap gap-2.5">
                        <button className={PRIMARY_BTN} type="submit" disabled={processing} data-tut="submit">{processing ? 'Saving…' : 'Create Quotation'}</button>
                    </div>
                    <Link href={route('quotations.index')} className={SECONDARY_BTN}>Cancel</Link>
                </div>
                <CustomerArAlertModal
                    open={arAlert}
                    data={customerAr.data}
                    processing={processing}
                    onClose={() => setArAlert(false)}
                    onConfirm={() => { setArAlert(false); postQuotation(); }}
                />
            </form>

            <QuotationProductModal open={productModalOpen} onClose={closeProductModal} onAccept={upsertProduct} initial={editingLine} options={options} applications={applications} projects={projects} usdRate={rate} companyId={data.CompanyID} isOrder={data.IsOrder} />

            {tutorial.guide && <TutorialPanel open={tutorial.isOpen} onClose={tutorial.close} {...tutorial.guide} />}
        </section>
    );
}

QuotationsCreateV2.layout = [AppLayout];
