var _useState_ASV    = React.useState;
var _useParams_ASV   = ReactRouterDOM.useParams;
var _useNavigate_ASV = ReactRouterDOM.useNavigate;

/**
 * AssessmentVisitPage — one property visit: the header the inspector fills once
 * and the list of rooms they work through (route `/assessment/:id`).
 *
 * Spec: architecture/MOULD-ASSESSMENT-MIGRATION-BRIEF.md §5 (phone rules) and
 * §6 slice 1. Record: AssessmentStore.jsx / ADR-009.
 *
 * Shape of the screen, and why:
 *   • One room per screen (§5.1) — this page never shows a component grid; it
 *     lists rooms and hands off to AssessmentRoomPage. The source app's
 *     desktop matrix does not survive 390 px.
 *   • No Save button anywhere (§5.6). Every field writes straight through the
 *     store, which debounces at 300 ms and flushes on pagehide; the "Saved"
 *     chip is the receipt.
 *   • A completed visit stays fully editable (decision 1) — lab results arrive
 *     days or weeks later. "Reopen to amend" records the return; it does not
 *     unlock anything, because nothing was locked.
 *
 * File-local helpers use the `asv` prefix (the app has no module system, so
 * every top-level var is a window global — CLAUDE.md hard rule).
 */

// Quick-picks for the add-room row. Deliberately no icons: the Material
// Symbols font is SUBSET (assets/fonts/symbols.icons.txt) and has no bedroom /
// kitchen / laundry glyphs, so an icon here would render as the literal
// ligature word.
// Names come from the shared list (AppConstants.ROOM_TYPES) so a quick-picked
// room and a typed one never differ ("Living" vs "Living Room").
var ASV_ROOM_QUICK_PICKS = ['Main Bedroom', 'Living Room', 'Kitchen', 'Bathroom', 'Laundry Room', 'Garage', 'Subfloor', 'Other'];

var ASV_INPUT_CLS = 'w-full bg-surface border-none rounded-xl text-sm p-3 shadow-soft ring-1 ring-sage/20 focus:ring-2 focus:ring-primary font-semibold text-forest placeholder:text-sage/60 placeholder:font-medium';
var ASV_LABEL_CLS = 'text-[10px] font-black text-sage uppercase tracking-[0.15em] ml-1';

/* Photos per room come from asRoomPhotoCount (AssessmentStore) — the same
   de-duplicating rule the room screen and the component rows use, because
   `photoIds` and `scanIds` carry the SAME ids (ADR-009 D3) and summing both
   would report every photo twice. */

/** asvRoomPendingSamples — lab samples for this room with no result yet. */
function asvRoomPendingSamples(assessment, roomId) {
    var pending = 0;
    var samples = (assessment && assessment.samples) || [];
    for (var i = 0; i < samples.length; i++) {
        if (samples[i].roomId === roomId && !samples[i].result) pending += 1;
    }
    return pending;
}

/* The n/8 counter (§5.1) is asRoomScored from AssessmentStore — the SAME rule
   the room screen's own "n/8 scored" line uses, so the two can never disagree
   (ADR-009 D4: derived, never stored, and derived in one place). */

/** asvDate — an ISO stamp as a short local date (the sign-off line). */
function asvDate(iso) {
    if (!iso) return '';
    var d = new Date(iso);
    if (isNaN(d.getTime())) return String(iso);
    return d.toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric' });
}

/**
 * Section D — the manual DMAT device readings, field for field from the
 * enterprise form (EMA_SectionEnvironmental.jsx): same keys, same labels, same
 * units, same hints, all optional.
 *
 * These are readings a technician takes with their own instrument. They are
 * NEVER merged with a Scan's automatic weatherSnapshot/airSnapshot (CLAUDE.md,
 * PRD §11.6) — one is what was measured in this room, the other is modelled
 * outdoor data, and conflating them would corrupt both the report and the moat.
 */
var ASV_READING_FIELDS = [
    { key: 'temp',         label: 'Temperature', unit: '°C',    hint: 'e.g. 22.5' },
    { key: 'humidity',     label: 'Humidity',    unit: '% RH',  hint: 'e.g. 68' },
    { key: 'co2',          label: 'CO₂ level',   unit: 'ppm',   hint: 'e.g. 1200' },
    { key: 'lightMin',     label: 'Light — min', unit: 'lux',   hint: 'e.g. 50' },
    { key: 'lightMax',     label: 'Light — max', unit: 'lux',   hint: 'e.g. 300' },
    { key: 'formaldehyde', label: 'Formaldehyde', unit: 'μg/m³', hint: 'e.g. 45' },
    { key: 'vocs',         label: 'VOCs (total)', unit: 'ppb',   hint: 'e.g. 250' }
];

/* Slice 5 — the team roles, with the words the UI shows. `lead` is offered
   because a large job can carry a second certified lead; the VISIT's lead is
   still `visit.assessorName` and is never a row in the list. */
var ASV_TEAM_ROLE_LABELS = { lead: 'Lead', assessor: 'Assessor', trainee: 'Trainee' };

