import { useState } from 'react';
import { Link, useForm, router, useHttp } from '@inertiajs/react';
import { ArrowLeft, RefreshCw, Ban, Check, RotateCcw, X, Save, Paperclip, Route, FileText, FlaskConical, Link2 as LinkIcon } from 'lucide-react';

// Page-header action chrome (locked sizing: h-9 / px-4). Shared so Change Status, Cancel and
// the Print/Download pair cannot drift into three different shapes again.
const HERO_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-primary transition-colors hover:border-primary hover:bg-accent/40 disabled:cursor-not-allowed disabled:opacity-60';
const HERO_BTN_DANGER = 'inline-flex h-9 items-center justify-center gap-1.5 rounded-lg border border-danger/40 bg-card px-4 text-xs font-bold text-danger transition-colors hover:bg-danger/10 disabled:cursor-not-allowed disabled:opacity-60';
import AppLayout from '@/Layouts/AppLayout';
import { Button } from '@/Components/ui/button';
import { StatusBadge } from '@/Components/Proto/UI/StatusBadge';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/Components/ui/dialog';
import { HistoryTimelinePopover } from '@/Components/MenuQuotations/QuotationDetailPage/HistoryTimelinePopover';
import { HistoryPopover } from '@/Components/MenuQuotations/QuotationDetailPage/HistoryPopover';
import { LwrPrintActionButtons } from '@/Components/MenuLWRs/LwrPrintActionButtons';
import { DecisionBar } from '@/Components/MenuSampleOrders/DecisionBar';
import { DecisionConfirmDialog } from '@/Components/MenuSampleOrders/DecisionConfirmDialog';
import CkEditorField from '@/Components/MenuLWRs/CkEditorField';
import { LinkPickerTable } from '@/Components/MenuLWRs/LinkPickerTable';
import CompanyTabs from '@/Components/MenuCompanies/CompanyTabs';
import { htmlToText } from '@/lib/utils';
import { NativeSelect } from '@/Components/ui/native-select';
import { useToast } from '@/Components/Toast';

// Maps real labworkrequeststatus.StatusName / labworkrequestdetailstatus name to a tone.
const STATUS_TONES = {
    'Request': 'warning',
    'Approval SM': 'primary',
    'Approval PM': 'primary',
    'Revise': 'warning',
    'Reject': 'danger',
    'Cancel': 'danger',
    'Print': 'success',
    'Feedback': 'primary',
    'Process': 'primary',
    'Lab Processing': 'primary',
    'Compiled': 'success',
    'Done': 'success',
};
const statusTone = (status) => STATUS_TONES[status] || 'neutral';

// The three "Link With …" buttons of listlwrdetails.php:504-506. `type` is the slug the
// server maps to labworkrequestlink.LabWorkRequestWith (2 / 3 / 4).
const LINK_PICKERS = [
    // Labels carry no "Link With" prefix: the section is already titled "Link With Other",
    // so repeating it three times spent width on the word the heading just said. NOT reduced
    // to bare icons — three different link TARGETS cannot be told apart by glyph, and these
    // are occasional actions nobody performs often enough to learn an icon for.
    { type: 'visit', label: 'Visit Report', Icon: Route },
    { type: 'quotation', label: 'Quotation', Icon: FileText },
    { type: 'sample', label: 'Sample', Icon: FlaskConical },
];

// Standalone card that sits directly on the page background (Link With Other).
const DOC_SECTION = 'relative rounded-xl border border-border bg-card px-4 pt-3.5 pb-3 shadow-sm';
// The SAME block nested inside the Doc Sections card carries no chrome of its own. It used to
// reuse DOC_SECTION, so a bordered white card sat inside a bordered white card on the same
// colour — 7 frames doing one job. The heading and the grid gap already separate the groups.
const DOC_GROUP = 'relative min-w-0';
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';

// "Created on …" subtitle from the LWR's own creation date.
function getCreatedOnString(q) {
    const dateStr = (q.lwrDate || '').trim();
    if (!dateStr || dateStr === '—') return '';
    try {
        const [datePart, timePart = ''] = dateStr.split(/\s+/);
        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'];
            formattedDate = `${parseInt(d, 10)} ${months[parseInt(m, 10) - 1] || m} ${y}`;
        }
        const t = timePart.split(':');
        const formattedTime = t.length >= 2 ? `${t[0]}:${t[1]}` : '';
        if (formattedDate && formattedTime) return `Created on ${formattedDate} · ${formattedTime}`;
        if (formattedDate) return `Created on ${formattedDate}`;
    } catch { /* ignore */ }
    return `Created on ${dateStr}`;
}
// Muted "NA" placeholder + normalizer (empty / "—" / "-" → NA).
const NA = () => <span className="font-normal text-muted-foreground/55">NA</span>;
const orNA = (v) => (v === null || v === undefined || v === '' || v === '—' || v === '-') ? <NA /> : v;

// DocList renders a field map as read-only <dt>/<dd> pairs. The legacy LWR detail
// (listlwrdetails.php / listlwrdetailsall.php) is fully read-only — no inline edits.
function DocList({ fields, cols = 1, fill = false }) {
    if (!fields || Object.keys(fields).length === 0)
        return <p className="px-0 py-1 text-[0.82rem] text-muted-foreground">Tidak ada data.</p>;
    return (
        <dl className={fill ? 'flex flex-1 flex-col gap-0' : cols === 2 ? 'grid grid-cols-2 gap-x-6 gap-y-1' : 'grid gap-0'}>
            {Object.entries(fields).map(([key, val]) => {
                const rowCls = fill
                    ? 'flex flex-1 flex-col gap-1 border-b border-dashed border-border py-2 last:border-b-0'
                    : cols === 2
                        ? 'flex flex-col gap-1 py-1'
                        : 'grid grid-cols-[minmax(0,110px)_1fr] items-baseline gap-2.5 border-b border-dashed border-border py-1.75 last:border-b-0';
                return (
                    <div key={key} className={rowCls}>
                        <dt className="m-0 flex items-center gap-1 text-[11px] font-medium text-muted-foreground">{key}</dt>
                        <dd className="m-0 wrap-break-word text-xs font-medium text-foreground">{orNA(val)}</dd>
                    </div>
                );
            })}
        </dl>
    );
}

// Compact multi-line cell: shows `preview` rows, the rest expand inline via "view more".
function MiniLines({ items, render, preview = 2 }) {
    const [open, setOpen] = useState(false);
    if (!items || items.length === 0) return <NA />;
    const shown = open ? items : items.slice(0, preview);
    return (
        <div className="flex flex-col gap-1">
            {shown.map((h, i) => render(h, i))}
            {items.length > preview && (
                <button type="button" onClick={() => setOpen((o) => !o)} className="self-start text-[10px] font-semibold text-primary hover:underline">
                    {open ? 'view less' : `view more (+${items.length - preview})`}
                </button>
            )}
        </div>
    );
}


