var _useState_ARP    = React.useState;
var _useEffect_ARP   = React.useEffect;
var _useParams_ARP   = ReactRouterDOM.useParams;
var _useNavigate_ARP = ReactRouterDOM.useNavigate;

/**
 * AssessmentReportPage — the assessment report document (route
 * `/assessment/:id/report`).
 *
 * Spec: architecture/ASSESSMENT-REPORT-SPEC.md §2 (route and toolbar), §4 (the
 * document, in this order), §5 (branding). Derivation lives in
 * `asReportModel.jsx` and is never repeated here: this file renders a model, it
 * does not compute one. ADR-009 D4 — derived at render, never stored.
 *
 * **Screen and print share ONE DOM** (R3, §4). There is no print-only copy of
 * the document and no html2canvas/jsPDF: the PDF is the browser's own print
 * path, which gives vector text, exact colours, an offline path and — on iOS —
 * a share sheet with "Save to Files". The differences between the two media are
 * a few CSS rules in index.html's print block:
 *   • app chrome (`.report-chrome`, the bottom nav) is hidden;
 *   • every collapsed section and room card opens (`.rr-collapsed` becomes
 *     visible again) — print never loses content;
 *   • a fixed footer band appears, and the tiled page background does not.
 *
 * **Layout (redesign 29 Sep 2026)** follows the main Reports page
 * (ReportsPage / ReportSummary / HealthRiskInfo) and DESIGN.md:
 *   • nothing white sits on the green page — every panel is cream `bio-bg`,
 *     white only for insets inside a cream panel (DESIGN.md §2 surface rule);
 *   • the header, risk and Summary are always visible; the heavier sections are
 *     report-section disclosures (`ArpSection`, DESIGN.md §4): a 44 px+ header
 *     button with title, one-line summary and a count, `aria-expanded` /
 *     `aria-controls`. Open by default: the action plan. The rooms, lab
 *     results, scoring key and amendment appendix start closed;
 *   • the actions are a cream panel above the document: Share / Print primary,
 *     Branding and Export CSV secondary, Export CSV locked-not-broken below
 *     Professional (DESIGN.md §5). The status chips live in the report header.
 *   Deviation from spec §2: the toolbar is no longer sticky. A sticky panel with
 *   three actions and a plan note covered a quarter of a 390×844 screen, and the
 *   progressively disclosed document is short enough to scroll back to it.
 *
 * The PRINT FOOTER is deliberately belt-and-braces (§4.9 wants it on every
 * printed page): `.report-print-footer` is `position: fixed; bottom: 0`, which
 * Chrome repeats on every printed page, and `.report-doc-footer` is a static
 * block at the end of the document, which is what Safari — where a fixed
 * element prints on the first page only — falls back to. Between them, no
 * printed report can end without the footer, and neither browser shows it
 * twice on the same page.
 *
 * Entitlement (§2): the page renders for anyone who can open the assessment.
 * Only BRANDING is tier-checked, and it is checked inside `asResolveBranding`
 * (R4), not here — this file passes the entitlements through and renders
 * whatever the resolver hands back.
 *
 * Slice 5 adds the **Export CSV** action: 47 source columns plus twelve
 * appended Mould Detect ones, built by the pure `asCsv.jsx` from the SAME model
 * this page renders, so the spreadsheet and the printed document can never
 * disagree. It is gated on `useEntitlements().csvExport` (Professional+) and
 * locked — with the reason, never hidden — below that.
 *
 * File-local helpers use the `arp` prefix (no module system — CLAUDE.md).
 * ES5 for Babel 6.
 */

/* Type scale, from the main Reports page: uppercase micro-labels for labels
   only; body text at text-sm; nothing a reader must read below 12 px. */
var ARP_LABEL_CLS = 'text-[10px] font-black text-sage uppercase tracking-[0.15em]';
var ARP_CHIP_CLS = 'inline-flex items-center gap-1.5 text-xs font-extrabold px-2.5 py-1 rounded-full whitespace-nowrap';
/* A white inset inside a cream panel — the only place white is allowed. */
var ARP_INSET_CLS = 'bg-surface rounded-2xl shadow-soft';

/* The sections that start OPEN on screen (the brief: summary and action plan;
   the summary is not collapsible at all). Everything else starts closed. */
var ARP_DEFAULT_OPEN = { 'report-action-plan': true };

/* The 0–3 key printed in §4.8. The words are AR_SEV_WORDS; the descriptions are
   the DMAT definitions from AppConstants.DMAT_SCORE_LABELS, read at call time. */
function arpScoreKey() {
    var list = (AppConstants && AppConstants.DMAT_SCORE_LABELS) || [];
    var out = [];
    for (var i = 0; i < list.length; i++) {
        out.push({
            value: list[i].value,
            word: AR_SEV_WORDS[list[i].value],
            desc: list[i].desc,
            hex: AR_BAND_HEX[['good', 'fair', 'poor', 'very_poor'][list[i].value]]
        });
    }
    return out;
}

/** arpToday — "21 Sep 2026" for the footer's "Generated" stamp. The model
 *  never carries this: it is a fact about the moment of printing, not about
 *  the inspection, and putting it in the model would make the shared fixture
 *  fail every day at midnight. */