/* The amendment words (category, target, field, before → after) live in the
   store's asChangeLog (ADR-009 D10), shared with the report appendix and the
   amendments CSV, so the three can never describe one change differently. */

var AssessmentVisitPage = function () {
    var params = _useParams_ASV();
    var navigate = _useNavigate_ASV();
    var store = useAssessments();
    var scanStore = useScanStore();

    var newRoomState = _useState_ASV('');
    var newRoomName = newRoomState[0];
    var setNewRoomName = newRoomState[1];

    var amendOpenState = _useState_ASV(false);
    var amendOpen = amendOpenState[0];
    var setAmendOpen = amendOpenState[1];

    // Slice 5 — the team disclosure, closed by default: most visits are one
    // inspector, and a list of empty name rows above the rooms would suggest
    // otherwise.
    var teamOpenState = _useState_ASV(false);
    var teamOpen = teamOpenState[0];
    var setTeamOpen = teamOpenState[1];

    // Professional+ (brief §4). One seam, read once: the dev tier preview
    // arrives through useEntitlements, not around it.
    var entitlements = useEntitlements();
    var teamAllowed = !!entitlements.team;

    // Sections D–F, collapsed by default: the rooms are the job, and three
    // long forms open above the completion button would bury them.
    /* The team editor's working copy. `null` means "nobody has touched it", and
       the stored list is shown. A draft is needed because the store's allowlist
       drops a member with no name (a cert number with nobody attached to it
       names nobody) — without it, "Add member" would create a row that
       disappeared before it could be typed into. */
    var teamDraftState = _useState_ASV(null);
    var teamDraft = teamDraftState[0];
    var setTeamDraft = teamDraftState[1];

    var openSectionState = _useState_ASV(null); // 'readings' | 'remediation' | 'signoff' | null
    var openSection = openSectionState[0];
    var setOpenSection = openSectionState[1];

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

    // Hydration and not-found are different states and must not be confused:
    // "no such visit" during the IndexedDB read would be a lie.
    if (!store.hydrated) {
        return (
            <Layout>
                <main className="flex-1 w-full mx-auto pb-36 overflow-y-auto overflow-x-hidden">
                    <PageHeader title="Assessment" showMenu={true} />
                    <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">Loading this visit…</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">
                    <PageHeader title="Assessment" showMenu={true} />
                    <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-xs font-medium text-forest mt-1">It may have been removed on another device.</p>
                            <button
                                onClick={function () { navigate('/location-details'); }}
                                className="mt-4 w-full 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;
    var summary = asDeriveSummary(assessment);
    var chip = asStatusChip(assessment.status);
    var isComplete = !!assessment.completedAt;
    // Derived at render, never stored (ADR-009 D4): newest first, target words
    // resolved against the record as it is NOW (D10).
    var changeLog = asChangeLog(assessment);

    var patchVisit = function (field, value) {
        var patch = {};
        patch[field] = value;
        store.updateVisit(assessment.id, patch);
    };

    /* ── Team (slice 5) ────────────────────────────────────────────────────
       The draft is the on-screen truth while a row is being typed; the store
       holds only rows that have a name. Every edit writes through immediately —
       there are no Save buttons on any assessment screen (§5.6). */
    var teamRows = (teamDraft === null) ? (assessment.visit.team || []) : teamDraft;
    // Team is optional: an empty team is a fact ("Just the lead"), not a to-do.
    var teamState = asSectionState('team', assessment);

    var writeTeam = function (rows) {
        setTeamDraft(rows);
        store.updateTeam(assessment.id, rows);
    };

    var patchTeamMember = function (index, field, value) {
        var rows = [];
        for (var i = 0; i < teamRows.length; i++) {
            rows.push({ name: teamRows[i].name, certNo: teamRows[i].certNo, role: teamRows[i].role });
        }
        if (!rows[index]) return;
        rows[index][field] = value;
        writeTeam(rows);
    };

    var addTeamMember = function () {
        if (teamRows.length >= AS_TEAM_MAX) return;
        var rows = teamRows.concat([{ name: '', certNo: '', role: 'assessor' }]);
        // Only the draft changes: an unnamed row is not a team member yet, and
        // writing it would be a no-op the store correctly discards.
        setTeamDraft(rows);
    };

    var removeTeamMember = function (index) {
        var rows = [];
        for (var i = 0; i < teamRows.length; i++) {
            if (i !== index) rows.push(teamRows[i]);
        }
        writeTeam(rows);
    };

    var addRoom = function (name) {
        var trimmed = (name || '').replace(/^\s+|\s+$/g, '');
        if (!trimmed) return;
        var updated = store.addRoom(assessment.id, { name: trimmed });
        setNewRoomName('');
        if (updated && updated.rooms.length) {
            var room = updated.rooms[updated.rooms.length - 1];
            navigate('/assessment/' + assessment.id + '/room/' + room.id);
        }
    };

    var field = function (label, testid, value, onChange, placeholder, type) {
        return (
            <div className="flex flex-col gap-1">
                <label className={ASV_LABEL_CLS}>{label}</label>
                <input
                    type={type || 'text'}
                    data-testid={testid}
                    className={ASV_INPUT_CLS}
                    placeholder={placeholder || ''}
                    value={value}
                    onChange={function (e) { onChange(e.target.value); }}
                />
            </div>
        );
    };

    /* ── Sections D–F, rewired to the aggregate (slice 2) ──────────────────
       These were the enterprise form's last three sections and they wrote flat
       fields onto the Scan. They now write `environmentalReadings`,
       `remediation` and `signoff` on the Assessment through their own
       allowlist writers. Option lists come from AppConstants verbatim — the
       same lists the DMAT form used, not a restatement of them.

       They live on the VISIT, not the room: a remediation plan and a sign-off
       cover the property visit as a whole, which is exactly why the flat
       one-scan record could not hold them. */
    var sectionCard = function (id, title, subtitle, body) {
        var open = openSection === id;
        // Section form state (ADR-009 D9): derived from the record, never
        // stored — untouched / in progress / complete, in the shared chip.
        var sectionState = asSectionState(id, assessment);
        return (
            <div className="bio-bg bio-bg-20 rounded-xl shadow-soft border border-stone-100/50 overflow-hidden">
                <button
                    data-testid={'section-' + id + '-toggle'}
                    aria-expanded={open ? 'true' : 'false'}
                    onClick={function () { setOpenSection(open ? null : id); }}
                    className="w-full flex items-center justify-between gap-3 p-4 text-left btn-nature"
                >
                    <span className="min-w-0">
                        <span className="flex items-center gap-2 flex-wrap">
                            <span className="text-sm font-extrabold text-forest">{title}</span>
                            <AssessmentStateChip testid={'section-' + id + '-state'} state={sectionState.state} label={sectionState.label} />
                        </span>
                        <span className="block text-[11px] font-semibold text-muted">{subtitle}</span>
                    </span>
                    <span className="material-symbols-outlined text-forest shrink-0">{open ? 'expand_less' : 'expand_more'}</span>
                </button>
                {open && <div data-testid={'section-' + id} className="px-4 pb-4 space-y-3">{body()}</div>}
            </div>
        );
    };

    var readings = assessment.environmentalReadings || {};
    var patchReadings = function (key, value) {
        var p = {};
        p[key] = value;
        store.updateReadings(assessment.id, p);
    };

    var renderReadings = function () {
        return (
            <React.Fragment>
                <p className="text-[11px] font-medium text-forest/70 leading-relaxed">
                    All optional — record only what your equipment supports. These are your own device
                    readings; the app's weather and air-quality figures are kept separate and never mixed in.
                </p>
                <div className="grid grid-cols-2 gap-3">
                    {ASV_READING_FIELDS.map(function (f) {
                        return (
                            <div key={f.key} className="flex flex-col gap-1">
                                <div className="flex items-center justify-between gap-1">
                                    <label className={ASV_LABEL_CLS}>{f.label}</label>
                                    <span className="text-[9px] font-bold text-forest shrink-0">{f.unit}</span>
                                </div>
                                <input
                                    type="number"
                                    step="any"
                                    // Decimal pad for non-negative measures; temperature
                                    // keeps the full keyboard because it can be below 0
                                    // and iOS's decimal pad has no minus key.
                                    inputMode={f.key === 'temp' ? undefined : 'decimal'}
                                    data-testid={'reading-' + f.key}
                                    className={ASV_INPUT_CLS}
                                    placeholder={f.hint}
                                    value={readings[f.key] || ''}
                                    onChange={function (e) { patchReadings(f.key, e.target.value); }}
                                />
                            </div>
                        );
                    })}
                </div>
            </React.Fragment>
        );
    };

    var patchRemediation = function (key, value) {
        var p = {};
        p[key] = value;
        store.updateRemediation(assessment.id, p);
    };

    var selectRow = function (label, testid, value, options, onChange, placeholder) {
        return (
            <div className="flex flex-col gap-1">
                <label className={ASV_LABEL_CLS}>{label}</label>
                <select
                    data-testid={testid}
                    className={ASV_INPUT_CLS + ' appearance-none'}
                    value={value}
                    onChange={function (e) { onChange(e.target.value); }}
                >
                    <option value="">{placeholder}</option>
                    {options.map(function (o) { return <option key={o} value={o}>{o}</option>; })}
                </select>
            </div>
        );
    };

    var renderRemediation = function () {
        var rem = assessment.remediation;
        return (
            <React.Fragment>
                <div className="flex flex-col gap-1">
                    <label className={ASV_LABEL_CLS}>Overall risk classification</label>
                    <div className="grid grid-cols-4 gap-2">
                        {AppConstants.RISK_LEVELS.map(function (level) {
                            var selected = rem.riskClassification === level;
                            return (
                                <button
                                    key={level}
                                    data-testid={'remediation-risk-' + level}
                                    aria-pressed={selected ? 'true' : 'false'}
                                    onClick={function () { patchRemediation('riskClassification', selected ? '' : level); }}
                                    className={'py-2.5 rounded-xl text-[11px] font-black btn-nature transition-all ' +
                                        (selected ? 'bg-forest text-white shadow-clay' : 'bg-surface border border-stone-100/50 text-forest shadow-soft hover:bg-forest hover:text-white')}
                                >
                                    {level}
                                </button>
                            );
                        })}
                    </div>
                </div>
                {selectRow('Recommended remediation method', 'remediation-method', rem.method,
                    AppConstants.REMEDIATION_METHODS, function (v) { patchRemediation('method', v); }, 'Select method…')}
                {selectRow('Containment required', 'remediation-containment', rem.containmentType,
                    AppConstants.CONTAINMENT_TYPES, function (v) { patchRemediation('containmentType', v); }, 'Select containment…')}
                {selectRow('PPE level required', 'remediation-ppe', rem.ppeLevel,
                    AppConstants.PPE_LEVELS, function (v) { patchRemediation('ppeLevel', v); }, 'Select PPE level…')}
                <div className="flex flex-col gap-1">
                    <label className={ASV_LABEL_CLS}>Total affected area (m²)</label>
                    <input
                        type="number"
                        inputMode="decimal"
                        min="0"
                        step="any"
                        data-testid="remediation-area"
                        className={ASV_INPUT_CLS}
                        placeholder="e.g. 12"
                        value={rem.affectedAreaM2 === null ? '' : rem.affectedAreaM2}
                        onChange={function (e) { patchRemediation('affectedAreaM2', e.target.value); }}
                    />
                    <p className="text-[11px] font-medium text-forest/70 leading-relaxed px-1">
                        The area this plan covers. Per-component areas are recorded on the component itself and are not summed into this.
                    </p>
                </div>
            </React.Fragment>
        );
    };

    var renderSignoff = function () {
        var sign = assessment.signoff;
        // The assessor's name stands in until the technician types a different
        // one; `signOff` persists whatever is on screen at the moment of
        // signing, so the stored record never disagrees with what was shown.
        var name = sign.technicianName || assessment.visit.assessorName;
        var cert = sign.technicianCertNo || assessment.visit.assessorCertNo;
        return (
            <React.Fragment>
                <div className="grid grid-cols-2 gap-3">
                    {field('Technician', 'signoff-name', name, function (v) { store.signOff(assessment.id, { technicianName: v }); }, 'Full name')}
                    {field('Cert / licence no.', 'signoff-cert', cert, function (v) { store.signOff(assessment.id, { technicianCertNo: v }); }, 'e.g. IICRC-12345')}
                </div>
                {sign.signedAt ? (
                    <p data-testid="signoff-signed" className="text-xs font-bold text-forest bg-primary/20 rounded-xl px-3 py-2">
                        Signed off {asvDate(sign.signedAt)} by {sign.technicianName || 'this technician'}
                    </p>
                ) : (
                    <button
                        data-testid="signoff-sign"
                        onClick={function () { store.signOff(assessment.id, { technicianName: name, technicianCertNo: cert, sign: true }); }}
                        className="w-full bg-forest text-white font-extrabold py-3 rounded-xl shadow-clay flex items-center justify-center gap-2 btn-nature hover:brightness-110 transition-all"
                    >
                        <span className="material-symbols-outlined">check_circle</span>
                        Sign off
                    </button>
                )}
                <p className="text-[11px] font-medium text-forest/70 leading-relaxed">
                    Signing completes the visit. It does not lock it — lab results and corrections are still welcome, and each one is stamped.
                </p>
            </React.Fragment>
        );
    };

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

                {/* Visit header — who, when, and the building this is about. */}
                <section className="px-5 pt-4">
                    <div className="bio-bg bio-bg-10 rounded-xl p-5 shadow-soft border border-stone-100/50 space-y-4">
                        <div className="flex items-start justify-between gap-3">
                            <div className="min-w-0">
                                <span className="text-[10px] font-black text-sage uppercase tracking-[0.15em]">Visit</span>
                                <h2 className="text-base font-extrabold text-forest truncate">{(property && (property.name || property.address)) || 'This property'}</h2>
                                {property && property.address && property.name && (
                                    <p className="text-[11px] font-semibold text-muted truncate">{property.address}</p>
                                )}
                            </div>
                            <div className="flex flex-col items-end gap-1 shrink-0">
                                <span data-testid="assessment-status" className={'text-[9px] px-2 py-0.5 rounded-full font-black uppercase tracking-tight ' + chip.cls}>{chip.label}</span>
                                <AssessmentSavedChip updatedAt={assessment.updatedAt} />
                            </div>
                        </div>

                        <div className="space-y-3">
                            {field('Visit date', 'visit-date', assessment.visit.date, function (v) { patchVisit('date', v); }, '', 'date')}
                            <div className="grid grid-cols-2 gap-3">
                                {field('Assessor', 'visit-assessor', assessment.visit.assessorName, function (v) { patchVisit('assessorName', v); }, 'Full name')}
                                {field('Cert no.', 'visit-cert', assessment.visit.assessorCertNo, function (v) { patchVisit('assessorCertNo', v); }, 'e.g. IICRC 1234')}
                            </div>
                            <div className="grid grid-cols-2 gap-3">
                                {field('Building', 'visit-building', assessment.visit.building, function (v) { patchVisit('building', v); }, 'e.g. Brick veneer')}
                                {field('Age', 'visit-building-age', assessment.visit.buildingAge, function (v) { patchVisit('buildingAge', v); }, 'e.g. 1970s')}
                            </div>
                            {field('Floor / level', 'visit-floor', assessment.visit.floor, function (v) { patchVisit('floor', v); }, 'e.g. Ground')}
                            <p className="text-[11px] font-medium text-forest/70 leading-relaxed">
                                Building details start from the property record and stay with this visit — editing them here changes the visit only.
                            </p>
                        </div>

                        {/* Team (slice 5, Professional+). A disclosure, not a
                            section: most visits are one inspector, and a list of
                            empty name rows above the rooms would suggest
                            otherwise. Below Professional the rows are VISIBLE
                            and disabled — hiding the capability from the
                            customer deciding whether to pay for it helps
                            nobody. */}
                        <div className="border-t border-sage/20 pt-3">
                            <button
                                data-testid="visit-team-toggle"
                                aria-expanded={teamOpen ? 'true' : 'false'}
                                onClick={function () { setTeamOpen(!teamOpen); }}
                                className="w-full flex items-center justify-between gap-3 text-left btn-nature"
                            >
                                <span className="min-w-0">
                                    <span className="flex items-center gap-2 flex-wrap">
                                        <span className="text-sm font-extrabold text-forest">
                                            Team{teamRows.length ? ' (' + teamRows.length + ')' : ''}
                                        </span>
                                        <AssessmentStateChip testid="section-team-state" state={teamState.state} label={teamState.label} />
                                    </span>
                                    <span className="block text-[11px] font-semibold text-muted">
                                        Who else was on site
                                    </span>
                                </span>
                                <span className="material-symbols-outlined text-forest shrink-0">{teamOpen ? 'expand_less' : 'expand_more'}</span>
                            </button>

                            {teamOpen && (
                                <div data-testid="visit-team" className="pt-3 space-y-3">
                                    {/* The lead is the visit's assessor, read
                                        only here: there is exactly one answer to
                                        "whose visit is this", and it is the
                                        field above. */}
                                    <div data-testid="visit-team-lead" className="flex items-center gap-2 bg-surface rounded-xl px-3 py-2 shadow-soft">
                                        <span className="text-xs font-extrabold text-forest flex-1 min-w-0 truncate">
                                            {assessment.visit.assessorName || 'Not named yet'}
                                        </span>
                                        <span className="text-[9px] px-2 py-0.5 rounded-full font-black uppercase tracking-tight bg-forest text-white shrink-0">Lead</span>
                                    </div>

                                    {teamRows.map(function (member, index) {
                                        return (
                                            <div key={'team-' + index} data-testid="visit-team-member" className="bg-surface rounded-xl p-3 shadow-soft space-y-2">
                                                <div className="grid grid-cols-2 gap-2">
                                                    <input
                                                        type="text"
                                                        data-testid={'team-name-' + index}
                                                        className={ASV_INPUT_CLS + ' disabled:opacity-60'}
                                                        placeholder="Full name"
                                                        disabled={!teamAllowed}
                                                        value={member.name}
                                                        onChange={function (e) { patchTeamMember(index, 'name', e.target.value); }}
                                                    />
                                                    <input
                                                        type="text"
                                                        data-testid={'team-cert-' + index}
                                                        className={ASV_INPUT_CLS + ' disabled:opacity-60'}
                                                        placeholder="Cert no."
                                                        disabled={!teamAllowed}
                                                        value={member.certNo}
                                                        onChange={function (e) { patchTeamMember(index, 'certNo', e.target.value); }}
                                                    />
                                                </div>
                                                <div className="flex items-center gap-2">
                                                    <div className="flex flex-wrap gap-1.5 flex-1">
                                                        {AS_TEAM_ROLES.map(function (role) {
                                                            var selected = member.role === role;
                                                            return (
                                                                <button
                                                                    key={role}
                                                                    data-testid={'team-role-' + index + '-' + role}
                                                                    aria-pressed={selected ? 'true' : 'false'}
                                                                    disabled={!teamAllowed}
                                                                    onClick={function () { patchTeamMember(index, 'role', role); }}
                                                                    className={'px-3 py-1.5 rounded-full text-[11px] font-black btn-nature transition-all disabled:opacity-60 ' +
                                                                        (selected ? 'bg-forest text-white shadow-clay' : 'bg-background-light text-forest shadow-soft')}
                                                                >
                                                                    {ASV_TEAM_ROLE_LABELS[role]}
                                                                </button>
                                                            );
                                                        })}
                                                    </div>
                                                    <button
                                                        data-testid={'team-remove-' + index}
                                                        disabled={!teamAllowed}
                                                        onClick={function () { removeTeamMember(index); }}
                                                        aria-label="Remove this team member"
                                                        className="w-9 h-9 rounded-xl bg-background-light text-forest flex items-center justify-center shrink-0 btn-nature hover:bg-forest hover:text-white transition-all disabled:opacity-40"
                                                    >
                                                        <span className="material-symbols-outlined text-lg">close</span>
                                                    </button>
                                                </div>
                                            </div>
                                        );
                                    })}

                                    {/* Below Professional the button reads LOCKED, not
                                        broken: full-strength forest on a dashed cream
                                        fill with a lock and the plan named in the
                                        label, instead of the 40%-opacity fade that
                                        looked like a rendering fault. The gate itself
                                        is unchanged — `disabled` still keys off
                                        teamAllowed; aria-disabled + describedby point
                                        assistive tech at the plan note below. */}
                                    <button
                                        data-testid="team-add"
                                        disabled={!teamAllowed || teamRows.length >= AS_TEAM_MAX}
                                        aria-disabled={(!teamAllowed || teamRows.length >= AS_TEAM_MAX) ? 'true' : undefined}
                                        aria-describedby={teamAllowed ? undefined : 'visit-team-locked-note'}
                                        onClick={addTeamMember}
                                        className={teamAllowed
                                            ? 'w-full bg-surface text-forest font-extrabold py-3 rounded-xl shadow-soft border border-sage/20 flex items-center justify-center gap-2 btn-nature hover:bg-forest hover:text-white transition-all disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-surface disabled:hover:text-forest'
                                            : 'w-full bg-background-light text-forest font-extrabold py-3 rounded-xl border border-dashed border-sage/50 flex items-center justify-center gap-2 cursor-not-allowed'}
                                    >
                                        {/* `person_add`, not `add`: the Material
                                            Symbols font is SUBSET
                                            (assets/fonts/symbols.icons.txt) and
                                            `add` is not in it — it would render
                                            as a stray glyph. `lock` IS listed. */}
                                        <span className="material-symbols-outlined text-lg" aria-hidden="true">{teamAllowed ? 'person_add' : 'lock'}</span>
                                        {teamAllowed ? 'Add member' : 'Add member · Professional'}
                                    </button>

                                    {!teamAllowed && (
                                        <p id="visit-team-locked-note" data-testid="visit-team-locked" className="text-[11px] font-bold text-forest/70 px-1">
                                            {AS_PRO_GATE_COPY}
                                        </p>
                                    )}
                                    {teamAllowed && (
                                        <p className="text-[11px] font-medium text-forest/70 leading-relaxed px-1">
                                            Up to {AS_TEAM_MAX} besides the lead. Pick who assessed each room on the room screen.
                                        </p>
                                    )}
                                </div>
                            )}
                        </div>
                    </div>
                </section>

                {/* Rooms — one per screen (§5.1). */}
                <section className="px-5 py-4 space-y-3">
                    <div className="flex items-center justify-between">
                        <h2 className="text-base font-extrabold text-forest">Rooms</h2>
                        <span className="text-[10px] font-black text-forest uppercase tracking-widest">
                            {summary.roomCount} room{summary.roomCount !== 1 ? 's' : ''}
                        </span>
                    </div>

                    {/* Pending lab results — a LIST, not a count (§5.1b). An
                        inspector opening the app three days later is here for
                        one sample; each row jumps straight to it in its room,
                        carrying the sample id as navigation state (the routes
                        are a MemoryRouter, so nothing travels in the URL). */}
                    {summary.pendingSamples > 0 && (
                        <div data-testid="visit-pending-lab" className="bg-[#d4a373]/20 rounded-xl px-3 py-2 space-y-2">
                            <p className="text-xs font-bold text-forest">
                                {summary.pendingSamples} lab result{summary.pendingSamples !== 1 ? 's' : ''} pending
                            </p>
                            <div className="space-y-1.5">
                                {asPendingSamples(assessment).map(function (entry) {
                                    return (
                                        <button
                                            key={entry.sample.id}
                                            data-testid="visit-pending-sample"
                                            onClick={function () {
                                                navigate('/assessment/' + assessment.id + '/room/' + entry.roomId,
                                                    { state: { focusSampleId: entry.sample.id } });
                                            }}
                                            className="w-full text-left bg-surface rounded-xl px-3 py-2 shadow-soft btn-nature hover:bg-forest transition-all duration-300 group flex items-center gap-2"
                                        >
                                            <span className="text-[11px] font-bold text-forest group-hover:text-white flex-1 min-w-0 truncate transition-colors">
                                                {entry.roomName} · {entry.sample.sampleRef || 'No reference'}
                                            </span>
                                            <span className="material-symbols-outlined text-forest group-hover:text-white text-base shrink-0 transition-colors">arrow_forward</span>
                                        </button>
                                    );
                                })}
                            </div>
                        </div>
                    )}

                    {assessment.rooms.length === 0 && (
                        <div className="bio-bg bio-bg-20 rounded-xl p-5 shadow-soft border border-stone-100/50 text-center">
                            <p className="text-sm font-extrabold text-forest">No rooms yet</p>
                            <p className="text-xs font-medium text-forest mt-1">Add the first area you are inspecting — one room per screen.</p>
                        </div>
                    )}

                    {assessment.rooms.map(function (room) {
                        var scored = asRoomScored(room);
                        var photos = asRoomPhotoCount(room);
                        var pending = asvRoomPendingSamples(assessment, room.id);
                        var progress = asRoomProgress(room);
                        return (
                            <button
                                key={room.id}
                                data-testid="assessment-room-row"
                                onClick={function () { navigate('/assessment/' + assessment.id + '/room/' + room.id); }}
                                className="group w-full text-left bio-bg bio-bg-10 rounded-xl p-4 shadow-soft border border-stone-100/50 btn-nature hover:bg-forest transition-all duration-300"
                            >
                                <div className="flex items-center gap-3">
                                    <div className="w-10 h-10 rounded-2xl bg-background-light text-forest group-hover:bg-primary/20 group-hover:text-primary flex items-center justify-center shrink-0 transition-colors">
                                        <span className="material-symbols-outlined text-lg">door_open</span>
                                    </div>
                                    <div className="flex-1 min-w-0">
                                        <div className="flex items-center gap-2">
                                            <p className="text-sm font-extrabold text-forest group-hover:text-white truncate transition-colors">{room.name || 'Unnamed room'}</p>
                                            {room.direction && (
                                                <span className="text-[9px] px-1.5 py-0.5 rounded-full font-black uppercase tracking-tight bg-accent/60 text-forest shrink-0">{room.direction}</span>
                                            )}
                                        </div>
                                        <p className="text-[10px] font-bold text-sage group-hover:text-white/60 transition-colors">
                                            {scored}/{AS_ROOM_COMPONENT_TARGET} components scored · {photos} photo{photos !== 1 ? 's' : ''}
                                        </p>
                                        {/* Room form state (D9). The chip's fill is light
                                            (bg-background-light), so it keeps forest text
                                            even when the card hovers to forest. */}
                                        <div className="pt-1">
                                            <AssessmentStateChip testid="assessment-room-state" state={progress.state} label={progress.label} />
                                        </div>
                                    </div>
                                    {pending > 0 && (
                                        <span className="text-[9px] px-2 py-0.5 rounded-full font-black uppercase tracking-tight bg-[#d4a373]/25 text-forest shrink-0">Pending lab</span>
                                    )}
                                    <span className="material-symbols-outlined text-forest group-hover:text-white text-lg shrink-0 transition-colors">arrow_forward</span>
                                </div>
                            </button>
                        );
                    })}

                    {/* Add a room — quick-picks first, because the eight names
                        below cover most of a residential inspection in one tap. */}
                    <div className="bio-bg bio-bg-30 rounded-xl p-4 shadow-soft border border-stone-100/50 space-y-3">
                        <label className={ASV_LABEL_CLS}>Add a room</label>
                        <div className="flex gap-2">
                            {/* Suggestions from the shared room list (same Typeahead
                                as the property page); any typed name still adds. */}
                            <div className="flex-1 min-w-0">
                                <Typeahead
                                    allowCustom
                                    testId="room-name-input"
                                    options={AppConstants.ROOM_TYPES}
                                    inputClassName={ASV_INPUT_CLS}
                                    placeholder="Search or name this area"
                                    value={newRoomName}
                                    onChange={setNewRoomName}
                                    onEnter={addRoom}
                                />
                            </div>
                            <button
                                data-testid="add-room"
                                onClick={function () { addRoom(newRoomName); }}
                                disabled={!newRoomName.replace(/^\s+|\s+$/g, '')}
                                className="px-4 rounded-xl bg-forest text-white font-extrabold shadow-clay btn-nature hover:brightness-110 transition-all disabled:opacity-40 disabled:cursor-not-allowed shrink-0"
                            >
                                Add
                            </button>
                        </div>
                        <div className="flex flex-wrap gap-2">
                            {ASV_ROOM_QUICK_PICKS.map(function (name) {
                                return (
                                    <button
                                        key={name}
                                        data-testid={'room-quick-' + name}
                                        onClick={function () { addRoom(name); }}
                                        className="px-3 min-h-[44px] rounded-full text-xs font-bold bg-surface border border-stone-100/50 text-forest shadow-soft btn-nature touch-manipulation hover:ring-1 hover:ring-forest/50 transition-all"
                                    >
                                        {name}
                                    </button>
                                );
                            })}
                        </div>
                    </div>
                </section>

                {/* Sections D–F — readings, plan, sign-off. Visit-level. */}
                <section className="px-5 pb-4 space-y-3">
                    {sectionCard('readings', 'Environmental readings', 'Your own device measurements — all optional', renderReadings)}
                    {sectionCard('remediation', 'Remediation', 'Risk, method, containment, PPE', renderRemediation)}
                    {sectionCard('signoff', 'Sign-off', 'Technician, certificate and signature', renderSignoff)}
                </section>

                {/* Completion + the amendment trail. */}
                <section className="px-5 pb-8 space-y-3">
                    {/* View report (slice 4, ASSESSMENT-REPORT-SPEC §2). Enabled
                        once ANY component is scored or the visit is complete: a
                        visit with nothing in it derives an empty document, and
                        offering to "view" that is a dead end, not a feature. */}
                    <button
                        data-testid="view-report"
                        onClick={function () { navigate('/assessment/' + assessment.id + '/report'); }}
                        disabled={summary.componentsScored === 0 && !isComplete}
                        className="w-full bio-bg bio-bg-30 text-forest font-extrabold py-4 rounded-xl shadow-soft border border-stone-100/50 flex items-center justify-center gap-2 btn-nature touch-manipulation hover:ring-2 hover:ring-forest/40 transition-all disabled:opacity-40 disabled:cursor-not-allowed"
                    >
                        <span className="material-symbols-outlined">description</span>
                        View report
                    </button>

                    {!isComplete && (
                        <button
                            data-testid="mark-complete"
                            onClick={function () { store.completeAssessment(assessment.id); }}
                            disabled={assessment.rooms.length === 0}
                            className="w-full bg-forest text-white font-extrabold py-4 rounded-xl shadow-clay flex items-center justify-center gap-2 btn-nature hover:brightness-110 transition-all disabled:opacity-40 disabled:cursor-not-allowed"
                        >
                            <span className="material-symbols-outlined">check_circle</span>
                            Mark complete
                        </button>
                    )}

                    {isComplete && (
                        <div className="space-y-3">
                            <button
                                data-testid="reopen"
                                onClick={function () { store.reopenAssessment(assessment.id); }}
                                className="w-full bio-bg bio-bg-30 text-forest font-extrabold py-4 rounded-xl shadow-soft border border-stone-100/50 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">edit</span>
                                Reopen to amend
                            </button>
                            <p className="text-xs font-bold text-forest leading-relaxed px-1">
                                A completed visit is never locked. Lab results and corrections can be added any time; each one is stamped below.
                            </p>

                            <div className="bio-bg bio-bg-20 rounded-xl shadow-soft border border-stone-100/50 overflow-hidden">
                                <button
                                    data-testid="amendments-toggle"
                                    onClick={function () { setAmendOpen(!amendOpen); }}
                                    aria-expanded={amendOpen ? 'true' : 'false'}
                                    className="w-full flex items-center justify-between p-4 text-left btn-nature"
                                >
                                    <span className="text-sm font-extrabold text-forest">
                                        Amendment history{changeLog.length ? ' (' + changeLog.length + ')' : ''}
                                    </span>
                                    <span className="material-symbols-outlined text-forest" aria-hidden="true">{amendOpen ? 'expand_less' : 'expand_more'}</span>
                                </button>
                                {/* The change log (ADR-009 D10). Read-only by
                                    design: no edit, no delete, no swipe action —
                                    an audit trail anyone can tidy is not one.
                                    Newest first, under local day headers; the
                                    words come from asChangeLog, the same ones
                                    the report appendix and the CSV use. */}
                                {amendOpen && (
                                    <div data-testid="amendments-list" className="px-4 pb-4 space-y-3">
                                        {changeLog.length === 0 && (
                                            <p className="text-xs font-medium text-forest">Nothing has changed since this visit was completed.</p>
                                        )}
                                        {asChangeLogDays(changeLog).map(function (day) {
                                            return (
                                                <section key={day.key} data-testid="amendment-day" aria-label={day.label}>
                                                    <h3 className={ASV_LABEL_CLS + ' mb-1.5'}>{day.label}</h3>
                                                    <ol className="space-y-2">
                                                        {day.entries.map(function (entry, n) {
                                                            return (
                                                                <li key={entry.at + '-' + n} data-testid="amendment-entry" className="bg-surface rounded-xl px-3 py-2.5 shadow-soft">
                                                                    <div className="flex items-start justify-between gap-3">
                                                                        <p data-testid="amendment-title" className="text-xs font-extrabold text-forest min-w-0 break-words">{entry.title}</p>
                                                                        <time
                                                                            data-testid="amendment-time"
                                                                            dateTime={entry.at}
                                                                            title={asRevisionStamp(entry.at)}
                                                                            className="text-[11px] font-bold text-forest/70 shrink-0 tabular-nums"
                                                                        >
                                                                            {asRevisionTime(entry.at)}
                                                                        </time>
                                                                    </div>
                                                                    {entry.change && (
                                                                        <p data-testid="amendment-change" className="text-xs font-semibold text-forest mt-0.5 break-words">{entry.change}</p>
                                                                    )}
                                                                    <p data-testid="amendment-by" className="text-[11px] font-medium text-forest/70 mt-0.5">
                                                                        {asChangeLogByline(entry)}
                                                                        {entry.legacy ? ' · recorded before field-level history' : ''}
                                                                    </p>
                                                                </li>
                                                            );
                                                        })}
                                                    </ol>
                                                </section>
                                            );
                                        })}
                                        {changeLog.length > 0 && (
                                            <p className="text-[11px] font-medium text-forest/80 leading-relaxed px-1">
                                                Read-only. Every change after completion is recorded here and in the report; entries cannot be edited or removed.
                                            </p>
                                        )}
                                    </div>
                                )}
                            </div>
                        </div>
                    )}
                </section>
            </main>
        </Layout>
    );
};

window.AssessmentVisitPage = AssessmentVisitPage;