// DEMO — placeholder until LabWorkRequestController detail wiring lands (frontend
// only). Shape mirrors what that controller would emit. Sourced from the real
// "Approval SM - Lab Work Request" screen sample (LWR #51).
const DEMO = {
    id: 51,
    lwrDate: '2025-05-08 14:05:17',
    status: 'Approval PM',
    statusLabel: 'Approval PM',
    sales: 'System',
    division: 'Coating',
    industry: 'Paint and Coatings',
    company: 'Abadi Coating Solusi',
    companyCategory: 'OEM/ODM',
    companyCpName: 'Bayuuu',
    companyCpAddress: 'Jl. Raya Cibinong Bekasi Km.20, Limusnunggal',
    projectInitiator: 'Willy',
    projectTitle: 'test123',
    customerExpectation: '1',
    projectInitiatorExpectation: '2',
    microbiology: '3',
    analyticalLab: '56',
    formulationLab: '4',
    otherLab: '78',
    spesificMethod: '9',
    specialInstructions: '11',
    processMethod: '10',
    methodRemark: '12',
    fileName: 'No file attachment',
    download: 'No file attachment',
    metadataUrl: null,
    lineItems: [
        {
            lwrNo: 51, productFrom: 'Colorindo', principalName: "Ange'l Yeast", productName: 'E-100', colour: 'aq', typeForm: 'Granule',
            volumeTest: '1.00', satuanVolume: 'tubs', unitPriceUSD: '1.00', satuanPrice: '/tubs', potentialVolume: '1.00', satuanPotential: 'tubs', potentialValues: '1.00', productRemark: '1',
            history: [
                { tanggal: '2025-05-08 14:05:17', status: 'Request', user: 'System' },
                { tanggal: '2025-05-08 14:07:56', status: 'Approval PM', user: 'System' },
            ],
        },
        {
            lwrNo: 51, productFrom: 'Colorindo', principalName: "Ange'l Yeast", productName: 'M-60', colour: '1', typeForm: 'Liquid',
            volumeTest: '1.00', satuanVolume: 'unit', unitPriceUSD: '11.00', satuanPrice: '/unit', potentialVolume: '1.00', satuanPotential: 'unit', potentialValues: '11.00', productRemark: '',
            history: [
                { tanggal: '2025-05-08 14:05:17', status: 'Request', user: 'System' },
                { tanggal: '2025-05-08 14:07:56', status: 'Approval PM', user: 'System' },
            ],
        },
    ],
    history: [
        { id: 115, status: 'Request', tanggal: '2025-05-08 14:05:17', user: 'System', comment: '' },
        { id: 117, status: 'Approval PM', tanggal: '2025-05-08 14:07:56', user: 'System', comment: 'a' },
    ],
};