function arpToday() {
    var d = new Date();
    var iso = d.getFullYear() + '-' + ('0' + (d.getMonth() + 1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2);
    return arDate(iso);
}

/** arpPlural — "1 room" / "3 rooms". */
function arpPlural(n, word, plural) {
    return n + ' ' + (n === 1 ? word : (plural || word + 's'));
}

/** arpSampleType — the inspector's word for a sample type. The labels live in
 *  AssessmentRoomPage (loaded before this file); read at call time, with a
 *  neutral fallback so a missing global can never break the report. */
function arpSampleType(key) {
    var labels = (typeof ASR_SAMPLE_TYPE_LABELS !== 'undefined' && ASR_SAMPLE_TYPE_LABELS) || {};
    return labels[key] || 'Sample';
}

/** arpDot — a band colour as a small dot beside its word. The band colours are
 *  light-to-mid and neither white nor forest text is legible across all four,
 *  so the colour never carries meaning on its own (CLAUDE.md). */
var ArpDot = function (props) {
    return (
        <span
            className="inline-block w-2.5 h-2.5 rounded-full shrink-0"
            style={{ backgroundColor: props.hex || AR_BAND_HEX.good }}
            aria-hidden="true"
        ></span>
    );
};

/** Inline SVG glyphs: the Material Symbols subset has no print, download or
 *  clock glyph, and an unlisted ligature renders as its literal word. They
 *  also keep a chip's textContent to its words (the suite reads them). */
var ArpSvg = function (props) {
    return (
        <svg viewBox="0 0 24 24" className={props.className || 'w-4 h-4'} fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
            <path d={props.d} strokeLinecap="round" strokeLinejoin="round" />
        </svg>
    );
};
var ARP_PATH_PRINT = 'M6 9V3h12v6M6 18H4v-6h16v6h-2M8 14h8v7H8z';
var ARP_PATH_DOWNLOAD = 'M12 3v11m0 0l-4-4m4 4l4-4M4 17v2a2 2 0 002 2h12a2 2 0 002-2v-2';
var ARP_PATH_CLOCK = 'M12 21a9 9 0 100-18 9 9 0 000 18zM12 7v5l3 2';
var ARP_PATH_CHECK = 'M5 12.5l4.5 4.5L19 7.5';

/** ArpGauge — the risk ring (§4.1). Inline SVG, not an icon: the Material
 *  Symbols subset has no gauge glyph. */
var ArpGauge = function (props) {
    var pct = props.pct;
    var hex = props.hex;
    var circumference = 2 * Math.PI * 15.9;
    return (
        <div className="relative w-20 h-20 shrink-0">
            <svg viewBox="0 0 36 36" className="w-full h-full" style={{ transform: 'rotate(-90deg)' }} aria-hidden="true">
                <circle cx="18" cy="18" r="15.9" fill="none" stroke="#C0D6BA" strokeWidth="3.4" />
                <circle
                    cx="18" cy="18" r="15.9" fill="none" stroke={hex} strokeWidth="3.4" strokeLinecap="round"
                    strokeDasharray={(circumference * pct / 100) + ' ' + circumference}
                />
            </svg>
            <span className="absolute inset-0 flex items-center justify-center text-lg font-extrabold text-forest">
                {pct}%
            </span>
        </div>
    );
};

/**
 * ArpSection — one §4 section as a cream panel (DESIGN.md §4 "Report section
 * disclosure"). Collapsible sections put the whole header in ONE button
 * (≥ 56 px tall, `aria-expanded` + `aria-controls`) carrying the title, a
 * one-line summary and a count, so a closed section still says what is in it.
 *
 * The body is ALWAYS in the DOM: closed on screen it carries `rr-collapsed`,
 * which index.html's print block turns back on, so print and PDF show every
 * section expanded without a second copy of the markup.
 *
 * `.report-seclabel` (break-after: avoid) sits on the h2, the hook the print
 * block uses to keep a heading off the foot of a page.
 */
var ArpSection = function (props) {
    var open = props.collapsible ? !!props.open : true;
    var bodyId = 'arp-body-' + props.testid;
    var head = (
        <React.Fragment>
            <span className="w-10 h-10 rounded-xl bg-background-light flex items-center justify-center shrink-0 text-forest">
                <span className="material-symbols-outlined text-xl" aria-hidden="true">{props.icon}</span>
            </span>
            <span className="flex-1 min-w-0">
                <span className="block text-base font-extrabold text-forest leading-tight">{props.title}</span>
                {props.summary && (
                    <span data-testid={props.testid + '-summary'} className="block text-sm font-semibold text-forest/70 mt-0.5 leading-snug">
                        {props.summary}
                    </span>
                )}
            </span>
            {props.count !== undefined && props.count !== null && (
                /* The summary line already says the number in words, so the
                   badge is visual only (no double read-out). */
                <span aria-hidden="true" className="min-w-[2rem] h-8 px-2 rounded-full bg-surface text-forest text-sm font-extrabold flex items-center justify-center shrink-0 shadow-soft">
                    {props.count}
                </span>
            )}
            {props.collapsible && (
                <span
                    aria-hidden="true"
                    className={'material-symbols-outlined text-forest text-2xl shrink-0 report-chrome transition-transform motion-reduce:transition-none ' + (open ? 'rotate-180' : '')}
                >
                    expand_more
                </span>
            )}
        </React.Fragment>
    );
    return (
        <section data-testid={props.testid} className="px-5 report-section">
            <div className={'report-panel bio-bg ' + (props.bg || 'bio-bg-20') + ' rounded-xl shadow-soft border border-stone-100/50 overflow-hidden'}>
                <h2 className="report-seclabel">
                    {props.collapsible ? (
                        <button
                            type="button"
                            data-testid={props.testid + '-toggle'}
                            aria-expanded={open ? 'true' : 'false'}
                            aria-controls={bodyId}
                            onClick={props.onToggle}
                            className="w-full min-h-[56px] flex items-center gap-3 p-4 text-left btn-nature touch-manipulation"
                        >
                            {head}
                        </button>
                    ) : (
                        <span className="flex items-center gap-3 p-4">{head}</span>
                    )}
                </h2>
                {props.always}
                <div id={bodyId} className={'px-4 pb-4' + (open ? '' : ' rr-collapsed')}>
                    {props.children}
                </div>
            </div>
        </section>
    );
};

var AssessmentReportPage = function () {
    var params = _useParams_ARP();
    var navigate = _useNavigate_ARP();
    var store = useAssessments();
    var scanStore = useScanStore();
    var brandingStore = useReportBranding();
    var gate = useReportBrandingEnabled();
    // Read before any early return: hooks run unconditionally, and the two
    // "loading" / "gone" branches below return before the document exists.
    var entitlements = useEntitlements();

    var sheetState = _useState_ARP(false);
    var sheetOpen = sheetState[0];
    var setSheetOpen = sheetState[1];

    // Accordion state, keyed by room id. `null` means "nobody has touched it
    // yet", which the renderer reads as "the first room is open" (§4.4).
    var openState = _useState_ARP(null);
    var openRooms = openState[0];
    var setOpenRooms = openState[1];

    // Section disclosure state, keyed by section testid (screen only — print
    // opens everything through CSS).
    var sectionState = _useState_ARP(ARP_DEFAULT_OPEN);
    var openSections = sectionState[0];
    var setOpenSections = sectionState[1];

    /* The print stylesheet is scoped to this body class, so nothing about the
       rest of the app's printing behaviour changes when this page is not
       mounted. Removed on unmount, including on a navigation away mid-print. */
    _useEffect_ARP(function () {
        if (window.MetricsService) MetricsService.track('report_viewed'); // product metrics §2
        document.body.classList.add('md-report-print');
        return function () { document.body.classList.remove('md-report-print'); };
    }, []);

    var assessment = store.getById(params.id);

    /* Uploaded component photos that were never scanned (2026-10-05) have no
       scan thumbnail; their images live under their own IndexedDB keys. Read
       before the early returns (hooks). */
    var arpPhotoIds = [];
    var arpRooms = (assessment && assessment.rooms) || [];
    for (var pr = 0; pr < arpRooms.length; pr++) {
        var prComps = arpRooms[pr].components || [];
        for (var pc = 0; pc < prComps.length; pc++) {
            var prAtt = prComps[pc].attachments || [];
            for (var pa = 0; pa < prAtt.length; pa++) arpPhotoIds.push(prAtt[pa].id);
        }
    }
    var photoImages = useComponentPhotoImages(arpPhotoIds);

    if (!store.hydrated) {
        return (
            <Layout>
                <main className="flex-1 w-full mx-auto pb-36 overflow-y-auto overflow-x-hidden">
                    <div className="report-chrome"><PageHeader title="Report" showMenu={true} /></div>
                    <div className="px-5 pt-4">
                        <div className="bio-bg bio-bg-10 rounded-xl p-5 shadow-soft border border-stone-100/50">
                            <p className="text-sm font-semibold text-forest">Building this report…</p>
                        </div>
                    </div>
                </main>
            </Layout>
        );
    }

    if (!assessment) {
        return (
            <Layout>
                <main className="flex-1 w-full mx-auto pb-36 overflow-y-auto overflow-x-hidden">
                    <div className="report-chrome"><PageHeader title="Report" showMenu={true} /></div>
                    <div className="px-5 pt-4">
                        <div className="bio-bg bio-bg-10 rounded-xl p-5 shadow-soft border border-stone-100/50 text-center">
                            <span className="material-symbols-outlined text-forest text-3xl mb-2">search_off</span>
                            <p className="text-sm font-extrabold text-forest">This visit is no longer here</p>
                            <p className="text-sm font-medium text-forest mt-1">There is nothing to report on.</p>
                            <button
                                onClick={function () { navigate('/location-details'); }}
                                className="mt-4 w-full min-h-[48px] bg-forest text-white font-extrabold py-3 rounded-xl shadow-clay btn-nature hover:brightness-110 transition-all"
                            >
                                Back to the property
                            </button>
                        </div>
                    </div>
                </main>
            </Layout>
        );
    }

    var property = assessment.propertyId ? scanStore.getPropertyById(assessment.propertyId) : null;

    /* Scans by id, for the photo galleries (§3.8). Built from the whole scan
       list rather than looked up per photo: the list is already in memory and a
       report renders every component's strip at once. */
    var scansById = {};
    var allScans = scanStore.scans || [];
    for (var i = 0; i < allScans.length; i++) scansById[allScans[i].id] = allScans[i];

    /* The entitlements the RESOLVER decides on. `brandingPreview` is the named
       dev override (§5, REPORT_BRANDING_PREVIEW) — passed in, never applied
       here, so the white-label decision still happens at the seam (R4). */
    var resolverEnt = {
        whiteLabelPdf: !!entitlements.whiteLabelPdf,
        brandingPreview: gate.brandingPreview
    };

    var model = asReportModel(assessment, property, scansById, resolverEnt, brandingStore.branding);
    if (!model) return null;

    var brand = model.brand;
    var chip = asStatusChip(model.recordStatus);
    var generated = arpToday();

    /* Slice 5 — the 47-column CSV (brief §6). Professional+, read from the one
       entitlement seam; the dev tier preview arrives through it rather than
       around it. */
    var csvAllowed = !!entitlements.csvExport;

    /**
     * exportCsv — build the text, hand the browser a Blob and click an anchor.
     *
     * A data: URL would be simpler and is refused by Safari for downloads over
     * a few hundred KB, which a photo-heavy visit's export can reach. On iOS
     * this opens the share sheet instead of writing a file, which is the right
     * behaviour there: "Save to Files" and "Mail" are both on it.
     *
     * The object URL is revoked on the next tick, not immediately — Safari
     * aborts the download if the URL dies before it starts reading it.
     */
    var exportCsv = function () {
        if (!csvAllowed) return;
        try {
            var text = asCsvText(asCsvRows(assessment, property, scansById, model));
            var blob = new Blob([text], { type: 'text/csv;charset=utf-8;' });
            var url = URL.createObjectURL(blob);
            var anchor = document.createElement('a');
            anchor.href = url;
            anchor.download = asCsvFileName(model);
            document.body.appendChild(anchor);
            anchor.click();
            document.body.removeChild(anchor);
            setTimeout(function () { URL.revokeObjectURL(url); }, 1000);
            if (window.MetricsService) MetricsService.track('csv_exported'); // product metrics §2 — after the download is under way, never before
        } catch (err) {
            // An export that fails is a disappointment; an export that takes
            // the report page down with it is a lost visit.
            console.warn('AssessmentReportPage: CSV export failed:', err);
        }
    };

    /* The amendment history (ADR-009 D10): newest first, the same words as the
       visit screen. Derived here, not in asReportModel, so the shared report
       fixture (and the Swift port that asserts it) is unchanged; the log has
       its own fixture block. */
    var changeLog = asChangeLog(assessment);

    /**
     * exportAmendmentsCsv — the history as its own CSV beside the main export
     * (asCsv.jsx says why it is a second file, not a trailing section). Same
     * gate, same Blob-and-anchor path, same Safari revoke delay as exportCsv.
     */
    var exportAmendmentsCsv = function () {
        if (!csvAllowed) return;
        try {
            var text = asCsvText(asCsvAmendmentRows(assessment));
            var blob = new Blob([text], { type: 'text/csv;charset=utf-8;' });
            var url = URL.createObjectURL(blob);
            var anchor = document.createElement('a');
            anchor.href = url;
            anchor.download = asCsvAmendmentFileName(model);
            document.body.appendChild(anchor);
            anchor.click();
            document.body.removeChild(anchor);
            setTimeout(function () { URL.revokeObjectURL(url); }, 1000);
        } catch (err) {
            console.warn('AssessmentReportPage: amendments CSV export failed:', err);
        }
    };

    var isRoomOpen = function (room, index) {
        if (openRooms === null) return index === 0;
        return !!openRooms[room.id];
    };

    var toggleRoom = function (room, index) {
        var next = {};
        if (openRooms === null) {
            // Materialise the default (first open) before flipping one, so the
            // first tap on room 2 does not also close room 1 by accident.
            for (var r = 0; r < model.rooms.length; r++) next[model.rooms[r].id] = (r === 0);
        } else {
            for (var k in openRooms) if (Object.prototype.hasOwnProperty.call(openRooms, k)) next[k] = openRooms[k];
        }
        next[room.id] = !isRoomOpen(room, index);
        setOpenRooms(next);
    };

    var isSectionOpen = function (key) { return !!openSections[key]; };
    var toggleSection = function (key) {
        var next = {};
        for (var k in openSections) if (Object.prototype.hasOwnProperty.call(openSections, k)) next[k] = openSections[k];
        next[key] = !openSections[key];
        setOpenSections(next);
    };

    // ── collapsed-header summaries (words, never colour alone) ─────────────
    var highActions = 0;
    for (var ha = 0; ha < model.actions.length; ha++) if (model.actions[ha].priority === 'High') highActions++;
    var actionSummary = model.actions.length === 0
        ? 'No actions derived'
        : (model.actionsTruncated
            ? 'Top ' + model.actions.length + ' of ' + model.actionsTotal + ' actions'
            : arpPlural(model.actions.length, 'action'))
          + (highActions > 0 ? ' · ' + highActions + ' high priority' : '');

    var roomSummary = model.rooms.length === 0
        ? 'No rooms recorded'
        : arpPlural(model.rooms.length, 'room') + ' · ' + arpPlural(model.stats.componentsFlagged, 'component') + ' flagged';

    /* Lab groups: reported first (the results a reader is looking for), then
       pending. The model's order is kept within each group. */
    var labReported = [];
    var labPending = [];
    for (var lr = 0; lr < model.lab.rows.length; lr++) {
        if (model.lab.rows[lr].result) labReported.push(model.lab.rows[lr]);
        else labPending.push(model.lab.rows[lr]);
    }
    var labSummary = model.lab.rows.length === 0
        ? 'No samples taken'
        : [labReported.length ? labReported.length + ' reported' : '',
           labPending.length ? labPending.length + ' pending' : ''].filter(function (b) { return !!b; }).join(' · ');

    // ── §4.9 the footer line, used by both footer renderers ──────────────
    var footerBits = [brand.footerLine, 'Report ' + model.ref, 'Generated ' + generated];
    if (model.amendedLabel) footerBits.push('Amended ' + model.amendedLabel);

    var renderFooterInner = function (sizeCls) {
        return (
            <div className={'flex flex-wrap items-center justify-center gap-x-2 gap-y-0.5 font-bold text-forest/80 text-center ' + sizeCls}>
                {footerBits.map(function (bit, n) {
                    return (
                        <React.Fragment key={'fb-' + n}>
                            {n > 0 && <span aria-hidden="true">·</span>}
                            <span>{bit}</span>
                        </React.Fragment>
                    );
                })}
                <span aria-hidden="true">·</span>
                {/* R6: on every report, white-label included. */}
                <span data-testid="report-powered-by" className="font-black text-forest">{AR_POWERED_BY}</span>
            </div>
        );
    };

    // ── §4.4 one room card (a white inset inside the Rooms panel) ─────────
    var renderRoom = function (room, index) {
        var open = isRoomOpen(room, index);
        var bodyId = 'arp-room-' + room.id;
        var bits = [];
        if (room.direction) bits.push(room.direction + ' facing');
        bits.push(room.odourWord + ' odour' + (room.odourSource ? ' (' + room.odourSource + ')' : ''));
        // The metric's word (Good / Uncertain / Poor), not the raw Yes/No/
        // Uncertain answer — "Ventilation No" is not a sentence, and this is
        // the same word the room's ventilation row shows below.
        bits.push('Ventilation ' + (room.ventilationAnswer ? room.metrics[2].word.toLowerCase() : 'not assessed'));
        // Slice 5: name the colleague who walked this room. Only when it is not
        // the lead — repeating the lead's name on every card is noise, and the
        // header has already said it.
        if (room.assessorDiffers) bits.push('Assessed by ' + room.assessorName);

        return (
            <div key={room.id} data-testid="report-room" className={'report-room ' + ARP_INSET_CLS + ' overflow-hidden'}>
                <button
                    type="button"
                    data-testid="report-room-toggle"
                    aria-expanded={open ? 'true' : 'false'}
                    aria-controls={bodyId}
                    onClick={function () { toggleRoom(room, index); }}
                    className="w-full min-h-[56px] flex items-center gap-3 p-4 text-left btn-nature touch-manipulation"
                >
                    <div className="flex-1 min-w-0">
                        <div className="flex flex-wrap items-center gap-x-2 gap-y-1">
                            <p className="text-base font-extrabold text-forest break-words">{room.name}</p>
                            <span className={ARP_CHIP_CLS + ' bg-background-light text-forest'}>
                                <ArpDot hex={room.severity.hex} />
                                {room.severity.word}
                            </span>
                        </div>
                        <p className="text-sm font-medium text-forest/70 mt-1 leading-snug">{bits.join(' · ')}</p>
                    </div>
                    <span aria-hidden="true" className={'material-symbols-outlined text-forest text-2xl shrink-0 report-chrome transition-transform motion-reduce:transition-none ' + (open ? 'rotate-180' : '')}>expand_more</span>
                </button>

                {/* Always in the DOM. Hidden on screen when collapsed; the print
                    block turns `.rr-collapsed` back on so every room prints
                    open (§4.4) without a second copy of the markup. */}
                <div id={bodyId} className={'px-4 pb-4 space-y-3' + (open ? '' : ' rr-collapsed')} data-testid="report-room-body">
                    {/* Three-metric matrix (§3.7), as label/value rows: three
                        columns of uppercase labels truncate at 320 px. */}
                    <div className="report-metrics bg-background-light rounded-xl px-3 divide-y divide-sage/15">
                        {room.metrics.map(function (metric) {
                            return (
                                <div key={metric.label} className="flex items-center justify-between gap-3 py-2">
                                    <span className={ARP_LABEL_CLS}>{metric.label}</span>
                                    <span className="text-sm font-extrabold text-forest flex items-center gap-1.5">
                                        <ArpDot hex={AR_BAND_HEX[metric.band]} />
                                        {metric.word}
                                    </span>
                                </div>
                            );
                        })}
                    </div>

                    {room.components.length === 0 && (
                        <p className="text-sm font-medium text-forest">No components were marked present in this room.</p>
                    )}

                    {room.components.map(function (comp) {
                        var shown = comp.photos.slice(0, 4);
                        var extra = comp.photos.length - shown.length;
                        return (
                            <div key={comp.key} data-testid="report-component" className="report-comp pt-3 border-t border-sage/20 space-y-2">
                                <div className="flex items-center gap-2">
                                    <p className="text-sm font-extrabold text-forest flex-1 min-w-0 break-words">{comp.label}</p>
                                    {comp.assessed === false ? (
                                        /* D9: photographed, never scored. Neutral
                                           words and a dashed ring — never a band
                                           colour, never a clean 0/0/0. */
                                        <span data-testid="report-component-unassessed" className={ARP_CHIP_CLS + ' bg-background-light text-stone-700 shrink-0'}>
                                            <span className="w-2.5 h-2.5 rounded-full border border-dashed border-stone-500" aria-hidden="true"></span>
                                            Not assessed
                                        </span>
                                    ) : (
                                        <span className={ARP_CHIP_CLS + ' bg-background-light text-forest shrink-0'}>
                                            <ArpDot hex={comp.severity.hex} />
                                            {comp.severity.word}
                                        </span>
                                    )}
                                </div>

                                {comp.assessed === false && (
                                    <p className="text-sm font-medium text-forest/80 leading-relaxed">
                                        Photographed but not scored — the photos are shown as evidence; no finding is recorded for this component.
                                    </p>
                                )}

                                {comp.assessed !== false && <div className="flex flex-wrap gap-x-4 gap-y-1">
                                    {AR_AXES.map(function (axis) {
                                        var value = comp[axis.as];
                                        return (
                                            /* The one-word axis name; the full
                                               DMAT wording is the title. */
                                            <span key={axis.as} className="flex items-center gap-1.5" title={axis.label}>
                                                <ArpDot hex={AR_BAND_HEX[['good', 'fair', 'poor', 'very_poor'][value]]} />
                                                <span className="text-sm font-bold text-forest">
                                                    {axis.short} {value}
                                                </span>
                                            </span>
                                        );
                                    })}
                                </div>}

                                {comp.materials.length > 0 && (
                                    <div className="flex flex-wrap gap-1.5">
                                        {comp.materials.map(function (m) {
                                            return (
                                                <span key={m} className="text-xs font-bold px-2.5 py-1 rounded-full bg-accent/60 text-forest">{m}</span>
                                            );
                                        })}
                                    </div>
                                )}

                                {comp.notes && (
                                    <p className="text-sm font-medium text-forest/80 leading-relaxed">{comp.notes}</p>
                                )}

                                {comp.affectedAreaM2 !== null && (
                                    <p className="text-sm font-bold text-forest/80">Affected area {comp.affectedAreaM2} m²</p>
                                )}

                                {shown.length > 0 && (
                                    <div data-testid="report-photos" className="report-photos flex flex-wrap items-start gap-2">
                                        {shown.map(function (photo) {
                                            var img = photo.photoId ? photoImages[photo.photoId] : null;
                                            var thumb = photo.thumbnail || (img && img.thumbnail) || '';
                                            return (
                                                <div key={photo.scanId || photo.photoId} data-scanned={photo.scanned === false ? 'false' : 'true'} className="w-16 shrink-0">
                                                    <div className="w-16 h-16 rounded-xl overflow-hidden bg-background-light flex items-center justify-center">
                                                        {thumb ? (
                                                            <img alt="" className="w-full h-full object-cover" src={thumb} />
                                                        ) : (
                                                            <span className="material-symbols-outlined text-sage text-base" aria-hidden="true">image</span>
                                                        )}
                                                    </div>
                                                    <p
                                                        data-testid="report-photo-verdict"
                                                        className="text-[10px] font-black text-forest uppercase tracking-tight flex items-center gap-1 mt-1"
                                                    >
                                                        {photo.scanned === false ? (
                                                            /* Neutral: the untouched glyph, never a band dot. */
                                                            <span className="w-2 h-2 rounded-full border border-dashed border-stone-500 shrink-0" aria-hidden="true"></span>
                                                        ) : (
                                                            <ArpDot hex={photo.verdict.hex} />
                                                        )}
                                                        {photo.verdict.label}
                                                    </p>
                                                </div>
                                            );
                                        })}
                                        {extra > 0 && (
                                            <span className="text-sm font-black text-forest self-center">+{extra}</span>
                                        )}
                                    </div>
                                )}
                            </div>
                        );
                    })}
                </div>
            </div>
        );
    };

    // ── §4.5 one lab sample, as a stacked card (mobile-first, not a table) ─
    var renderLabRow = function (row) {
        var where = [row.roomName, row.componentLabel].filter(function (b) { return !!b; }).join(' › ');
        var facts = [
            { k: 'Collected', v: row.collectedLabel || '—' },
            { k: 'Laboratory', v: row.lab || '—' }
        ];
        if (row.result) facts.push({ k: 'Reported', v: row.reportedLabel || '—', testid: 'report-lab-reported' });
        var detail = row.result ? [row.result.speciesText,
            (row.result.sporeCount !== null ? row.result.sporeCount + (row.result.unit ? ' ' + row.result.unit : '') : ''),
            row.result.interpretation].filter(function (b) { return !!b; }) : [];

        return (
            <div key={row.id} data-testid="report-lab-row" className={'report-lab-row ' + ARP_INSET_CLS + ' p-4 space-y-3'}>
                <div className="flex items-start gap-3">
                    <div className="flex-1 min-w-0">
                        <p className={ARP_LABEL_CLS}>{arpSampleType(row.sampleType)} sample</p>
                        <p className="text-base font-extrabold text-forest break-words">{row.sampleRef || 'No reference'}</p>
                    </div>
                    {/* Status: Pending is a neutral chip (a wait, not a
                        finding); Reported is solid forest with white text —
                        white only on a solid dark fill. Neither is a band
                        colour: status is not severity (DESIGN.md §2). */}
                    <span
                        data-testid="report-lab-status"
                        className={ARP_CHIP_CLS + ' shrink-0 ' + (row.result
                            ? 'bg-forest text-white'
                            : 'bg-background-light text-stone-700 border border-dashed border-stone-400')}
                    >
                        <ArpSvg className="w-3.5 h-3.5" d={row.result ? ARP_PATH_CHECK : ARP_PATH_CLOCK} />
                        {row.status}
                    </span>
                </div>

                {where && (
                    <p className="text-sm font-bold text-forest flex items-center gap-1.5">
                        <span className="material-symbols-outlined text-base text-forest/70" aria-hidden="true">location_on</span>
                        <span className="min-w-0 break-words">{where}</span>
                    </p>
                )}

                <dl className="grid grid-cols-2 gap-x-3 gap-y-2">
                    {facts.map(function (f) {
                        return (
                            <div key={f.k} className="min-w-0" data-testid={f.testid}>
                                <dt className={ARP_LABEL_CLS}>{f.k}</dt>
                                <dd className="text-sm font-bold text-forest break-words">{f.v}</dd>
                            </div>
                        );
                    })}
                </dl>

                {row.result ? (
                    <div className="bg-background-light rounded-xl p-3">
                        <p className={ARP_LABEL_CLS}>Result</p>
                        <p data-testid="report-lab-result" className="text-sm font-medium text-forest leading-relaxed mt-0.5 break-words">
                            <span className="text-base font-extrabold">{row.result.genus || 'Result recorded'}</span>
                            {detail.length > 0 ? ' · ' + detail.join(' · ') : ''}
                        </p>
                        {row.result.notes && (
                            <p className="text-sm font-medium text-forest/80 leading-relaxed mt-1">{row.result.notes}</p>
                        )}
                    </div>
                ) : (
                    <p className="text-sm font-semibold text-forest/80 bg-background-light rounded-xl px-3 py-2.5">
                        Awaiting the laboratory.
                    </p>
                )}
            </div>
        );
    };

    return (
        <Layout>
            <main data-testid="assessment-report" className="flex-1 w-full mx-auto pb-36 overflow-y-auto overflow-x-hidden">
                <div className="report-chrome"><PageHeader title="Report" showMenu={true} backColor="text-forest" /></div>

                {/* ── §2 actions — a cream panel, never printed ──────────── */}
                <div data-testid="report-toolbar" className="report-chrome px-5 pt-2 pb-4">
                    <div className="bio-bg bio-bg-40 rounded-xl shadow-soft border border-stone-100/50 p-4 space-y-3">
                        <button
                            type="button"
                            data-testid="report-print"
                            onClick={function () { if (window.MetricsService) MetricsService.track('report_printed'); window.print(); }}
                            className="w-full min-h-[48px] bg-forest text-white font-extrabold py-3 rounded-xl shadow-clay flex items-center justify-center gap-2 btn-nature touch-manipulation hover:brightness-110 transition-all"
                        >
                            <ArpSvg className="w-5 h-5" d={ARP_PATH_PRINT} />
                            Share / Print
                        </button>

                        {/* Secondary actions. flex-wrap with a 140 px floor: two
                            across at 390 px, stacked at 320 px — no breakpoint. */}
                        <div className="flex flex-wrap gap-2">
                            <button
                                type="button"
                                data-testid="report-branding-open"
                                onClick={function () { setSheetOpen(true); }}
                                className="flex-1 min-w-[140px] min-h-[44px] px-3 py-2 rounded-xl bg-surface text-forest text-sm font-extrabold shadow-soft ring-1 ring-sage/20 flex items-center justify-center gap-2 btn-nature touch-manipulation hover:ring-2 hover:ring-forest/40 transition-all"
                            >
                                <span className="material-symbols-outlined text-lg" aria-hidden="true">palette</span>
                                Branding
                            </button>

                            {/* Export CSV (slice 5, Professional+). The gate is
                                `useEntitlements().csvExport` and nothing else.
                                Below the plan it is locked-not-broken (DESIGN.md
                                §5): full-strength forest on a dashed cream fill,
                                a lock, the plan in the label, never a fade. */}
                            <button
                                type="button"
                                data-testid="report-export-csv"
                                onClick={exportCsv}
                                disabled={!csvAllowed}
                                aria-disabled={csvAllowed ? undefined : 'true'}
                                aria-describedby={csvAllowed ? undefined : 'report-export-locked'}
                                className={'flex-1 min-w-[140px] min-h-[44px] px-3 py-2 rounded-xl text-sm font-extrabold text-forest flex items-center justify-center gap-2 text-center leading-tight ' +
                                    (csvAllowed
                                        ? 'bg-surface shadow-soft ring-1 ring-sage/20 btn-nature touch-manipulation hover:ring-2 hover:ring-forest/40 transition-all'
                                        : 'bg-background-light border border-dashed border-sage/60 cursor-not-allowed')}
                            >
                                {csvAllowed
                                    ? <ArpSvg className="w-4 h-4 shrink-0" d={ARP_PATH_DOWNLOAD} />
                                    : <span className="material-symbols-outlined text-lg shrink-0" aria-hidden="true">lock</span>}
                                {csvAllowed ? 'Export CSV' : 'Export CSV · Professional'}
                            </button>
                        </div>

                        {!csvAllowed && (
                            <p id="report-export-locked" data-testid="report-export-locked" className="text-xs font-semibold text-forest/80 px-1">
                                {AS_PRO_GATE_COPY}
                            </p>
                        )}
                    </div>
                </div>

                {/* ── the document ───────────────────────────────────────── */}
                <article data-testid="report-doc" className="report-doc space-y-4">

                    {/* 1. Header — never splits across pages. Carries the
                        status chips: status is a fact about the document. */}
                    <section className="px-5">
                        <div data-testid="report-header" className="report-head bio-bg bio-bg-10 rounded-xl p-5 shadow-soft border border-stone-100/50 space-y-4">
                            <div data-testid="report-brand" className="flex items-center gap-3">
                                {brand.logo ? (
                                    <img
                                        data-testid="report-brand-logo"
                                        src={brand.logo}
                                        alt={brand.name}
                                        className="h-10 w-auto max-w-[150px] object-contain shrink-0"
                                    />
                                ) : null}
                                <div className="min-w-0">
                                    <p data-testid="report-brand-name" className="text-base font-extrabold text-forest break-words">{brand.name}</p>
                                    {brand.contact && (
                                        <p data-testid="report-brand-contact" className="text-sm font-semibold text-forest/70 break-words">{brand.contact}</p>
                                    )}
                                </div>
                            </div>

                            {brand.intro && (
                                <p data-testid="report-brand-intro" className="text-sm font-medium text-forest/80 leading-relaxed">{brand.intro}</p>
                            )}

                            <div className="flex flex-wrap items-center gap-2">
                                <span data-testid="report-status-chip" className={ARP_CHIP_CLS + ' ' + chip.cls}>
                                    {chip.label}{model.recordStatus === 'amended' && model.amendedLabel ? ' ' + model.amendedLabel : ''}
                                </span>
                                {model.stats.labPending > 0 && (
                                    /* Neutral: waiting on a lab is not a
                                       severity (DESIGN.md §2). */
                                    <span data-testid="report-pending-chip" className={ARP_CHIP_CLS + ' bg-surface text-stone-700 border border-dashed border-stone-400'}>
                                        <ArpSvg className="w-3.5 h-3.5" d={ARP_PATH_CLOCK} />
                                        {model.stats.labPending} lab result{model.stats.labPending !== 1 ? 's' : ''} pending
                                    </span>
                                )}
                            </div>

                            <div className="flex items-start gap-4">
                                <div className="flex-1 min-w-0">
                                    <span className={ARP_LABEL_CLS}>{model.title}</span>
                                    <h1 data-testid="report-address" className="text-xl font-extrabold text-forest leading-tight mt-0.5 break-words">
                                        {model.property.address || model.property.name || 'This property'}
                                    </h1>
                                    <p className="text-sm font-semibold text-forest/70 mt-1">
                                        {model.visit.dateLabel ? 'Visited ' + model.visit.dateLabel : 'Visit date not recorded'}
                                        {' · '}
                                        <span data-testid="report-ref">Ref {model.ref}</span>
                                    </p>
                                    {model.visit.assessorName && (
                                        /* Slice 5: the lead, and how many
                                           colleagues were with them. The team's
                                           names belong on the rooms they
                                           walked, not in a list in the header. */
                                        <p data-testid="report-assessed-by" className="text-sm font-semibold text-forest/70">
                                            Assessed by {model.visit.assessorName}
                                            {model.visit.teamCount > 0
                                                ? ' with ' + model.visit.teamCount + ' other' + (model.visit.teamCount !== 1 ? 's' : '')
                                                : ''}
                                            {model.visit.assessorCertNo ? ' · ' + model.visit.assessorCertNo : ''}
                                        </p>
                                    )}
                                </div>
                                <div className="flex flex-col items-center gap-1.5 shrink-0">
                                    <ArpGauge pct={model.risk.gaugePct} hex={model.risk.hex} />
                                    <span
                                        data-testid="report-risk-badge"
                                        className={ARP_CHIP_CLS + ' bg-surface text-forest'}
                                    >
                                        <ArpDot hex={model.risk.hex} />
                                        {model.risk.badge}
                                    </span>
                                </div>
                            </div>
                        </div>
                    </section>

                    {/* 2. Summary — always visible, laid out like the Reports
                        page's ReportSummary: a forest micro-title on the page,
                        then cream cards (no white on green). */}
                    <section data-testid="report-summary" className="px-5 report-section space-y-3">
                        <h2 className="report-seclabel text-[10px] font-black text-forest uppercase tracking-[0.15em]">Summary</h2>

                        <div className="bio-bg bio-bg-20 rounded-2xl p-5 shadow-soft border border-stone-100/50 space-y-3">
                            <div className="flex flex-wrap items-center gap-2">
                                <p data-testid="report-risk-profile" className="text-base font-extrabold text-forest flex items-center gap-2">
                                    <ArpDot hex={model.risk.hex} />
                                    {model.risk.profile}
                                </p>
                                <span className={ARP_CHIP_CLS + ' bg-surface text-forest'} data-testid="report-status">
                                    {model.status}
                                </span>
                            </div>
                            <p data-testid="report-summary-sentence" className="text-sm font-medium text-forest leading-relaxed">
                                {model.summary}
                            </p>

                            {/* Severity distribution — counts at 3/2/1 (§3.5). */}
                            <div data-testid="report-distribution" className="pt-3 border-t border-forest/10 space-y-2">
                                <span className={ARP_LABEL_CLS}>Severity distribution</span>
                                <div className="flex h-3.5 rounded-full overflow-hidden bg-surface shadow-clay">
                                    {[
                                        { n: model.stats.distribution.severe, band: 'very_poor' },
                                        { n: model.stats.distribution.moderate, band: 'poor' },
                                        { n: model.stats.distribution.low, band: 'fair' }
                                    ].map(function (seg) {
                                        if (!seg.n) return null;
                                        return (
                                            <span
                                                key={seg.band}
                                                className="h-full"
                                                style={{ flex: seg.n, backgroundColor: AR_BAND_HEX[seg.band] }}
                                            ></span>
                                        );
                                    })}
                                </div>
                                <div className="flex flex-wrap gap-x-4 gap-y-1">
                                    {[
                                        { n: model.stats.distribution.severe, word: 'Severe', band: 'very_poor' },
                                        { n: model.stats.distribution.moderate, word: 'Moderate', band: 'poor' },
                                        { n: model.stats.distribution.low, word: 'Low', band: 'fair' }
                                    ].map(function (seg) {
                                        return (
                                            <span key={seg.word} className="text-sm font-bold text-forest flex items-center gap-1.5">
                                                <ArpDot hex={AR_BAND_HEX[seg.band]} />
                                                {seg.n} {seg.word}
                                            </span>
                                        );
                                    })}
                                </div>
                                {model.stats.distributionTotal === 0 && (
                                    <p className="text-sm font-medium text-forest">No components flagged.</p>
                                )}
                            </div>
                        </div>

                        {/* Four stat tiles, 2×2 (§4.2) — ReportSummary's tile:
                            cream, big forest numeral, uppercase micro-label. */}
                        <div data-testid="report-stats" className="report-stats grid grid-cols-2 gap-3">
                            {[
                                { key: 'rooms', n: model.stats.roomsAssessed, label: 'Rooms assessed' },
                                { key: 'flagged', n: model.stats.componentsFlagged, label: 'Components flagged' },
                                { key: 'critical', n: model.stats.criticalIssues, label: 'Critical issues' },
                                { key: 'lab', n: model.stats.labReported + ' / ' + model.stats.labPending, label: 'Lab reported / pending' }
                            ].map(function (stat, n) {
                                return (
                                    <div key={stat.key} data-testid={'report-stat-' + stat.key} className={'bio-bg ' + (n % 2 ? 'bio-bg-40' : 'bio-bg-30') + ' rounded-2xl shadow-soft border border-stone-100/50 p-4 flex flex-col items-center text-center gap-1.5'}>
                                        <p className="text-2xl font-extrabold text-forest leading-none">{stat.n}</p>
                                        <p className="text-[10px] font-black text-sage uppercase tracking-widest leading-snug">{stat.label}</p>
                                    </div>
                                );
                            })}
                        </div>
                    </section>

                    {/* 3. Remediation Action Plan — open by default. */}
                    <ArpSection
                        testid="report-action-plan" title="Remediation action plan" icon="construction" bg="bio-bg-30"
                        summary={actionSummary} count={model.actions.length}
                        collapsible={true} open={isSectionOpen('report-action-plan')} onToggle={function () { toggleSection('report-action-plan'); }}
                    >
                        {model.actions.length === 0 ? (
                            <p data-testid="report-actions-empty" className={'text-sm font-medium text-forest px-4 py-3 ' + ARP_INSET_CLS}>
                                No remediation actions derived.
                            </p>
                        ) : (
                            <div className="space-y-2">
                                {model.actions.map(function (action) {
                                    return (
                                        <div key={action.id} data-testid="report-action" className={'report-action ' + ARP_INSET_CLS + ' p-4 space-y-1.5'}>
                                            <div className="flex flex-wrap items-center gap-2">
                                                <span
                                                    data-testid="report-action-priority"
                                                    className={ARP_CHIP_CLS + ' bg-background-light text-forest'}
                                                >
                                                    <ArpDot hex={AR_BAND_HEX[action.band]} />
                                                    {action.priority}
                                                </span>
                                                {action.extension && (
                                                    <span className={ARP_CHIP_CLS + ' bg-accent/60 text-forest'}>Lab</span>
                                                )}
                                            </div>
                                            <p data-testid="report-action-title" className="text-sm font-extrabold text-forest">
                                                {action.title}
                                            </p>
                                            <p className="text-sm font-medium text-forest/80 leading-relaxed">{action.text}</p>
                                        </div>
                                    );
                                })}
                                {model.actionsTruncated && (
                                    <p data-testid="report-actions-truncated" className="text-sm font-semibold text-forest/80 px-1">
                                        Showing the {AR_ACTION_CAP} highest-priority actions of {model.actionsTotal}.
                                    </p>
                                )}
                            </div>
                        )}
                    </ArpSection>

                    {/* 4. Rooms — per-room findings; each room is its own
                        accordion inside (first open, §4.4). */}
                    <ArpSection
                        testid="report-rooms" title="Rooms" icon="door_open" bg="bio-bg-20"
                        summary={roomSummary} count={model.rooms.length}
                        collapsible={true} open={isSectionOpen('report-rooms')} onToggle={function () { toggleSection('report-rooms'); }}
                    >
                        {model.rooms.length === 0 ? (
                            <p className={'text-sm font-medium text-forest px-4 py-3 ' + ARP_INSET_CLS}>No rooms were recorded on this visit.</p>
                        ) : (
                            <div className="space-y-3">{model.rooms.map(renderRoom)}</div>
                        )}
                    </ArpSection>

                    {/* 5. Laboratory results — stacked cards, reported then
                        pending, with the counts in the header. */}
                    <ArpSection
                        testid="report-lab" title="Laboratory results" icon="science" bg="bio-bg-40"
                        summary={labSummary} count={model.lab.rows.length}
                        collapsible={true} open={isSectionOpen('report-lab')} onToggle={function () { toggleSection('report-lab'); }}
                    >
                        {model.lab.rows.length === 0 ? (
                            <p className={'text-sm font-medium text-forest px-4 py-3 ' + ARP_INSET_CLS}>No samples were taken on this visit.</p>
                        ) : (
                            <div className="space-y-4">
                                {labReported.length > 0 && (
                                    <div data-testid="report-lab-group-reported" className="space-y-2">
                                        <p className={ARP_LABEL_CLS + ' px-1'}>Reported · {labReported.length}</p>
                                        {labReported.map(renderLabRow)}
                                    </div>
                                )}
                                {labPending.length > 0 && (
                                    <div data-testid="report-lab-group-pending" className="space-y-2">
                                        <p className={ARP_LABEL_CLS + ' px-1'}>Awaiting the laboratory · {labPending.length}</p>
                                        {labPending.map(renderLabRow)}
                                    </div>
                                )}
                                {model.amendments.length > 0 && (
                                    // One amendment day per line (it ran on as one
                                    // dot-joined string); detail lives in the appendix.
                                    <div data-testid="report-lab-footnote" className="px-1 space-y-1">
                                        <ul className="space-y-1">
                                            {model.amendments.map(function (a) {
                                                return (
                                                    <li key={a.day} className="flex gap-2 text-sm font-semibold text-forest leading-relaxed">
                                                        <span className="material-symbols-outlined text-base mt-0.5 shrink-0" aria-hidden="true">history</span>
                                                        <span>{a.line}</span>
                                                    </li>
                                                );
                                            })}
                                        </ul>
                                        <p className="text-xs font-semibold text-forest/80">Full detail: Appendix — Amendment history.</p>
                                    </div>
                                )}
                            </div>
                        )}
                    </ArpSection>

                    {/* 6. Environmental readings — short, always visible. */}
                    <ArpSection testid="report-readings" title="Environmental readings" icon="thermostat" bg="bio-bg-10">
                        {model.readings.items.length === 0 ? (
                            <p className={'text-sm font-medium text-forest px-4 py-3 ' + ARP_INSET_CLS}>No device readings were recorded.</p>
                        ) : (
                            <div className="grid grid-cols-2 gap-2">
                                {model.readings.items.map(function (item) {
                                    return (
                                        <div key={item.key} data-testid="report-reading" className={'report-reading ' + ARP_INSET_CLS + ' p-3'}>
                                            <p className={ARP_LABEL_CLS}>{item.label}</p>
                                            <p className="text-lg font-extrabold text-forest">{item.value} <span className="text-sm font-bold text-forest/70">{item.unit}</span></p>
                                        </div>
                                    );
                                })}
                            </div>
                        )}
                        <p className="text-sm font-medium text-forest/80 leading-relaxed mt-3 px-1">{model.readings.caption}</p>
                    </ArpSection>

                    {/* 7. Remediation plan & sign-off — short, always visible. */}
                    <ArpSection testid="report-signoff" title="Remediation plan & sign-off" icon="verified" bg="bio-bg-30">
                        <div className={'report-signoff ' + ARP_INSET_CLS + ' px-4 py-2'}>
                            {[
                                ['Risk classification', model.remediation.riskClassification],
                                ['Method', model.remediation.method],
                                ['Containment', model.remediation.containmentType],
                                ['PPE level', model.remediation.ppeLevel],
                                ['Affected area', model.remediation.affectedAreaM2 !== null ? model.remediation.affectedAreaM2 + ' m²' : '']
                            ].map(function (pair) {
                                return (
                                    <div key={pair[0]} className="flex flex-wrap items-baseline justify-between gap-x-3 gap-y-0.5 py-2 border-b border-sage/15">
                                        <span className={ARP_LABEL_CLS + ' shrink-0'}>{pair[0]}</span>
                                        <span className="text-sm font-bold text-forest text-right break-words">{pair[1] || '—'}</span>
                                    </div>
                                );
                            })}
                            <div className="py-3">
                                {model.signoff.signed ? (
                                    <p data-testid="report-signed" className="text-sm font-bold text-forest">
                                        Signed {model.signoff.signedAtLabel} by {model.signoff.technicianName || 'the technician'}
                                        {model.signoff.technicianCertNo ? ' · ' + model.signoff.technicianCertNo : ''}
                                    </p>
                                ) : (
                                    <p data-testid="report-not-signed" className="text-sm font-bold text-forest">Not signed</p>
                                )}
                            </div>
                        </div>
                    </ArpSection>

                    {/* 8. Scoring key & notes. The key and the lab note are
                        reference material and collapse; the decision-support
                        disclaimer (ADR-0007) stays visible under the header
                        even when the section is closed — it qualifies every
                        finding above it and must never be one tap away. */}
                    <ArpSection
                        testid="report-key" title="Scoring key & notes" icon="info" bg="bio-bg-20"
                        summary="The 0–3 scale and the lab-action rule" count={arpScoreKey().length}
                        collapsible={true} open={isSectionOpen('report-key')} onToggle={function () { toggleSection('report-key'); }}
                        always={(
                            <p data-testid="report-disclaimer" className="text-sm font-medium text-forest/80 leading-relaxed px-4 pb-4 -mt-1">
                                {model.disclaimer}
                            </p>
                        )}
                    >
                        <div className={ARP_INSET_CLS + ' p-4 space-y-3'}>
                            <div className="space-y-2">
                                {arpScoreKey().map(function (step) {
                                    return (
                                        <p key={step.value} className="text-sm font-medium text-forest flex items-start gap-2 leading-snug">
                                            <span className="mt-1"><ArpDot hex={step.hex} /></span>
                                            <span><span className="font-extrabold">{step.value} {step.word}</span> — {step.desc}</span>
                                        </p>
                                    );
                                })}
                            </div>
                            <p data-testid="report-lab-note" className="text-sm font-medium text-forest/80 leading-relaxed pt-3 border-t border-sage/15">
                                Actions marked <span className="font-black">Lab</span> are a Mould Detect extension to the
                                standard assessment rules: a reported result of High or Very high raises a high-priority
                                action, Elevated raises a medium one. Samples still awaiting the laboratory raise none.
                            </p>
                        </div>
                    </ArpSection>

                    {/* Appendix — the amendment history (ADR-009 D10). Printed
                        (it is part of the document an auditor reads), after
                        the key so the findings come first. Read-only: the
                        report has no way to change it, and neither does the
                        app. Rendered only once something was amended. */}
                    {changeLog.length > 0 && (
                        <ArpSection
                            testid="report-amendments" title="Appendix — Amendment history" icon="history" bg="bio-bg-10"
                            summary={arpPlural(changeLog.length, 'change') + ' after completion · read-only'} count={changeLog.length}
                            collapsible={true} open={isSectionOpen('report-amendments')} onToggle={function () { toggleSection('report-amendments'); }}
                        >
                            <div className="space-y-3">
                                <p className="text-sm font-medium text-forest/80 leading-relaxed px-1">
                                    Every change made after this visit was completed, newest first. Times are shown in the
                                    reader's time zone. Entries are recorded automatically and cannot be edited or removed.
                                </p>
                                {asChangeLogDays(changeLog).map(function (day) {
                                    return (
                                        <div key={day.key} data-testid="report-amendment-day" className="report-amend-day space-y-2">
                                            <p className={ARP_LABEL_CLS + ' px-1'}>{day.label}</p>
                                            {day.entries.map(function (entry, n) {
                                                return (
                                                    <div key={entry.at + '-' + n} data-testid="report-amendment" className={'report-amend-row ' + ARP_INSET_CLS + ' px-4 py-3'}>
                                                        <div className="flex items-start justify-between gap-3">
                                                            <p className="text-sm font-extrabold text-forest min-w-0 break-words">{entry.title}</p>
                                                            <time dateTime={entry.at} title={asRevisionStamp(entry.at)} className="text-xs font-bold text-forest/70 shrink-0 tabular-nums">
                                                                {asRevisionTime(entry.at)}
                                                            </time>
                                                        </div>
                                                        {entry.change && (
                                                            <p className="text-sm font-semibold text-forest mt-1 break-words">{entry.change}</p>
                                                        )}
                                                        <p className="text-xs font-semibold text-forest/70 mt-1">
                                                            {asChangeLogByline(entry)}
                                                            {entry.legacy ? ' · recorded before field-level history' : ''}
                                                        </p>
                                                    </div>
                                                );
                                            })}
                                        </div>
                                    );
                                })}

                                {/* Screen only (report-chrome is hidden in print):
                                    the history as its own CSV, Professional+ like
                                    the main export. Locked-not-broken (DESIGN §5). */}
                                <div className="report-chrome">
                                    <button
                                        type="button"
                                        data-testid="report-export-amendments"
                                        onClick={exportAmendmentsCsv}
                                        disabled={!csvAllowed}
                                        aria-disabled={csvAllowed ? undefined : 'true'}
                                        aria-describedby={csvAllowed ? undefined : 'report-amendments-locked-note'}
                                        className={csvAllowed
                                            ? 'w-full min-h-[44px] bg-surface text-forest text-sm font-extrabold py-3 rounded-xl shadow-soft ring-1 ring-sage/20 flex items-center justify-center gap-2 btn-nature touch-manipulation hover:ring-2 hover:ring-forest/40 transition-all'
                                            : 'w-full min-h-[44px] bg-background-light text-forest text-sm font-extrabold py-3 px-3 rounded-xl border border-dashed border-sage/60 flex items-center justify-center gap-2 text-center leading-tight cursor-not-allowed'}
                                    >
                                        {csvAllowed
                                            ? <ArpSvg className="w-4 h-4 shrink-0" d={ARP_PATH_DOWNLOAD} />
                                            : <span className="material-symbols-outlined text-lg shrink-0" aria-hidden="true">lock</span>}
                                        {csvAllowed ? 'Export amendment history (CSV)' : 'Export amendment history · Professional'}
                                    </button>
                                    {!csvAllowed && (
                                        <p id="report-amendments-locked-note" className="text-xs font-semibold text-forest/80 mt-1.5 px-1">
                                            {AS_PRO_GATE_COPY}
                                        </p>
                                    )}
                                </div>
                            </div>
                        </ArpSection>
                    )}

                    {/* 9. Footer, on the document itself. Printed at the end of
                        the last page in every browser — Safari included, where a
                        position:fixed footer prints on page 1 only. On screen it
                        is a cream strip: no text directly on the green page. */}
                    <div data-testid="report-doc-footer" className="report-doc-footer px-5 pb-6">
                        <div className="bio-bg bio-bg-30 rounded-xl border border-stone-100/50 px-4 py-3">
                            {renderFooterInner('text-xs')}
                        </div>
                    </div>
                </article>

                {/* The repeating print footer. Chrome renders a position:fixed
                    element once per printed page, which is what §4.9 asks for;
                    it is display:none on screen and in browsers that do not
                    print it, the document footer above still lands. */}
                <div data-testid="report-print-footer" className="report-print-footer px-5 py-2">
                    {renderFooterInner('text-[9px]')}
                </div>

                {sheetOpen && <ReportBrandingSheet onClose={function () { setSheetOpen(false); }} />}
            </main>
        </Layout>
    );
};

window.AssessmentReportPage = AssessmentReportPage;