export default function LwrDetail({ labWorkRequest = null, viewAll = false, capabilities = {}, statusOptions = [], printActions = null, backRoute = null, backLabel = null, approvalSm = null, pengerjaanLab = null, feedback = null }) {
    const { show: showToast } = useToast();
    const q = labWorkRequest && Object.keys(labWorkRequest).length ? labWorkRequest : DEMO;
    const lineItems = q.lineItems || [];
    const history = q.history || [];

    // Write actions (admin / View All) — Change Status + Cancel, gated by capabilities.
    // backRoute overrides the list target (e.g. Approval PM detail → back to its queue).
    const listRoute = backRoute || (viewAll ? 'lwrs.view-all' : 'lwrs.index');
    const [csOpen, setCsOpen] = useState(false);
    const [revOpen, setRevOpen] = useState(false);
    const [cancelOpen, setCancelOpen] = useState(false);

    // Link With Other. `linkForm` posts the checked ids; the picker list itself is fetched
    // from lwrs.related (the same endpoint the Create form's link pickers use, now gated on
    // THIS record when ?lwrId= is present).
    // Legacy has THREE modes, not two. listlwrdetails.php / listlwrdetailsall.php carry the
    // full block; listlwrdetailsonlyview.php and listlwrdetailsviewsm1.php (behind Approval
    // PM/SM and the View Details reports) render the SAME table but with no "Link With …"
    // buttons and their Unlink commented out; lwrfeedback.php / lwrpengerjaanlab.php /
    // listlwrdetailslab.php have nothing at all. `showLinks` draws the table, `canLink` adds
    // the write affordances on top.
    const showLinks = !!capabilities.showLinks;
    const canLink = !!capabilities.canLink;
    const linkForm = useForm({ type: '', ids: [] });
    const [picker, setPicker] = useState(null); // { type, label, rows, loading, checked:Set }
    const [unlinkTarget, setUnlinkTarget] = useState(null);
    const linkHttp = useHttp({});

    const openPicker = (type) => {
        const cfg = LINK_PICKERS.find((p) => p.type === type);
        setPicker({ ...cfg, rows: [], loading: true, checked: new Set() });
        linkHttp.get(route('lwrs.related', { type, companyId: q.companyId, lwrId: q.id }))
            .then((rows) => setPicker((p) => (p && p.type === type ? { ...p, rows: rows ?? [], loading: false } : p)))
            .catch(() => { setPicker(null); showToast('Gagal memuat daftar dokumen.', 'error'); });
    };

    const togglePick = (id) => setPicker((p) => {
        const checked = new Set(p.checked);
        if (checked.has(id)) checked.delete(id); else checked.add(id);
        return { ...p, checked };
    });

    const submitLinks = () => {
        const ids = [...picker.checked];
        if (ids.length === 0) return;
        linkForm.transform(() => ({ type: picker.type, ids }));
        linkForm.post(route('lwrs.links.store', q.id), {
            preserveScroll: true,
            onError: () => showToast('Please check the form and try again.', 'error'),
            onSuccess: () => setPicker(null),
        });
    };

    const submitUnlink = () => {
        const target = unlinkTarget;
        setUnlinkTarget(null);
        router.delete(route('lwrs.links.destroy', { id: q.id, linkId: target.id }), { preserveScroll: true });
    };

    // Confirmation dialog for Pengerjaan Lab — the only decision flow still on this generic
    // path (Approval SM and Feedback both confirm through their own DecisionConfirmDialog
    // state instead). window.confirm is banned for decision/destructive actions
    // (.claude/rules/notifications.md, .claude/rules/ui-conventions.md) and it cannot show
    // the consequence — which here is always "…and every product line moves too".
    // Shape: { title, body, label, onConfirm }.
    const [confirmCfg, setConfirmCfg] = useState(null);
    const askConfirm = (cfg) => setConfirmCfg(cfg);
    const runConfirm = () => {
        const act = confirmCfg?.onConfirm;
        setConfirmCfg(null);
        act?.();
    };
    const csForm = useForm({ StatusID: '', Comment: '' });

    const submitChangeStatus = (e) => {
        e.preventDefault();
        csForm.post(route('lwrs.change-status', q.id), {
            onError: () => showToast('Please check the form and try again.', 'error'), preserveScroll: true, onSuccess: () => { setCsOpen(false); csForm.reset(); } });
    };
    // Confirmed by the shadcn dialog below, not window.confirm — see the dialog's comment.
    const submitCancel = () => {
        setCancelOpen(false);
        router.post(route('lwrs.cancel', q.id), {}, { preserveScroll: true });
    };

    // Approval SM (legacy lwrapprovalsm.php): three decisions over the whole LWR with a
    // comment. Locked until the LWR reaches Approval PM (3) — the actionable state.
    const smForm = useForm({ comment: '' });
    const smLocked = approvalSm ? approvalSm.locked : false;
    const SM_CONFIRM = {
        approve: { title: 'Approve LWR?', body: 'Header dan seluruh product line berpindah ke Approval SM.' },
        revise: { title: 'Revise LWR?', body: 'Header dan seluruh product line kembali ke Revise, dan email revisi dikirim ke pembuat.' },
        reject: { title: 'Reject LWR?', body: 'Header dan seluruh product line ditandai Reject, dan email penolakan dikirim ke pembuat.' },
    };
    // Approval SM moves to DecisionConfirmDialog so the comment is typed and read back in
    // the same step as the decision. It gets its own state instead of joining `confirmCfg`:
    // Feedback later moved the same way, so `confirmCfg` now belongs to Pengerjaan Lab
    // alone, which carries no comment of its own.
    const [smConfirm, setSmConfirm] = useState(null);   // 'approve' | 'revise' | 'reject' | null
    const submitSm = (action) => {
        if (!approvalSm || smLocked || smForm.processing) return;
        setSmConfirm(action);
    };
    const runSmConfirmed = () => {
        const action = smConfirm;
        setSmConfirm(null);
        if (!approvalSm || smLocked || smForm.processing) return;
        smForm.post(route('lwrs.approval-sm.act', { id: q.id, action }), {
            onError: () => showToast('Please check the form and try again.', 'error'),
            preserveScroll: true,
        });
    };

    // Pengerjaan Lab (legacy lwrpengerjaanlab.php): the lab writes its TechnicalRemark
    // conclusion, then Submit (→ Lab Processing), Save Remark (draft, no status change),
    // or Revise. Locked unless the LWR is still at Approval SM (2). File upload deferred.
    // technicalFile makes this a multipart submit — Inertia switches to FormData on its own
    // as soon as the payload carries a File.
    // `remarkSM` has NO control on the page on purpose — see the note where the Lab
    // Conclusion block ends. It rides along seeded from the payload so the write keeps
    // matching legacy's UPDATE, exactly like legacy's hidden textarea did.
    const plForm = useForm({ technicalRemark: q.technicalRemark || '', remarkSM: q.remarkSM || '', technicalFile: null });
    const plLocked = pengerjaanLab ? pengerjaanLab.locked : false;
    // Client pre-check for the lab result: extension + size, so the lab is told immediately
    // instead of after a round trip. UX only — LwrPengerjaanLabRequest re-validates both
    // server-side, which is the gate that actually counts.
    const PL_FILE_EXT = ['pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'jpg', 'jpeg', 'png'];
    const PL_FILE_MAX = 2 * 1024 * 1024;
    const pickTechnicalFile = (input) => {
        const file = input.files?.[0];
        if (!file) { plForm.setData('technicalFile', null); return; }

        const ext = (file.name.split('.').pop() || '').toLowerCase();
        if (!PL_FILE_EXT.includes(ext)) {
            showToast('File hasil lab harus PDF, Word, Excel, PowerPoint, atau gambar.', 'error');
            input.value = '';
            return;
        }
        if (file.size > PL_FILE_MAX) {
            showToast('File hasil lab maksimal 2 MB.', 'error');
            input.value = '';
            return;
        }
        plForm.setData('technicalFile', file);
    };

    const PL_CONFIRM = {
        submit: { title: 'Submit hasil pengerjaan lab?', body: 'LWR beserta seluruh product line masuk Lab Processing. Lab Conclusion ikut tersimpan.', label: 'Submit' },
        comment: { title: 'Simpan remark?', body: 'Lab Conclusion disimpan tanpa mengubah status LWR.', label: 'Simpan' },
        revise: { title: 'Revise LWR?', body: 'LWR beserta seluruh product line kembali ke Revise, dan email revisi dikirim ke pembuat.', label: 'Revise' },
    };
    const submitPl = (action) => {
        if (!pengerjaanLab || plLocked || plForm.processing) return;
        askConfirm({
            ...PL_CONFIRM[action],
            onConfirm: () => plForm.post(route('lwrs.pengerjaan-lab.act', { id: q.id, action }), {
                onError: () => showToast('Please check the form and try again.', 'error'), preserveScroll: true }),
        });
    };

    // Feedback (legacy lwrfeedback.php): a required feedback status + an optional comment
    // move the LWR to Feedback (8). Locked unless still at Lab Processing (4).
    const fbForm = useForm({ feedbackStatusId: '', comment: '' });
    const fbLocked = feedback ? feedback.locked : false;
    const fbOptions = feedback ? (feedback.statusOptions || []) : [];
    const [fbConfirm, setFbConfirm] = useState(false);
    const submitFb = () => {
        setFbConfirm(false);
        if (!feedback || fbLocked || fbForm.processing || !fbForm.data.feedbackStatusId) return;
        fbForm.post(route('lwrs.feedback.act', { id: q.id }), {
            onError: () => showToast('Please check the form and try again.', 'error'),
            preserveScroll: true,
        });
    };

    // --- Section field maps ---
    const generalFields = { 'No': q.id, 'LWR Date': q.lwrDate, 'Status': q.statusLabel || q.status, 'Sales': q.sales };
    const classFields = { 'Division': q.division, 'Industry': q.industry, 'Company Category': q.companyCategory };
    const companyFields = { 'Company Name': q.company, 'Company CP Name': q.companyCpName, 'Company CP Address': q.companyCpAddress };
    const projectFields = {
        'Project Initiator': q.projectInitiator, 'Project Title': q.projectTitle,
        'Customer Expectation': q.customerExpectation, 'Project Initiator Expectation': q.projectInitiatorExpectation,
    };
    const labFields = { 'Microbiology': q.microbiology, 'Analytical Lab': q.analyticalLab, 'Formulation Lab': q.formulationLab, 'Other Lab': q.otherLab };
    const methodFields = {
        'Spesific Method': q.spesificMethod, 'Special Instructions': q.specialInstructions,
        'File Name': q.fileName,
        'Process Method': q.processMethod, 'Method Remark': q.methodRemark,
        // Plain <a>, not Inertia <Link> — lwrs.metadata streams a file, so it must be a
        // real browser navigation rather than an XHR visit. Falls back to the text the
        // payload already carried when the LWR has no attachment.
        'Download': q.metadataUrl
            ? (
                <a href={q.metadataUrl} className="inline-flex items-center gap-1.5 font-bold text-primary hover:underline">
                    <Paperclip aria-hidden="true" className="size-3.5" />
                    {q.fileName}
                </a>
            )
            : q.download,
    };
    // Conclusion (legacy listlwrdetails.php:791-825). Legacy prints "No file attachment" in
    // BOTH rows when there is no lab result, which reads as "Download: No file attachment" —
    // a label whose value is not an answer to it, and the same sentence twice in one block.
    // With an attachment the two rows are genuinely different things, so they both stay;
    // without one there is nothing to download, so only File Name is rendered.
    const conclusionFields = q.technicalFileName
        ? {
            'File Name': q.technicalFileName,
            'Download': (
                <a href={q.technicalFileUrl} className="inline-flex items-center gap-1.5 font-bold text-primary hover:underline">
                    <Paperclip aria-hidden="true" className="size-3.5" />
                    Download
                </a>
            ),
        }
        : { 'File Name': 'No file attachment' };

    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={route(listRoute)} className="text-muted-foreground no-underline hover:text-primary">{backLabel || 'Lab Work Request'}</Link>
                        <span aria-hidden="true">›</span>
                        <span className="text-foreground">View LWR</span>
                    </p>
                </div>
                <Link href={route(listRoute)} className={BACK_BTN}>
                    <ArrowLeft className="size-3.5" />
                    Back to List
                </Link>
            </header>

            {/* Hero */}
            <div className="mt-1 flex items-center justify-between gap-4">
                {/* One line, not three. The second badge used to read "Feedback Status: Follow up"
                    directly after a badge already saying "Feedback" — the word twice in a row, and
                    a label:value pair inside a pill. It now carries the VALUE only; sitting next to
                    the status badge is what says which status it qualifies, and `title` keeps the
                    full wording for anyone who needs it. "Created on …" joins the same row instead
                    of taking a line of its own. */}
                <div className="flex min-w-0 flex-wrap items-center gap-x-3 gap-y-1.5">
                    <h1 className="m-0 text-2xl font-extrabold tracking-tight text-foreground">Lab Work Request #{q.id}</h1>
                    {(q.statusLabel || q.status) && (
                        <StatusBadge tone={statusTone(q.status)}>{q.statusLabel || q.status}</StatusBadge>
                    )}
                    {q.feedbackStatus && (
                        <StatusBadge tone="neutral" title={`Feedback Status: ${q.feedbackStatus}`}>{q.feedbackStatus}</StatusBadge>
                    )}
                    {getCreatedOnString(q) && (
                        <span className="text-[12px] font-medium text-muted-foreground">{getCreatedOnString(q)}</span>
                    )}
                </div>
                {/* Header carries DOCUMENT actions only — Print/Download do not change the record.
                    Change Status and Cancel are decisions AGAINST the record, so they moved to the
                    floating pill at the bottom (locked rule: long detail pages put the action row
                    in a centred pinned pill, not in a header you have to scroll back up to).
                    h-9 px-4 outline, per the locked page-header action sizing. */}
                {(capabilities.canRevise || printActions) && (
                    <div className="flex shrink-0 items-center gap-2 whitespace-nowrap">
                        {printActions && <LwrPrintActionButtons printActions={printActions} lwrId={q.id} />}
                        {capabilities.canRevise && (
                            <button type="button" onClick={() => setRevOpen(true)}
                                className="inline-flex h-9 items-center justify-center gap-1.5 rounded-lg bg-linear-to-br from-emerald-500 to-green-600 px-4 text-xs font-bold text-white shadow-sm transition-[filter] hover:brightness-105">
                                <RotateCcw aria-hidden="true" className="size-3.5" />
                                Revise/Recreate
                            </button>
                        )}
                    </div>
                )}
            </div>

            {/* Stats Strip */}
            <article className="flex items-center gap-2 rounded-2xl border border-border bg-card shadow-sm p-[14px_18px]">
                <div className="grid min-w-0 flex-1 grid-cols-5 gap-0 max-[860px]:grid-cols-2">
                {[
                    { label: 'Division', value: q.division },
                    { label: 'Industry', value: q.industry },
                    { label: 'Sales', value: q.sales },
                    { label: 'Project Title', value: q.projectTitle },
                    { label: 'Products', value: lineItems.length },
                ].map((s, i) => (
                    <div key={s.label} className={`flex min-w-0 flex-col gap-0.5 p-[0_14px] ${i > 0 ? 'border-l border-border' : ''} ${i % 2 === 0 ? 'max-[860px]:border-l-0' : ''}`}>
                        <small className="block text-[10px] font-medium text-muted-foreground">{s.label}</small>
                        <strong className="block truncate text-base font-extrabold leading-[1.1] text-card-foreground">{orNA(s.value)}</strong>
                    </div>
                ))}
                </div>
                {history.length > 0 && (
                    <div className="flex shrink-0 items-center gap-2 self-center border-l border-border pl-3">
                        {history.length > 0 && (
                            <HistoryTimelinePopover entries={history.map((h) => ({ ID: h.id, Status: h.status, Tanggal: h.tanggal, User: h.user, Comment: h.comment }))} />
                        )}
                    </div>
                )}
            </article>

            {/* Doc Sections */}
            <article className="rounded-2xl border border-border bg-card shadow-sm p-[22px_24px]">
                <div className="grid grid-cols-3 gap-x-8 gap-y-7 max-[1180px]:grid-cols-2 max-[860px]:grid-cols-1">
                    <section className={DOC_GROUP}>
                        <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_GROUP}>
                        <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" /></svg>
                            </span>
                            Classification
                        </h3>
                        <DocList fields={classFields} />
                    </section>

                    <section className={DOC_GROUP}>
                        <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_GROUP}>
                        <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" /></svg>
                            </span>
                            Project
                        </h3>
                        <DocList fields={projectFields} />
                    </section>

                    <section className={`${DOC_GROUP} flex flex-col`}>
                        <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="M9 3h6v4l4 9a3 3 0 0 1-3 4H8a3 3 0 0 1-3-4l4-9z" /><line x1="9" y1="3" x2="15" y2="3" /></svg>
                            </span>
                            Lab Requirements
                        </h3>
                        <DocList fields={labFields} fill />
                    </section>

                    <section className={DOC_GROUP}>
                        <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>
                            Test Method
                        </h3>
                        <DocList fields={methodFields} />
                    </section>
                </div>

                {/* Conclusion — after Test Method (method → result), full width.
                    ALWAYS rendered. Legacy prints the whole block unconditionally in all five
                    read-only details (listlwrdetails.php:788-843, …all:873, …lab:406,
                    …onlyview:583, …viewsm1:575, lwrfeedback.php:315): both rows fall back to
                    the literal "No file attachment" and the editor comes up empty. It used to
                    hide behind `pengerjaanLab || q.technicalRemark` here, so an LWR that had
                    not been through the lab yet showed no Conclusion section at all — and an
                    LWR that had a result FILE but no remark hid the download with it.
                    Pengerjaan Lab is the one page that titles the same block "Technical Remark"
                    (lwrpengerjaanlab.php:683), because there the editor and the upload are live
                    instead of read-only. */}
                <div className="mt-5 border-t border-border/60 pt-5">
                    <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="M9 3h6" /><path d="M10 3v6l-5 9a1 1 0 0 0 .9 1.5h12.2a1 1 0 0 0 .9-1.5l-5-9V3" /></svg>
                            </span>
                            {pengerjaanLab ? 'Technical Remark' : 'Conclusion'}
                        </h3>
                        {/* Both remarks are CKEditor HTML in legacy AND here, so they are always
                            rendered through CkEditorField — its read-only mode goes through
                            CKEditor's own pipeline, so no dangerouslySetInnerHTML is needed. */}
                        {pengerjaanLab ? (
                            <div className="flex flex-col gap-2">
                                <CkEditorField className="cc-ck-rounded"
                                    value={plForm.data.technicalRemark}
                                    onChange={(html) => plForm.setData('technicalRemark', html)}
                                    disabled={plLocked || plForm.processing}
                                    placeholder="Tulis hasil / kesimpulan pengerjaan lab…" />
                                {/* Lab result attachment. Goes to the private disk, never a DB
                                    BLOB (ATURAN #21); the download link below reads either. */}
                                <div className="flex flex-wrap items-center gap-2 text-[12px]">
                                    <label className={`inline-flex items-center gap-1.5 rounded-lg border border-input bg-card px-3 py-1.5 font-medium text-foreground transition-colors ${plLocked || plForm.processing ? 'cursor-not-allowed opacity-60' : 'cursor-pointer hover:border-primary hover:text-primary'}`}>
                                        <Paperclip className="size-3.5" />
                                        {plForm.data.technicalFile ? 'Ganti file hasil lab' : 'Lampirkan file hasil lab'}
                                        <input type="file" className="hidden"
                                            accept=".pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.jpg,.jpeg,.png"
                                            disabled={plLocked || plForm.processing}
                                            onChange={(e) => pickTechnicalFile(e.target)} />
                                    </label>
                                    {plForm.data.technicalFile ? (
                                        <span className="inline-flex items-center gap-1.5 text-muted-foreground">
                                            {plForm.data.technicalFile.name}
                                            <button type="button" className="font-medium text-danger hover:underline"
                                                onClick={() => plForm.setData('technicalFile', null)}>hapus</button>
                                        </span>
                                    ) : q.technicalFileName ? (
                                        <a href={q.technicalFileUrl} className="font-medium text-primary hover:underline">{q.technicalFileName}</a>
                                    ) : (
                                        <span className="text-muted-foreground/70">Belum ada file hasil lab.</span>
                                    )}
                                </div>
                                {plForm.errors.technicalFile && <p className="m-0 text-xs font-medium text-danger">{plForm.errors.technicalFile}</p>}
                            </div>
                        ) : (
                            <div className="flex flex-col gap-3">
                                {/* File Name + Download, the two rows legacy puts above the
                                    read-only editor. Both read TechnicalUpload*, not the Work
                                    Metadata attachment shown under Test Method — different
                                    column family, different download route. */}
                                <DocList cols={2} fields={conclusionFields} />
                                {/* Rendered even with no content, like legacy: listlwrdetails.php:828
                                    echoes the readonly InsertCKEditor5 textarea unconditionally and
                                    ckEditor5.js upgrades it, so an LWR that has not been through the
                                    lab still shows an empty editor. A "Belum ada technical remark"
                                    placeholder div used to stand in here — the empty box IS the empty
                                    state, and swapping it for a message made the editor look missing. */}
                                <CkEditorField className="cc-ck-rounded" value={q.technicalRemark || ''} readOnly />
                            </div>
                        )}
                        {pengerjaanLab && plForm.errors.technicalRemark && <p className="m-0 mt-1.5 text-xs font-medium text-danger">{plForm.errors.technicalRemark}</p>}

                        {/* Remark SM is NOT rendered — legacy keeps it as
                            `<textarea id="InsertRemarkSM" style="display:none;">` in all four
                            files that carry it, with its CKEDITOR.replace line commented out.
                            The user never sees or types it; the hidden textarea just posts the
                            stored value straight back, which is why lwrpengerjaanlab.php still
                            lists RemarkSM in its UPDATE. plForm seeds `remarkSM` from the
                            payload and submits it unchanged, so that UPDATE stays identical
                            without inventing a field legacy does not show. */}

                        {pengerjaanLab && plLocked && (
                            <p className="m-0 mt-2 text-[11px] font-medium text-warning-text">
                                LWR belum Approval SM — pengerjaan lab belum bisa dilakukan.
                            </p>
                        )}
                </div>
            </article>

            {/* Details List Lab Work Request */}
            <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>Details List Lab Work Request</h2>
                            <small>Product lines breakdown</small>
                        </div>
                    </div>
                </header>

                {lineItems.length === 0 ? (
                    <p className="p-6 text-[0.85rem] text-muted-foreground">Tidak ada produk.</p>
                ) : (
                    <div className="overflow-x-auto rounded-xl border border-border/70">
                        <table className="w-full min-w-[1000px] 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 [&_td.number]:tabular-nums [&_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-[11px] [&_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>
                                    <th>LWR No</th>
                                    <th>Product From</th>
                                    <th>Principal</th>
                                    <th>Product Name</th>
                                    <th>Colour</th>
                                    <th>Type/Form</th>
                                    <th className="number">Volume Test</th>
                                    <th className="number">Unit Price (USD)</th>
                                    <th className="number">Potential Volume</th>
                                    <th className="number">Potential Values</th>
                                    <th>Product Remark</th>
                                    <th>Remark PM</th>
                                    <th className="!text-center">History</th>
                                </tr>
                            </thead>
                            <tbody>
                                {lineItems.map((item, idx) => (
                                    <tr key={idx}>
                                        <td className="tabular-nums text-muted-foreground">{orNA(item.lwrNo ?? q.id)}</td>
                                        <td>{orNA(item.productFrom)}</td>
                                        <td>{orNA(item.principalName)}</td>
                                        <td className="font-semibold text-foreground">{orNA(item.productName)}</td>
                                        <td>{orNA(item.colour)}</td>
                                        <td>{orNA(item.typeForm)}</td>
                                        <td className="number">{item.volumeTest ? `${item.volumeTest}${item.satuanVolume ? ` ${item.satuanVolume}` : ''}` : <NA />}</td>
                                        <td className="number">{item.unitPriceUSD ? `${item.unitPriceUSD}${item.satuanPrice ? ` ${item.satuanPrice}` : ''}` : <NA />}</td>
                                        <td className="number">{item.potentialVolume ? `${item.potentialVolume}${item.satuanPotential ? ` ${item.satuanPotential}` : ''}` : <NA />}</td>
                                        <td className="number font-semibold text-foreground">{orNA(item.potentialValues)}</td>
                                        <td>{orNA(item.productRemark)}</td>
                                        <td>
                                            {(item.remarkPM ?? []).length === 0 ? <NA /> : (
                                                <div className="flex max-w-60 flex-col gap-1 whitespace-normal">
                                                    {item.remarkPM.map((c, i) => (
                                                        <div key={i} className="leading-snug">{c}</div>
                                                    ))}
                                                </div>
                                            )}
                                        </td>
                                        <td className="text-center">
                                            <HistoryPopover count={(item.history ?? []).length} title="History" width={300}>
                                                {(item.history ?? []).map((h, i) => (
                                                    <div key={i} className="text-[11px] leading-snug text-muted-foreground">
                                                        <span className="font-semibold text-foreground">{h.status || '—'}</span>
                                                        {h.tanggal ? <> · <span className="tabular-nums">{h.tanggal}</span></> : null}
                                                        {h.user ? ` · ${h.user}` : ''}
                                                    </div>
                                                ))}
                                            </HistoryPopover>
                                        </td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    </div>
                )}
            </article>

            {/* Link With Other — legacy listlwrdetails.php:500-560. Three buttons that attach
                a Quotation / Sample Order / Visit Report, and a table of what is attached with
                an Unlink per row. Read-only for anyone outside the record scope (the server
                gates both writes anyway — this only hides what would 403). */}
            {showLinks && (
                <article className={`${DOC_SECTION} mt-5`}>
                    <div className="mb-3 flex flex-wrap items-center gap-2">
                        <h3 className={`${DOC_HEADING} !mb-0 mr-auto`}>
                            <span className={DOC_ICON} aria-hidden="true"><LinkIcon className="size-3.5" /></span>
                            Link With Other
                        </h3>
                        {canLink && LINK_PICKERS.map((p) => (
                            <Button key={p.type} type="button" variant="outline" size="sm"
                                onClick={() => openPicker(p.type)} disabled={linkForm.processing}
                                title={`Link With ${p.label}`}>
                                <p.Icon aria-hidden="true" className="size-3.5" />
                                {p.label}
                            </Button>
                        ))}
                    </div>

                    {(q.links || []).length === 0 ? (
                        <p className="m-0 text-[13px] text-muted-foreground">Belum ada dokumen yang ditautkan.</p>
                    ) : (
                        <div className="overflow-x-auto">
                            <table className="w-full border-separate border-spacing-0 text-[12px]">
                                <thead>
                                    <tr className="[&_th]:border-b [&_th]:border-border [&_th]:px-3.5 [&_th]:py-2 [&_th]:text-left [&_th]:text-[11px] [&_th]:font-bold [&_th]:uppercase [&_th]:tracking-wide [&_th]:text-muted-foreground">
                                        <th className="first:pl-[22px]">Linked With</th>
                                        <th>URL</th>
                                        {canLink && <th>Unlink</th>}
                                        <th className="last:pr-5">Details</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    {q.links.map((l) => (
                                        <tr key={l.id} className="[&_td]:border-b [&_td]:border-border/60 [&_td]:px-3.5 [&_td]:py-3 [&_td]:align-top">
                                            <td className="first:pl-[22px] font-semibold text-foreground">{l.typeName}</td>
                                            <td>
                                                {l.url
                                                    ? <a href={l.url} target="_blank" rel="noreferrer" className="font-medium text-primary hover:underline">{l.typeName} No. {l.linkedId}</a>
                                                    : <span className="text-muted-foreground">{l.typeName} No. {l.linkedId}</span>}
                                            </td>
                                            {canLink && (
                                                <td>
                                                    <button type="button" className="font-bold text-danger hover:underline disabled:opacity-60"
                                                        disabled={linkForm.processing}
                                                        onClick={() => setUnlinkTarget(l)}>
                                                        Unlink
                                                    </button>
                                                </td>
                                            )}
                                            <td className="last:pr-5 text-muted-foreground">
                                                {(l.details || []).length === 0
                                                    ? <NA />
                                                    : <div className="flex flex-col gap-0.5">{l.details.map((d, i) => <span key={i}>{d}</span>)}</div>}
                                            </td>
                                        </tr>
                                    ))}
                                </tbody>
                            </table>
                        </div>
                    )}
                </article>
            )}

            {/* Company Records — the LWR slice of the block. listlwrdetails.php embeds 11 of
                the 14 panes (no Project, no Visit Report All, no Complain), so the preset
                keeps this page at legacy parity instead of over-showing. Each pane fetches
                lazily on click via the companies.tabs endpoint. */}
            {q.companyId > 0 && (
                <CompanyTabs companyId={q.companyId} preset="lwr" />
            )}

            {/* Konfirmasi sebelum pindah ke form Revise. Belum ada yang ditulis di sini —
                pembatalan LWR lama terjadi saat form itu di-submit. Batal sebagai anak
                pertama: perlakuan dialog destruktif (.claude/rules/ui-conventions.md).
                Teks ringkas atas permintaan user (2026-08-04) — penjelasan panjangnya ada di
                banner form Revise. aria-describedby={undefined} WAJIB: tanpa DialogDescription,
                Radix menyetel aria-describedby ke id yang tidak pernah terisi lalu melempar
                console.warn. Ini cara Radix menyatakan "memang tidak ada deskripsi". */}
            <Dialog open={revOpen} onOpenChange={setRevOpen}>
                <DialogContent aria-describedby={undefined}>
                    <DialogHeader>
                        <DialogTitle>Revise this LWR?</DialogTitle>
                    </DialogHeader>
                    <DialogFooter>
                        <Button type="button" variant="outline" onClick={() => setRevOpen(false)}>Batal</Button>
                        <Button type="button" className="font-bold"
                            onClick={() => { setRevOpen(false); router.visit(route('lwrs.revise', q.id)); }}>
                            Lanjut ke form revisi
                        </Button>
                    </DialogFooter>
                </DialogContent>
            </Dialog>

            {/* Cancel LWR — dulu window.confirm (GH #284). Sekarang dialog shadcn seperti
                Revise di atasnya: `alert()`/`window.confirm()` dilarang untuk konfirmasi
                destruktif (.claude/rules/notifications.md). Batal sebagai anak PERTAMA dan
                tombol merah sesudahnya — perlakuan dialog destruktif di
                .claude/rules/ui-conventions.md, supaya aksi yang tidak bisa dibatalkan bukan
                tombol paling kiri yang paling gampang terklik. Konsekuensinya ditulis di
                DialogDescription karena membatalkan header ikut membatalkan SEMUA line. */}
            <Dialog open={cancelOpen} onOpenChange={setCancelOpen}>
                <DialogContent>
                    <DialogHeader>
                        <DialogTitle>Cancel LWR #{q.id}?</DialogTitle>
                        <DialogDescription>
                            Seluruh product line pada LWR ini ikut dibatalkan. Tindakan ini tidak bisa dibatalkan.
                        </DialogDescription>
                    </DialogHeader>
                    <DialogFooter>
                        <Button type="button" variant="outline" onClick={() => setCancelOpen(false)}>Batal</Button>
                        <Button type="button" variant="destructive" className="font-bold" onClick={submitCancel}>
                            Cancel LWR
                        </Button>
                    </DialogFooter>
                </DialogContent>
            </Dialog>

            {/* Link picker. Legacy navigated AWAY to vrlinkwith*.php and came back; a dialog
                keeps the detail page in place, which also means the table behind it refreshes
                on success without a second round trip. Candidates come from lwrs.related —
                the same company-scoped query the Create form's pickers use. */}
            <Dialog open={picker !== null} onOpenChange={(o) => { if (!o) setPicker(null); }}>
                <DialogContent className="sm:max-w-[92vw]">
                    <DialogHeader>
                        <DialogTitle>{picker?.label}</DialogTitle>
                        <DialogDescription>
                            Pilih dokumen milik company ini untuk ditautkan ke LWR #{q.id}.
                        </DialogDescription>
                    </DialogHeader>

                    {/* Legacy rendered the full listlinkwith*.php table here — 10-12 columns,
                        including the document's own item list. Same component the Create and
                        Revise forms use, because legacy included the same partial in all three. */}
                    <div className="max-h-[55vh] overflow-auto rounded-lg border border-border">
                        <LinkPickerTable type={picker?.type} rows={picker?.rows || []}
                            checked={picker?.checked ?? new Set()} onToggle={togglePick}
                            loading={!!picker?.loading} />
                    </div>
                    {linkForm.errors.ids && <p className="m-0 text-xs font-medium text-danger">{linkForm.errors.ids}</p>}

                    <DialogFooter>
                        <Button type="button" variant="outline" onClick={() => setPicker(null)}>Batal</Button>
                        <Button type="button" className="font-bold" onClick={submitLinks}
                            disabled={linkForm.processing || !picker || picker.checked.size === 0}>
                            Tautkan{picker && picker.checked.size > 0 ? ` (${picker.checked.size})` : ''}
                        </Button>
                    </DialogFooter>
                </DialogContent>
            </Dialog>

            {/* Unlink — destructive, so Batal stays the first child (ui-conventions.md). */}
            <Dialog open={unlinkTarget !== null} onOpenChange={(o) => { if (!o) setUnlinkTarget(null); }}>
                <DialogContent>
                    <DialogHeader>
                        <DialogTitle>Lepas tautan ke {unlinkTarget?.typeName} No. {unlinkTarget?.linkedId}?</DialogTitle>
                        <DialogDescription>
                            Tautan dilepas dari kedua sisi — LWR ini dan dokumen tersebut.
                        </DialogDescription>
                    </DialogHeader>
                    <DialogFooter>
                        <Button type="button" variant="outline" onClick={() => setUnlinkTarget(null)}>Batal</Button>
                        <Button type="button" variant="destructive" className="font-bold" onClick={submitUnlink}>Unlink</Button>
                    </DialogFooter>
                </DialogContent>
            </Dialog>

            <DecisionConfirmDialog
                action={smConfirm}
                onCancel={() => setSmConfirm(null)}
                onConfirm={runSmConfirmed}
                comment={smForm.data.comment}
                onCommentChange={(v) => smForm.setData('comment', v)}
                processing={smForm.processing}
                error={smForm.errors.comment}
                errors={smForm.errors}
                label={smConfirm ? SM_CONFIRM[smConfirm].title : undefined}
                commentMaxLength={500}
                summary={smConfirm && (
                    <>
                        <p className="m-0 text-[12px] leading-snug text-muted-foreground">
                            {SM_CONFIRM[smConfirm].body}
                        </p>
                        {/* Moved out of the pill (see the comment there): explains what the
                            comment field above does. Kept second — the consequence sentence
                            above it is the more important read. */}
                        <p className="m-0 mt-1.5 text-[12px] leading-snug text-muted-foreground">
                            Komentar tercatat di history LWR (dikirim ke pembuat saat revise / reject).
                        </p>
                    </>
                )}
            />

            <DecisionConfirmDialog
                action={fbConfirm ? 'feedback' : null}
                onCancel={() => setFbConfirm(false)}
                onConfirm={submitFb}
                comment={fbForm.data.comment}
                onCommentChange={(v) => fbForm.setData('comment', v)}
                processing={fbForm.processing}
                error={fbForm.errors.comment}
                commentRequired={false}
                commentMaxLength={500}
                confirmDisabled={!fbForm.data.feedbackStatusId}
                label="Submit feedback untuk LWR ini?"
                summary={(
                    <>
                        <p className="m-0 text-[12px] leading-snug text-muted-foreground">
                            LWR beserta seluruh product line berpindah ke status Feedback dan feedback status tersimpan.
                        </p>
                        <label className="mt-3 block">
                            <span className="mb-1.5 block text-[12px] font-semibold text-muted-foreground">
                                Feedback Status <span className="text-danger-text">*</span>
                            </span>
                            <NativeSelect
                                value={fbForm.data.feedbackStatusId}
                                disabled={fbForm.processing}
                                onChange={(e) => fbForm.setData('feedbackStatusId', e.target.value)}
                                className="h-9 w-full rounded-lg border border-input bg-card px-2 text-[13px] text-card-foreground outline-none transition-colors focus:border-primary disabled:cursor-not-allowed disabled:opacity-60"
                            >
                                <option value="">Select status…</option>
                                {fbOptions.map((s) => <option key={s.id} value={s.id}>{s.name}</option>)}
                            </NativeSelect>
                            {fbForm.errors.feedbackStatusId && (
                                <p className="m-0 mt-1 text-xs font-medium text-danger">{fbForm.errors.feedbackStatusId}</p>
                            )}
                        </label>
                    </>
                )}
            />

            {/* Confirmation dialog for Pengerjaan Lab — the last flow still on confirmCfg;
                Approval SM and Feedback both confirm through DecisionConfirmDialog instead.
                Same Batal-first ordering as the destructive dialogs above. None of Pengerjaan
                Lab's three actions (submit/comment/revise) is destructive, so the confirm
                button always renders as the ordinary primary style. */}
            <Dialog open={confirmCfg !== null} onOpenChange={(o) => { if (!o) setConfirmCfg(null); }}>
                <DialogContent>
                    <DialogHeader>
                        <DialogTitle>{confirmCfg?.title}</DialogTitle>
                        <DialogDescription>{confirmCfg?.body}</DialogDescription>
                    </DialogHeader>
                    <DialogFooter>
                        <Button type="button" variant="outline" onClick={() => setConfirmCfg(null)}>Batal</Button>
                        <Button type="button" className="font-bold" onClick={runConfirm}>
                            {confirmCfg?.label || 'Lanjut'}
                        </Button>
                    </DialogFooter>
                </DialogContent>
            </Dialog>

            <Dialog open={csOpen} onOpenChange={setCsOpen}>
                <DialogContent>
                    <DialogHeader>
                        <DialogTitle>Change Status — LWR #{q.id}</DialogTitle>
                    </DialogHeader>
                    <form onSubmit={submitChangeStatus} className="flex flex-col gap-3">
                        <div>
                            <label htmlFor="csStatus" className="mb-1 block text-[11px] font-bold uppercase tracking-wide text-muted-foreground">New Status</label>
                            <NativeSelect id="csStatus" value={csForm.data.StatusID} onChange={(e) => csForm.setData('StatusID', e.target.value)}
                                className="w-full rounded-md border border-input bg-card px-2.5 py-2 text-xs text-foreground outline-none focus:border-primary focus:ring-1 focus:ring-primary">
                                <option value="">Select status…</option>
                                {statusOptions.map((s) => <option key={s.id} value={s.id}>{s.name}</option>)}
                            </NativeSelect>
                            {csForm.errors.StatusID && <p className="mt-1 text-[11px] text-danger">{csForm.errors.StatusID}</p>}
                        </div>
                        <div>
                            <label htmlFor="csComment" className="mb-1 block text-[11px] font-bold uppercase tracking-wide text-muted-foreground">Comment</label>
                            <textarea id="csComment" value={csForm.data.Comment} onChange={(e) => csForm.setData('Comment', e.target.value)}
                                className="min-h-[56px] w-full resize-y rounded-md border border-input bg-card px-2.5 py-2 text-xs text-foreground outline-none focus:border-primary focus:ring-1 focus:ring-primary" />
                        </div>
                        <DialogFooter>
                            <Button type="submit" disabled={!csForm.data.StatusID || csForm.processing} className="font-bold">Save</Button>
                            <Button type="button" variant="outline" onClick={() => setCsOpen(false)}>Cancel</Button>
                        </DialogFooter>
                    </form>
                </DialogContent>
            </Dialog>
            {/* Record decisions, pinned. This page is ~2200px: with these in the header only,
                deciding after reading the product lines and Company Records meant scrolling back
                to the top. Change Status leads (primary, gradient) and Cancel sits after it —
                heavier action to the right, per the pill's locked button order.
                Safe to key on capabilities alone: the Approval SM, Pengerjaan Lab, and Feedback
                screens all ship canChangeStatus/canCancel = false, so this pill and their own
                decision rows can never be on screen together. */}
            {(capabilities.canChangeStatus || capabilities.canCancel) && (
                <DecisionBar>
                    <span className="text-[12px] font-semibold text-muted-foreground">LWR #{q.id}</span>
                    <div className="flex items-center gap-2.5">
                        {capabilities.canChangeStatus && (
                            <button type="button" onClick={() => setCsOpen(true)}
                                className="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">
                                <RefreshCw aria-hidden="true" className="size-3.5" />
                                Change Status
                            </button>
                        )}
                        {capabilities.canCancel && (
                            <button type="button" onClick={() => setCancelOpen(true)} className={HERO_BTN_DANGER}>
                                <Ban aria-hidden="true" className="size-3.5" />
                                Cancel
                            </button>
                        )}
                    </div>
                </DecisionBar>
            )}

            {/* Approval SM / Pengerjaan Lab / Feedback pills live here, at the very end of the
                page, not next to the flow each belongs to. DecisionBar's in-flow spacer reserves
                its clearance wherever the component is rendered — with these mid-page (right
                after Doc Sections) the spacer opened a gap in the middle of the document while
                the true last card (Company Records / Link With Other) got none, so the fixed
                pill floated ~30px over it. Keeping them last, like the Quotation Approval SM /
                Feedback screens, is what makes the spacer land after the last card. */}
            {/* Approval SM actions — floating centre pill, same shape as Pengerjaan Lab below.
                These used to sit in the page header in the reverse order (Reject · Revise ·
                Approve): on a screen this long, deciding meant scrolling back to the top every
                time, and the destructive action sat in the easiest position to hit by accident.
                Order is the locked one: Approve leftmost, heavier to the right. */}
            {approvalSm && (
                <DecisionBar>
                    <span className="hidden shrink-0 whitespace-nowrap text-[12px] font-medium text-muted-foreground sm:inline">Approval SM · LWR #{q.id}</span>
                    {/* Nothing in a DecisionBar pill may be shrinkable text. Unlocked copy
                        ("Komentar tercatat…") moved into the dialog's summary below — DecisionBar's
                        sm:flex-nowrap crushes a long paragraph to min-content width here instead
                        of shrinking it. The id span above is pinned with shrink-0 + whitespace-
                        nowrap too: once the paragraph was gone it became the next shrinkable
                        text and wrapped to 4 lines on its own (measured 98px vs. the 62px
                        baseline). The locked note below stays, shrink-0 and short: the dialog
                        never opens while locked, so this is the only explanation left. */}
                    {smLocked && (
                        <>
                            <span className="hidden h-6 w-px bg-border sm:block" />
                            <p className="m-0 hidden shrink-0 text-[12px] font-medium text-muted-foreground sm:block">Belum di-approve PM</p>
                        </>
                    )}
                    <div className="flex w-full items-center gap-2 sm:w-auto sm:gap-2.5">
                        <Button type="button" disabled={smLocked || smForm.processing}
                            onClick={() => submitSm('approve')}
                            className="h-9 flex-1 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 sm:flex-none">
                            <Check className="size-3.5" /> Approve SM
                        </Button>
                        <Button type="button" variant="outline" disabled={smLocked || smForm.processing}
                            onClick={() => submitSm('revise')}
                            className="h-9 flex-1 gap-1.5 rounded-lg border border-primary bg-card px-4 text-xs font-bold text-primary hover:bg-primary/10 sm:flex-none">
                            <RotateCcw className="size-3.5" /> Revise
                        </Button>
                        <Button type="button" variant="outline" disabled={smLocked || smForm.processing}
                            onClick={() => submitSm('reject')}
                            className="h-9 flex-1 gap-1.5 rounded-lg border border-danger/40 bg-card px-4 text-xs font-bold text-danger hover:bg-danger/10 sm:flex-none">
                            <X className="size-3.5" /> Reject
                        </Button>
                    </div>
                </DecisionBar>
            )}

            {/* Pengerjaan Lab actions — floating centre pill (design-system decision bar). The
                Technical Remark lives in its card above; only the buttons dock here. Positive/terminal
                (Submit) leftmost, neutral (Save Remark), send-back (Revise) rightmost. */}
            {pengerjaanLab && (
                <DecisionBar>
                    {/* This pill never had a helper paragraph, but the id span still needs
                        shrink-0 — nothing in a DecisionBar row may be shrinkable text, and the
                        other three pills in this file only found that out once their long
                        paragraph was gone and the id span became the next shrinkable thing to
                        crush. Pinned here pre-emptively instead of waiting for the same defect. */}
                    <span className="hidden shrink-0 text-[12px] font-medium text-muted-foreground sm:inline">Pengerjaan Lab · LWR #{q.id}</span>
                    <span className="hidden h-6 w-px bg-border sm:block" />
                    <div className="flex w-full items-center gap-2 sm:w-auto sm:gap-2.5">
                        <Button type="button" disabled={plLocked || plForm.processing}
                            onClick={() => submitPl('submit')} title="Submit hasil — LWR pindah ke Lab Processing"
                            className="h-9 flex-1 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 sm:flex-none">
                            <Check className="size-3.5" /> Submit
                        </Button>
                        <Button type="button" variant="outline" disabled={plLocked || plForm.processing}
                            onClick={() => submitPl('comment')} title="Simpan technical remark tanpa mengubah status"
                            className="h-9 flex-1 gap-1.5 rounded-lg border border-border bg-card px-4 text-xs font-bold text-foreground hover:border-primary hover:text-primary sm:flex-none">
                            <Save className="size-3.5" /> Save Remark
                        </Button>
                        <Button type="button" variant="outline" disabled={plLocked || plForm.processing}
                            onClick={() => submitPl('revise')} title="Kembalikan LWR ke pembuat"
                            className="h-9 flex-1 gap-1.5 rounded-lg border border-primary bg-card px-4 text-xs font-bold text-primary hover:bg-primary/10 sm:flex-none">
                            <RotateCcw className="size-3.5" /> Revise
                        </Button>
                    </div>
                </DecisionBar>
            )}

            {/* Feedback actions — same pill shape as Approval SM and Pengerjaan Lab. The form
                (status + comment) used to live in a popover hanging off a stat-strip icon. */}
            {feedback && (
                <DecisionBar>
                    <span className="hidden shrink-0 whitespace-nowrap text-[12px] font-medium text-muted-foreground sm:inline">Feedback · LWR #{q.id}</span>
                    {/* Nothing in a DecisionBar pill may be shrinkable text — that includes the
                        id span above, now pinned with shrink-0 + whitespace-nowrap so it can't
                        become the next thing to wrap. Unlocked copy dropped entirely —
                        DecisionConfirmDialog's summary already opens with the same sentence in
                        more detail, and it would get crushed here anyway: DecisionBar's
                        sm:flex-nowrap squeezes a long child to min-content width instead of
                        shrinking it. The locked note stays, shrink-0 and short: the dialog never
                        opens while locked. */}
                    {fbLocked && (
                        <>
                            <span className="hidden h-6 w-px bg-border sm:block" />
                            <p className="m-0 hidden shrink-0 text-[12px] font-medium text-muted-foreground sm:block">Bukan status Lab Processing</p>
                        </>
                    )}
                    <Button type="button" disabled={fbLocked || fbForm.processing}
                        onClick={() => setFbConfirm(true)}
                        className="h-9 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">
                        <Check className="size-3.5" /> Submit Feedback
                    </Button>
                </DecisionBar>
            )}
        </section>
    );
}

LwrDetail.layout = [AppLayout]
