import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Head, router, usePage } from '@inertiajs/react';
import {
    AlertTriangle,
    BatteryCharging,
    Camera,
    Check,
    CheckCircle2,
    ClipboardList,
    CloudUpload,
    FileText,
    Gauge,
    ImagePlus,
    Info,
    Lock,
    MessageSquare,
    PenLine,
    Plus,
    RotateCcw,
    ShieldCheck,
    Sun,
    Trash2,
    TriangleAlert,
    X,
} from 'lucide-react';
import type { LucideIcon } from 'lucide-react';
import { NativeSelect } from '@/components/crm/native-select';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { formatDate, formatDateTime, toDateInputValue } from '@/lib/format';
import { cn } from '@/lib/utils';
import type {
    InspectionAttachment,
    InspectionChecklistSection,
    InspectionData,
    InspectionDefect,
    PublicInspection,
} from '@/types';

type PageProps = {
    report: PublicInspection;
};

type Answer = 'pass' | 'fail' | 'na';

const ANSWER_OPTIONS: Array<{
    value: Answer;
    label: string;
    className: string;
    activeClassName: string;
}> = [
    {
        value: 'pass',
        label: 'Pass',
        className: 'hover:border-emerald-500/40',
        activeClassName:
            'border-emerald-600 bg-emerald-600 text-white dark:border-emerald-500 dark:bg-emerald-500',
    },
    {
        value: 'fail',
        label: 'Fail',
        className: 'hover:border-red-500/40',
        activeClassName:
            'border-red-600 bg-red-600 text-white dark:border-red-500 dark:bg-red-500',
    },
    {
        value: 'na',
        label: 'N/A',
        className: 'hover:border-slate-400/40',
        activeClassName:
            'border-slate-600 bg-slate-700 text-white dark:border-slate-400 dark:bg-slate-500',
    },
];

const SEVERITIES = [
    { value: 'minor', label: 'Minor' },
    { value: 'major', label: 'Major' },
    { value: 'critical', label: 'Critical' },
];

/**
 * The report a crew fills in, at the other end of the shared link.
 *
 * Written for a phone held in one hand on a roof: big tap targets, one section at
 * a time with a progress rail, photos uploaded as they are taken (so poor signal
 * cannot cost them), a draft that saves itself, and a signature drawn on the
 * screen. Everything the server needs is posted as one document at the end.
 */
export default function PublicInspection({ report }: PageProps) {
    const { errors } = usePage<{ errors: Record<string, string> }>().props;

    const prefill = report.prefill;

    const [doc, setDoc] = useState<InspectionData>(() => ({
        system: { ...prefill.system },
        site: { ...prefill.site },
        answers: prefill.answers ?? {},
        item_notes: prefill.item_notes ?? {},
        measurements: prefill.measurements ?? {},
        defects: prefill.defects ?? [],
        findings: prefill.findings ?? '',
        recommendations: prefill.recommendations ?? '',
        customer: {
            name: prefill.customer?.name ?? '',
            comment: prefill.customer?.comment ?? '',
        },
        declaration: {
            ...prefill.declaration,
            date: prefill.declaration?.date ?? toDateInputValue(new Date().toISOString()),
            agreed: false,
        },
        outcome: prefill.outcome ?? '',
    }));

    // The type decides which sections are asked for, so it follows the form:
    // switching to a hybrid system has to reveal the battery section at once.
    const systemType = doc.system?.type ?? 'pv';
    const hasBattery = ['hybrid', 'battery'].includes(systemType);

    const [savingDraft, setSavingDraft] = useState(false);
    const [savedAt, setSavedAt] = useState<string | null>(null);
    const [submitting, setSubmitting] = useState(false);
    const [uploading, setUploading] = useState(0);
    const [signatureSaved, setSignatureSaved] = useState(false);
    const [showIssues, setShowIssues] = useState(false);

    const dirty = useRef(false);
    const firstRender = useRef(true);

    const update = useCallback((mutator: (draft: InspectionData) => void) => {
        setDoc((previous) => {
            const next: InspectionData = {
                ...previous,
                system: { ...previous.system },
                site: { ...previous.site },
                answers: { ...previous.answers },
                item_notes: { ...previous.item_notes },
                measurements: { ...previous.measurements },
                defects: previous.defects ? [...previous.defects] : [],
                customer: { ...previous.customer },
                declaration: { ...previous.declaration },
            };

            mutator(next);
            dirty.current = true;

            return next;
        });
    }, []);

    const sections = useMemo(
        () =>
            report.checklist.sections.filter(
                (section) => !section.battery_only || hasBattery,
            ),
        [report.checklist.sections, hasBattery],
    );

    const answers = doc.answers ?? {};
    const itemNotes = doc.item_notes ?? {};
    const measurements = doc.measurements ?? {};
    const defects = doc.defects ?? [];

    const totalChecks = sections.reduce(
        (total, section) => total + section.items.length,
        0,
    );
    const answeredChecks = sections.reduce(
        (total, section) =>
            total +
            section.items.filter((item) => Boolean(answers[section.key]?.[item.key]))
                .length,
        0,
    );

    const photos = report.attachments.filter((file) => file.kind === 'photo');
    const documents = report.attachments.filter((file) => file.kind === 'document');
    const signature = report.attachments.find((file) => file.kind === 'signature');

    const requiredMeasurements = useMemo(
        () =>
            systemType === 'battery'
                ? ['earth_continuity', 'ac_voltage', 'frequency']
                : [
                      'string_voltage',
                      'insulation',
                      'earth_continuity',
                      'ac_voltage',
                      'frequency',
                  ],
        [systemType],
    );

    /**
     * What still has to be done before this can be submitted. The server checks
     * all of it again — this list exists so nobody has to guess.
     */
    const issues = useMemo(() => {
        const found: string[] = [];
        const system = doc.system ?? {};
        const site = doc.site ?? {};
        const declaration = doc.declaration ?? {};

        if (!system.kw) found.push('Record the system size.');
        if (systemType !== 'battery' && !system.modules)
            found.push('Record the modules installed.');
        if (!system.inverter) found.push('Record the inverter make and model.');
        if (hasBattery && !system.battery)
            found.push('Record the battery make and model.');
        if (!site.switchboard) found.push('Say where the switchboard is.');
        if (!site.roof_condition) found.push('Record the condition of the roof.');

        const unanswered = sections.flatMap((section) =>
            section.items
                .filter((item) => !answers[section.key]?.[item.key])
                .map((item) => `${section.title}: ${item.label}`),
        );

        if (unanswered.length > 0) {
            found.push(
                unanswered.length === 1
                    ? 'One checklist item still needs an answer.'
                    : `${unanswered.length} checklist items still need an answer.`,
            );
        }

        requiredMeasurements.forEach((key) => {
            if (!measurements[key]) {
                found.push(
                    `Record the ${
                        report.checklist.measurements[key]?.label.toLowerCase() ??
                        'reading'
                    }.`,
                );
            }
        });

        if (!measurements.instrument) found.push('Record the test instrument used.');

        if (Object.values(answers).some((section) => Object.values(section).includes('fail')) && defects.length === 0) {
            found.push('Something failed: add the defect behind it.');
        }

        if (doc.outcome === 'pass' && defects.length > 0)
            found.push('A report with defects outstanding cannot pass.');

        if ((doc.outcome === 'conditional' || doc.outcome === 'fail') && defects.length === 0)
            found.push('Record the defects behind this outcome.');

        if (!doc.outcome) found.push('Choose what the inspection concluded.');

        if (photos.length < 3)
            found.push(
                `Add at least three photos — the array, the inverter and the switchboard (${photos.length} so far).`,
            );

        if (!declaration.name) found.push('Sign the declaration with your name.');
        if (!declaration.company) found.push('Record your company.');
        if (!declaration.licence)
            found.push('Record the electrical licence number of the person signing.');
        if (!declaration.agreed) found.push('Tick the declaration to submit.');

        if (!signatureSaved && !signature)
            found.push('Sign the declaration on the screen.');

        return found;
    }, [
        answers,
        defects.length,
        doc.declaration,
        doc.outcome,
        doc.site,
        doc.system,
        hasBattery,
        measurements,
        photos.length,
        report.checklist.measurements,
        requiredMeasurements,
        sections,
        signature,
        signatureSaved,
        systemType,
    ]);

    // Save quietly while the crew works, so a dropped page is not a lost
    // inspection. Nothing is written until something has actually changed.
    useEffect(() => {
        if (firstRender.current) {
            firstRender.current = false;

            return;
        }

        if (!dirty.current) {
            return;
        }

        const timer = window.setTimeout(() => {
            if (!dirty.current) {
                return;
            }

            dirty.current = false;
            setSavingDraft(true);

            router.post(
                report.urls.draft,
                { data: doc, quiet: true },
                {
                    preserveState: true,
                    preserveScroll: true,
                    onFinish: () => setSavingDraft(false),
                    onSuccess: () => setSavedAt(new Date().toISOString()),
                },
            );
        }, 25000);

        return () => window.clearTimeout(timer);
    }, [doc, report.urls.draft]);

    const saveDraft = () => {
        dirty.current = false;
        setSavingDraft(true);

        router.post(
            report.urls.draft,
            { data: doc },
            {
                preserveState: true,
                preserveScroll: true,
                onFinish: () => setSavingDraft(false),
                onSuccess: () => setSavedAt(new Date().toISOString()),
            },
        );
    };

    const uploadFiles = (files: FileList | null, kind: 'photo' | 'document') => {
        if (files === null || files.length === 0) {
            return;
        }

        const queue = Array.from(files);

        const send = (index: number) => {
            if (index >= queue.length) {
                return;
            }

            setUploading((count) => count + 1);

            router.post(
                report.urls.files,
                { file: queue[index], kind },
                {
                    forceFormData: true,
                    preserveState: true,
                    preserveScroll: true,
                    onFinish: () => {
                        setUploading((count) => Math.max(count - 1, 0));
                        send(index + 1);
                    },
                },
            );
        };

        send(0);
    };

    const removeFile = (file: InspectionAttachment) => {
        if (!file.delete_url) {
            return;
        }

        router.delete(file.delete_url, {
            preserveState: true,
            preserveScroll: true,
        });
    };

    const submit = () => {
        if (issues.length > 0) {
            setShowIssues(true);

            return;
        }

        setSubmitting(true);

        router.post(report.urls.submit, doc, {
            // A rejected submission comes back with errors; the answers already
            // on the page have to survive that, or the crew would retype them.
            preserveState: true,
            preserveScroll: true,
            onFinish: () => setSubmitting(false),
            onError: () => setShowIssues(true),
        });
    };

    if (!report.is_open) {
        return <SubmittedReport report={report} />;
    }

    const progress = totalChecks === 0 ? 0 : Math.round((answeredChecks / totalChecks) * 100);

    return (
        <>
            <Head title={`${report.reference} — inspection report`}>
                <meta name="robots" content="noindex,nofollow" />
            </Head>

            <div className="flex flex-col gap-4 pb-28 sm:pb-6">
                <ReportHeader report={report} />

                <div className="bg-background/95 supports-[backdrop-filter]:bg-background/80 sticky top-0 z-20 -mx-4 border-b border-hairline px-4 py-2.5 backdrop-blur sm:-mx-6 sm:px-6">
                    <div className="flex items-center gap-3">
                        <div className="bg-muted h-1.5 flex-1 overflow-hidden rounded-full">
                            <div
                                className="bg-solar h-full rounded-full transition-[width] duration-300"
                                style={{ width: `${progress}%` }}
                            />
                        </div>
                        <span className="crm-num text-muted-foreground shrink-0 text-xs">
                            {answeredChecks}/{totalChecks} checks
                        </span>
                    </div>
                    <nav className="mt-2 flex gap-1 overflow-x-auto pb-0.5">
                        <RailLink href="#system" label="System" done={Boolean(doc.system?.kw && doc.site?.switchboard)} />
                        {sections.map((section) => (
                            <RailLink
                                key={section.key}
                                href={`#section-${section.key}`}
                                label={section.title.split(' ')[0]}
                                done={section.items.every(
                                    (item) => answers[section.key]?.[item.key],
                                )}
                            />
                        ))}
                        <RailLink href="#tests" label="Tests" done={requiredMeasurements.every((key) => measurements[key]) && Boolean(measurements.instrument)} />
                        <RailLink href="#photos" label="Photos" done={photos.length >= 3} />
                        <RailLink href="#declaration" label="Sign" done={Boolean(signature || signatureSaved) && Boolean(doc.declaration?.agreed)} />
                    </nav>
                </div>

                {showIssues && issues.length > 0 ? (
                    <div className="rounded-xl bg-amber-500/10 px-4 py-3 ring-1 ring-inset ring-amber-500/25">
                        <p className="flex items-center gap-2 text-sm font-semibold">
                            <TriangleAlert className="size-4 text-amber-600 dark:text-amber-400" />
                            A few things are still needed
                        </p>
                        <ul className="mt-1.5 list-disc space-y-1 pl-5 text-xs leading-relaxed">
                            {issues.map((issue) => (
                                <li key={issue}>{issue}</li>
                            ))}
                        </ul>
                    </div>
                ) : null}

                <SystemSection
                    doc={doc}
                    hasBattery={hasBattery}
                    systemTypes={report.checklist.system_types}
                    onChange={update}
                    errors={errors}
                />

                {sections.map((section) => (
                    <ChecklistSectionCard
                        key={section.key}
                        section={section}
                        answers={answers[section.key] ?? {}}
                        notes={itemNotes[section.key] ?? {}}
                        errors={errors}
                        onChange={(itemKey, value) =>
                            update((draft) => {
                                draft.answers = draft.answers ?? {};
                                draft.answers[section.key] = {
                                    ...draft.answers[section.key],
                                    [itemKey]: value,
                                };
                            })
                        }
                        onNoteChange={(itemKey, value) =>
                            update((draft) => {
                                draft.item_notes = draft.item_notes ?? {};
                                draft.item_notes[section.key] = {
                                    ...draft.item_notes[section.key],
                                    [itemKey]: value,
                                };
                            })
                        }
                    />
                ))}

                <TestsSection
                    measurements={measurements}
                    measurementsDefinition={report.checklist.measurements}
                    required={requiredMeasurements}
                    onChange={(key, value) =>
                        update((draft) => {
                            draft.measurements = {
                                ...draft.measurements,
                                [key]: value,
                            };
                        })
                    }
                    errors={errors}
                />

                <DefectsSection
                    defects={defects}
                    onChange={(next) =>
                        update((draft) => {
                            draft.defects = next;
                        })
                    }
                    errors={errors}
                />

                <FilesSection
                    photos={photos}
                    documents={documents}
                    limits={report.limits}
                    uploading={uploading}
                    onUpload={uploadFiles}
                    onRemove={removeFile}
                />

                <FindingsSection
                    findings={doc.findings ?? ''}
                    recommendations={doc.recommendations ?? ''}
                    customerComment={doc.customer?.comment ?? ''}
                    outcome={doc.outcome ?? ''}
                    outcomes={report.checklist.outcomes}
                    hasDefects={defects.length > 0}
                    onChange={update}
                />

                <DeclarationSection
                    declaration={doc.declaration ?? {}}
                    hasBattery={hasBattery}
                    signature={signature}
                    signatureSaved={signatureSaved}
                    errors={errors}
                    onChange={update}
                    onSignatureUpload={(file) => {
                        setUploading((count) => count + 1);

                        router.post(
                            report.urls.files,
                            {
                                file,
                                kind: 'signature',
                                caption: `Signed declaration — ${doc.declaration?.name ?? 'crew'}`,
                            },
                            {
                                forceFormData: true,
                                preserveState: true,
                                preserveScroll: true,
                                onFinish: () =>
                                    setUploading((count) => Math.max(count - 1, 0)),
                                onSuccess: () => setSignatureSaved(true),
                            },
                        );
                    }}
                />

                <div className="grid gap-3 sm:grid-cols-[1fr_auto] sm:items-center">
                    <p className="text-muted-foreground text-xs">
                        {savedAt
                            ? `Draft saved ${formatDateTime(savedAt)}.`
                            : 'Your answers are kept as a draft as you go. You can close this page and come back.'}
                    </p>
                    <div className="flex gap-2">
                        <Button
                            type="button"
                            variant="outline"
                            onClick={saveDraft}
                            disabled={savingDraft}
                        >
                            <CloudUpload />
                            {savingDraft ? 'Saving…' : 'Save draft'}
                        </Button>
                        <Button type="button" onClick={submit} disabled={submitting}>
                            <CheckCircle2 />
                            {submitting ? 'Submitting…' : 'Submit report'}
                        </Button>
                    </div>
                </div>

                {/* On a phone the actions follow the crew down the page. */}
                <div className="bg-background/95 fixed inset-x-0 bottom-0 z-30 border-t border-hairline px-4 py-3 backdrop-blur sm:hidden">
                    <div className="flex items-center gap-2">
                        <Button
                            type="button"
                            variant="outline"
                            size="sm"
                            onClick={saveDraft}
                            disabled={savingDraft}
                        >
                            Save
                        </Button>
                        <Button
                            type="button"
                            size="sm"
                            className="flex-1"
                            onClick={submit}
                            disabled={submitting}
                        >
                            {submitting ? 'Submitting…' : 'Submit report'}
                        </Button>
                    </div>
                </div>
            </div>
        </>
    );
}

function ReportHeader({ report }: { report: PublicInspection }) {
    return (
        <header className="bg-card shadow-card overflow-hidden rounded-xl ring-1 ring-hairline">
            <div className="bg-solar-soft/70 border-solar/20 border-b px-4 py-3 sm:px-5">
                <p className="text-solar-ink text-[11px] font-medium tracking-wide uppercase">
                    {report.purpose.label}
                </p>
                <div className="mt-1 flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1">
                    <h1 className="crm-num text-lg font-semibold tracking-tight">
                        {report.reference}
                    </h1>
                    {report.expires_at ? (
                        <p className="text-solar-ink/80 text-xs">
                            Fill in by {formatDate(report.expires_at)}
                        </p>
                    ) : null}
                </div>
                <p className="text-solar-ink/80 mt-1 text-xs leading-relaxed">
                    {report.purpose.description}
                </p>
            </div>
            <div className="grid gap-2 px-4 py-3 text-xs sm:grid-cols-2 sm:px-5">
                <p>
                    <span className="text-muted-foreground">Site: </span>
                    {report.job.site?.address ?? report.intake.summary ?? '—'}
                </p>
                <p>
                    <span className="text-muted-foreground">Customer: </span>
                    {report.job.customer ?? report.intake.customer_name ?? '—'}
                </p>
                <p>
                    <span className="text-muted-foreground">Job: </span>
                    {report.job.job_number ?? '—'}
                </p>
                <p>
                    <span className="text-muted-foreground">Crew: </span>
                    {report.job.installer ?? '—'}
                </p>
                {/* A crew that started this at the gate has no job and no
                    office behind it yet, which the person reading it should
                    know before they wonder why the file is half empty. */}
                {report.source.value === 'self_service' ? (
                    <p className="sm:col-span-2">
                        <span className="text-muted-foreground">Opened: </span>
                        On site by the crew, from the standing warranty link.
                    </p>
                ) : null}
            </div>
        </header>
    );
}

function RailLink({
    href,
    label,
    done,
}: {
    href: string;
    label: string;
    done: boolean;
}) {
    return (
        <a
            href={href}
            className={cn(
                'inline-flex shrink-0 items-center gap-1 rounded-full px-2.5 py-1 text-[11px] font-medium ring-1 transition-colors',
                done
                    ? 'bg-emerald-500/10 text-emerald-700 ring-emerald-500/25 dark:text-emerald-400'
                    : 'text-muted-foreground ring-hairline hover:bg-muted',
            )}
        >
            {done ? <Check className="size-3" /> : null}
            {label}
        </a>
    );
}

function Card({
    id,
    icon: Icon,
    title,
    description,
    badge,
    children,
}: {
    id?: string;
    icon: LucideIcon;
    title: string;
    description?: string | null;
    badge?: React.ReactNode;
    children: React.ReactNode;
}) {
    return (
        <section
            id={id}
            className="bg-card shadow-card scroll-mt-28 rounded-xl ring-1 ring-hairline"
        >
            <header className="flex items-start gap-3 px-4 pt-4 sm:px-5">
                <span className="bg-solar-soft text-solar-ink flex size-8 shrink-0 items-center justify-center rounded-lg">
                    <Icon className="size-4" />
                </span>
                <div className="min-w-0 flex-1">
                    <h2 className="text-sm font-semibold tracking-tight">{title}</h2>
                    {description ? (
                        <p className="text-muted-foreground mt-0.5 text-xs leading-relaxed">
                            {description}
                        </p>
                    ) : null}
                </div>
                {badge}
            </header>
            <div className="px-4 py-4 sm:px-5">{children}</div>
        </section>
    );
}

function Field({
    label,
    hint,
    error,
    children,
    className,
}: {
    label: string;
    hint?: string | null;
    error?: string;
    children: React.ReactNode;
    className?: string;
}) {
    return (
        <div className={cn('grid gap-1.5', className)}>
            <span className="text-xs font-medium">{label}</span>
            {children}
            {hint ? (
                <span className="text-muted-foreground text-[11px] leading-snug">
                    {hint}
                </span>
            ) : null}
            {error ? (
                <span className="text-[11px] text-red-600 dark:text-red-400">
                    {error}
                </span>
            ) : null}
        </div>
    );
}

function SystemSection({
    doc,
    hasBattery,
    systemTypes,
    onChange,
    errors,
}: {
    doc: InspectionData;
    hasBattery: boolean;
    systemTypes: PublicInspection['checklist']['system_types'];
    onChange: (mutator: (draft: InspectionData) => void) => void;
    errors: Record<string, string>;
}) {
    const system = doc.system ?? {};
    const site = doc.site ?? {};

    return (
        <Card
            id="system"
            icon={hasBattery ? BatteryCharging : Sun}
            title="The system and the site"
            description="Most of this is prefilled from the job. Correct anything that is wrong."
        >
            <div className="grid gap-4 sm:grid-cols-2">
                <Field label="System type" error={errors['system.type']}>
                    <NativeSelect
                        value={system.type ?? 'pv'}
                        onChange={(event) =>
                            onChange((draft) => {
                                draft.system = draft.system ?? {};
                                draft.system.type = event.target.value;
                            })
                        }
                    >
                        {systemTypes.map((type) => (
                            <option key={type.value} value={type.value}>
                                {type.label}
                            </option>
                        ))}
                    </NativeSelect>
                </Field>

                <Field
                    label="System size (kW)"
                    hint="As commissioned, not as quoted."
                    error={errors['system.kw']}
                >
                    <Input
                        inputMode="decimal"
                        value={system.kw ?? ''}
                        onChange={(event) =>
                            onChange((draft) => {
                                draft.system = draft.system ?? {};
                                draft.system.kw = event.target.value;
                            })
                        }
                        placeholder="6.6"
                    />
                </Field>

                <Field
                    label="Modules"
                    hint="How many, and what they are."
                    error={errors['system.modules']}
                >
                    <Input
                        value={system.modules ?? ''}
                        onChange={(event) =>
                            onChange((draft) => {
                                draft.system = draft.system ?? {};
                                draft.system.modules = event.target.value;
                            })
                        }
                        placeholder="18 × 370 W Trina Vertex S"
                    />
                </Field>

                <Field
                    label="Inverter"
                    hint="Make and model, as printed on the unit."
                    error={errors['system.inverter']}
                >
                    <Input
                        value={system.inverter ?? ''}
                        onChange={(event) =>
                            onChange((draft) => {
                                draft.system = draft.system ?? {};
                                draft.system.inverter = event.target.value;
                            })
                        }
                        placeholder="Fronius Primo 5.0-1"
                    />
                </Field>

                {hasBattery ? (
                    <Field
                        label="Battery"
                        hint="Make, model and capacity."
                        error={errors['system.battery']}
                    >
                        <Input
                            value={system.battery ?? ''}
                            onChange={(event) =>
                                onChange((draft) => {
                                    draft.system = draft.system ?? {};
                                    draft.system.battery = event.target.value;
                                })
                            }
                            placeholder="BYD HVM 11.0"
                        />
                    </Field>
                ) : null}

                <Field
                    label="Serial numbers"
                    hint="Inverter and battery, plus the module serials if they differ."
                    error={errors['system.serials']}
                >
                    <Input
                        value={system.serials ?? ''}
                        onChange={(event) =>
                            onChange((draft) => {
                                draft.system = draft.system ?? {};
                                draft.system.serials = event.target.value;
                            })
                        }
                        placeholder="Fronius 1234567890"
                    />
                </Field>

                <Field
                    label="Date installed"
                    error={errors['system.installed_on']}
                >
                    <Input
                        type="date"
                        value={system.installed_on ?? ''}
                        max={toDateInputValue(new Date().toISOString())}
                        onChange={(event) =>
                            onChange((draft) => {
                                draft.system = draft.system ?? {};
                                draft.system.installed_on = event.target.value;
                            })
                        }
                    />
                </Field>

                <Field
                    label="Monitoring"
                    hint="Platform, and whether the customer is signed in."
                    error={errors['system.monitoring']}
                >
                    <Input
                        value={system.monitoring ?? ''}
                        onChange={(event) =>
                            onChange((draft) => {
                                draft.system = draft.system ?? {};
                                draft.system.monitoring = event.target.value;
                            })
                        }
                        placeholder="Fronius Solar.web — customer signed in"
                    />
                </Field>

                <Field
                    label="Switchboard location"
                    error={errors['site.switchboard']}
                >
                    <Input
                        value={site.switchboard ?? ''}
                        onChange={(event) =>
                            onChange((draft) => {
                                draft.site = draft.site ?? {};
                                draft.site.switchboard = event.target.value;
                            })
                        }
                        placeholder="Left side of the garage"
                    />
                </Field>

                <Field label="Supply" error={errors['site.phase']}>
                    <NativeSelect
                        value={site.phase ?? 'single'}
                        onChange={(event) =>
                            onChange((draft) => {
                                draft.site = draft.site ?? {};
                                draft.site.phase = event.target.value as
                                    | 'single'
                                    | 'three';
                            })
                        }
                    >
                        <option value="single">Single phase</option>
                        <option value="three">Three phase</option>
                    </NativeSelect>
                </Field>

                <Field label="Meter number" error={errors['site.meter_number']}>
                    <Input
                        value={site.meter_number ?? ''}
                        onChange={(event) =>
                            onChange((draft) => {
                                draft.site = draft.site ?? {};
                                draft.site.meter_number = event.target.value;
                            })
                        }
                    />
                </Field>

                <Field label="NMI" error={errors['site.nmi']}>
                    <Input
                        value={site.nmi ?? ''}
                        onChange={(event) =>
                            onChange((draft) => {
                                draft.site = draft.site ?? {};
                                draft.site.nmi = event.target.value;
                            })
                        }
                    />
                </Field>

                <Field label="Network (DNSP)" error={errors['site.dnsp']}>
                    <Input
                        value={site.dnsp ?? ''}
                        onChange={(event) =>
                            onChange((draft) => {
                                draft.site = draft.site ?? {};
                                draft.site.dnsp = event.target.value;
                            })
                        }
                    />
                </Field>

                <Field label="Roof" error={errors['site.roof_type']}>
                    <Input
                        value={site.roof_type ?? ''}
                        onChange={(event) =>
                            onChange((draft) => {
                                draft.site = draft.site ?? {};
                                draft.site.roof_type = event.target.value;
                            })
                        }
                        placeholder="Colorbond, tiled, flat…"
                    />
                </Field>

                <Field
                    label="Roof condition"
                    error={errors['site.roof_condition']}
                >
                    <NativeSelect
                        value={site.roof_condition ?? ''}
                        onChange={(event) =>
                            onChange((draft) => {
                                draft.site = draft.site ?? {};
                                draft.site.roof_condition = event.target.value;
                            })
                        }
                    >
                        <option value="">Choose…</option>
                        <option value="good">Good — no issues</option>
                        <option value="fair">Fair — wear but sound</option>
                        <option value="poor">Poor — needs attention</option>
                    </NativeSelect>
                </Field>

                <Field label="Tilt (°)" error={errors['site.tilt']}>
                    <Input
                        inputMode="numeric"
                        value={site.tilt ?? ''}
                        onChange={(event) =>
                            onChange((draft) => {
                                draft.site = draft.site ?? {};
                                draft.site.tilt = event.target.value;
                            })
                        }
                        placeholder="22"
                    />
                </Field>

                <Field label="Azimuth" error={errors['site.azimuth']}>
                    <Input
                        value={site.azimuth ?? ''}
                        onChange={(event) =>
                            onChange((draft) => {
                                draft.site = draft.site ?? {};
                                draft.site.azimuth = event.target.value;
                            })
                        }
                        placeholder="North, 350°"
                    />
                </Field>

                <Field
                    label="Access notes"
                    className="sm:col-span-2"
                    error={errors['site.access_notes']}
                >
                    <textarea
                        value={site.access_notes ?? ''}
                        onChange={(event) =>
                            onChange((draft) => {
                                draft.site = draft.site ?? {};
                                draft.site.access_notes = event.target.value;
                            })
                        }
                        rows={2}
                        className="border-input focus-visible:border-ring focus-visible:ring-ring/50 min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-sm shadow-xs outline-none focus-visible:ring-[3px]"
                        placeholder="Dogs, gate codes, where to park — anything the next person needs."
                    />
                </Field>
            </div>
        </Card>
    );
}

function ChecklistSectionCard({
    section,
    answers,
    notes,
    errors,
    onChange,
    onNoteChange,
}: {
    section: InspectionChecklistSection;
    answers: Record<string, string>;
    notes: Record<string, string>;
    errors: Record<string, string>;
    onChange: (item: string, value: Answer) => void;
    onNoteChange: (item: string, value: string) => void;
}) {
    const answered = section.items.filter((item) => answers[item.key]).length;

    return (
        <Card
            id={`section-${section.key}`}
            icon={ClipboardList}
            title={section.title}
            description={`${section.intro} · ${section.standard}`}
            badge={
                <span
                    className={cn(
                        'crm-num shrink-0 rounded-full px-2 py-0.5 text-[11px] font-medium',
                        answered === section.items.length
                            ? 'bg-emerald-500/10 text-emerald-700 dark:text-emerald-400'
                            : 'bg-muted text-muted-foreground',
                    )}
                >
                    {answered}/{section.items.length}
                </span>
            }
        >
            <ul className="divide-y divide-hairline">
                {section.items.map((item) => {
                    const value = answers[item.key] as Answer | undefined;
                    const failed = value === 'fail';

                    return (
                        <li key={item.key} className="py-3 first:pt-0 last:pb-0">
                            <p className="text-[13px] leading-snug">{item.label}</p>
                            {item.hint ? (
                                <p className="text-muted-foreground mt-0.5 text-[11px] leading-snug">
                                    {item.hint}
                                </p>
                            ) : null}

                            <div className="mt-2 grid grid-cols-3 gap-1.5">
                                {ANSWER_OPTIONS.map((option) => (
                                    <button
                                        key={option.value}
                                        type="button"
                                        onClick={() => onChange(item.key, option.value)}
                                        aria-pressed={value === option.value}
                                        className={cn(
                                            'rounded-lg border py-2 text-xs font-medium transition-colors',
                                            value === option.value
                                                ? option.activeClassName
                                                : cn(
                                                      'border-hairline text-muted-foreground',
                                                      option.className,
                                                  ),
                                        )}
                                    >
                                        {option.label}
                                    </button>
                                ))}
                            </div>

                            {failed ? (
                                <textarea
                                    value={notes[item.key] ?? ''}
                                    onChange={(event) =>
                                        onNoteChange(item.key, event.target.value)
                                    }
                                    rows={2}
                                    className="border-input focus-visible:border-ring focus-visible:ring-ring/50 mt-2 min-h-14 w-full rounded-md border bg-transparent px-3 py-2 text-xs shadow-xs outline-none focus-visible:ring-[3px]"
                                    placeholder="What is wrong, exactly? This becomes the defect."
                                />
                            ) : null}

                            {errors[`answers.${section.key}.${item.key}`] ? (
                                <p className="mt-1 text-[11px] text-red-600 dark:text-red-400">
                                    {errors[`answers.${section.key}.${item.key}`]}
                                </p>
                            ) : null}
                        </li>
                    );
                })}
            </ul>
        </Card>
    );
}

function TestsSection({
    measurements,
    measurementsDefinition,
    required,
    onChange,
    errors,
}: {
    measurements: Record<string, string>;
    measurementsDefinition: PublicInspection['checklist']['measurements'];
    required: string[];
    onChange: (key: string, value: string) => void;
    errors: Record<string, string>;
}) {
    const done = required.filter((key) => measurements[key]).length;

    return (
        <Card
            id="tests"
            icon={Gauge}
            title="Test results"
            description="The readings you measured, with the instrument you measured them with. A tick is not a test record."
            badge={
                <span className="crm-num bg-muted text-muted-foreground shrink-0 rounded-full px-2 py-0.5 text-[11px] font-medium">
                    {done}/{required.length}
                </span>
            }
        >
            <div className="grid gap-3 sm:grid-cols-2">
                {Object.entries(measurementsDefinition).map(([key, definition]) => (
                    <Field
                        key={key}
                        label={`${definition.label} (${definition.unit})`}
                        hint={definition.hint}
                        error={errors[`measurements.${key}`]}
                    >
                        <Input
                            inputMode="decimal"
                            value={measurements[key] ?? ''}
                            onChange={(event) => onChange(key, event.target.value)}
                            placeholder={
                                required.includes(key) ? 'Required' : 'Optional'
                            }
                        />
                    </Field>
                ))}

                <Field
                    label="Test instrument"
                    hint="Make and model, for example Fluke 1664 FC."
                    error={errors['measurements.instrument']}
                >
                    <Input
                        value={measurements.instrument ?? ''}
                        onChange={(event) => onChange('instrument', event.target.value)}
                    />
                </Field>

                <Field
                    label="Instrument calibrated to"
                    error={errors['measurements.calibration_due']}
                >
                    <Input
                        type="date"
                        value={measurements.calibration_due ?? ''}
                        onChange={(event) =>
                            onChange('calibration_due', event.target.value)
                        }
                    />
                </Field>
            </div>
        </Card>
    );
}

function DefectsSection({
    defects,
    onChange,
    errors,
}: {
    defects: InspectionDefect[];
    onChange: (next: InspectionDefect[]) => void;
    errors: Record<string, string>;
}) {
    const add = () =>
        onChange([
            ...defects,
            { severity: 'minor', title: '', description: '', action: '', due: '' },
        ]);

    const patch = (index: number, changes: Partial<InspectionDefect>) =>
        onChange(
            defects.map((defect, current) =>
                current === index ? { ...defect, ...changes } : defect,
            ),
        );

    return (
        <Card
            id="defects"
            icon={TriangleAlert}
            title="Defects"
            description="Everything that has to be fixed, with how serious it is. A fail needs at least one major or critical defect."
            badge={
                defects.length > 0 ? (
                    <span className="crm-num shrink-0 rounded-full bg-amber-500/10 px-2 py-0.5 text-[11px] font-medium text-amber-700 dark:text-amber-400">
                        {defects.length}
                    </span>
                ) : undefined
            }
        >
            {defects.length === 0 ? (
                <p className="text-muted-foreground rounded-lg bg-muted/40 px-3 py-2.5 text-xs leading-relaxed">
                    Nothing listed yet. If everything passed, leave this empty.
                </p>
            ) : null}

            <div className="mt-3 space-y-3">
                {defects.map((defect, index) => (
                    <div
                        key={index}
                        className="ring-hairline rounded-lg p-3 ring-1"
                    >
                        <div className="flex items-center justify-between gap-2">
                            <span className="text-muted-foreground text-[11px] font-medium">
                                Defect {index + 1}
                            </span>
                            <button
                                type="button"
                                onClick={() =>
                                    onChange(
                                        defects.filter(
                                            (_, current) => current !== index,
                                        ),
                                    )
                                }
                                className="text-muted-foreground hover:text-red-600"
                                aria-label={`Remove defect ${index + 1}`}
                            >
                                <Trash2 className="size-4" />
                            </button>
                        </div>

                        <div className="mt-2 grid gap-2 sm:grid-cols-2">
                            <Field label="How serious" className="sm:col-span-2">
                                <div className="grid grid-cols-3 gap-1.5">
                                    {SEVERITIES.map((severity) => (
                                        <button
                                            key={severity.value}
                                            type="button"
                                            onClick={() =>
                                                patch(index, {
                                                    severity: severity.value,
                                                })
                                            }
                                            className={cn(
                                                'rounded-lg border py-1.5 text-xs font-medium transition-colors',
                                                defect.severity === severity.value
                                                    ? 'border-foreground bg-foreground text-background'
                                                    : 'border-hairline text-muted-foreground hover:bg-muted',
                                            )}
                                        >
                                            {severity.label}
                                        </button>
                                    ))}
                                </div>
                            </Field>

                            <Field
                                label="What is wrong"
                                className="sm:col-span-2"
                                error={errors[`defects.${index}.title`]}
                            >
                                <Input
                                    value={defect.title}
                                    onChange={(event) =>
                                        patch(index, { title: event.target.value })
                                    }
                                    placeholder="Two modules shaded by the new carport"
                                />
                            </Field>

                            <Field
                                label="Detail"
                                className="sm:col-span-2"
                                error={errors[`defects.${index}.description`]}
                            >
                                <textarea
                                    value={defect.description ?? ''}
                                    onChange={(event) =>
                                        patch(index, {
                                            description: event.target.value,
                                        })
                                    }
                                    rows={2}
                                    className="border-input focus-visible:border-ring focus-visible:ring-ring/50 min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-sm shadow-xs outline-none focus-visible:ring-[3px]"
                                />
                            </Field>

                            <Field label="Action required">
                                <Input
                                    value={defect.action ?? ''}
                                    onChange={(event) =>
                                        patch(index, { action: event.target.value })
                                    }
                                    placeholder="Relocate the two strings"
                                />
                            </Field>

                            <Field label="Fix by">
                                <Input
                                    type="date"
                                    value={defect.due ?? ''}
                                    onChange={(event) =>
                                        patch(index, { due: event.target.value })
                                    }
                                />
                            </Field>
                        </div>
                    </div>
                ))}
            </div>

            <div className="mt-3 flex items-center justify-between gap-3">
                <Button type="button" variant="outline" size="sm" onClick={add}>
                    <Plus />
                    Add a defect
                </Button>
                {errors.defects ? (
                    <p className="text-xs text-red-600 dark:text-red-400">
                        {errors.defects}
                    </p>
                ) : null}
            </div>
        </Card>
    );
}

function FilesSection({
    photos,
    documents,
    limits,
    uploading,
    onUpload,
    onRemove,
}: {
    photos: InspectionAttachment[];
    documents: InspectionAttachment[];
    limits: PublicInspection['limits'];
    uploading: number;
    onUpload: (files: FileList | null, kind: 'photo' | 'document') => void;
    onRemove: (file: InspectionAttachment) => void;
}) {
    const photoInput = useRef<HTMLInputElement>(null);
    const documentInput = useRef<HTMLInputElement>(null);

    return (
        <Card
            id="photos"
            icon={Camera}
            title="Photos and documents"
            description="At least three photos: the array, the inverter and the switchboard. Labels and any defect are worth photographing too."
            badge={
                <span
                    className={cn(
                        'crm-num shrink-0 rounded-full px-2 py-0.5 text-[11px] font-medium',
                        photos.length >= 3
                            ? 'bg-emerald-500/10 text-emerald-700 dark:text-emerald-400'
                            : 'bg-amber-500/10 text-amber-700 dark:text-amber-400',
                    )}
                >
                    {photos.length} photo{photos.length === 1 ? '' : 's'}
                </span>
            }
        >
            <input
                ref={photoInput}
                type="file"
                accept="image/*"
                multiple
                className="hidden"
                onChange={(event) => {
                    onUpload(event.target.files, 'photo');
                    event.target.value = '';
                }}
            />
            <input
                ref={documentInput}
                type="file"
                accept=".pdf,.doc,.docx,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document"
                multiple
                className="hidden"
                onChange={(event) => {
                    onUpload(event.target.files, 'document');
                    event.target.value = '';
                }}
            />

            <div className="flex flex-wrap gap-2">
                <Button
                    type="button"
                    variant="outline"
                    size="sm"
                    onClick={() => photoInput.current?.click()}
                    disabled={photos.length >= limits.photos || uploading > 0}
                >
                    <ImagePlus />
                    Add photos
                </Button>
                <Button
                    type="button"
                    variant="outline"
                    size="sm"
                    onClick={() => documentInput.current?.click()}
                    disabled={documents.length >= limits.documents || uploading > 0}
                >
                    <FileText />
                    Add a PDF or Word file
                </Button>
                {uploading > 0 ? (
                    <span className="text-muted-foreground flex items-center gap-1.5 text-xs">
                        <CloudUpload className="size-3.5 animate-pulse" />
                        Uploading {uploading}…
                    </span>
                ) : null}
            </div>
            <p className="text-muted-foreground mt-1.5 text-[11px]">
                Up to {limits.photos} photos and {limits.documents} documents, 20 MB
                each. A PDF or Word copy of your own test sheet belongs here too.
            </p>

            {photos.length > 0 ? (
                <div className="mt-3 grid grid-cols-3 gap-1.5 sm:grid-cols-4">
                    {photos.map((photo) => (
                        <div
                            key={photo.id}
                            className="group ring-hairline relative aspect-square overflow-hidden rounded-lg ring-1"
                        >
                            <a href={photo.url} target="_blank" rel="noreferrer">
                                {photo.is_image ? (
                                    <img
                                        src={photo.url}
                                        alt={photo.caption ?? photo.name}
                                        className="size-full object-cover"
                                        loading="lazy"
                                    />
                                ) : (
                                    <span className="text-muted-foreground flex size-full items-center justify-center text-[11px]">
                                        {photo.name}
                                    </span>
                                )}
                            </a>
                            <button
                                type="button"
                                onClick={() => onRemove(photo)}
                                className="absolute top-1 right-1 flex size-6 items-center justify-center rounded-full bg-black/60 text-white"
                                aria-label={`Remove ${photo.name}`}
                            >
                                <X className="size-3.5" />
                            </button>
                            {photo.caption ? (
                                <span className="absolute inset-x-0 bottom-0 truncate bg-black/55 px-1.5 py-0.5 text-[10px] text-white">
                                    {photo.caption}
                                </span>
                            ) : null}
                        </div>
                    ))}
                </div>
            ) : null}

            {documents.length > 0 ? (
                <ul className="divide-y divide-hairline mt-3">
                    {documents.map((document) => (
                        <li key={document.id} className="flex items-center gap-2 py-2">
                            <FileText className="text-muted-foreground size-4 shrink-0" />
                            <a
                                href={document.url}
                                target="_blank"
                                rel="noreferrer"
                                className="min-w-0 flex-1 truncate text-xs hover:underline"
                            >
                                {document.name}
                            </a>
                            <span className="text-muted-foreground shrink-0 text-[11px]">
                                {document.label} · {document.human_size}
                            </span>
                            <button
                                type="button"
                                onClick={() => onRemove(document)}
                                className="text-muted-foreground hover:text-red-600"
                                aria-label={`Remove ${document.name}`}
                            >
                                <Trash2 className="size-4" />
                            </button>
                        </li>
                    ))}
                </ul>
            ) : null}
        </Card>
    );
}

function FindingsSection({
    findings,
    recommendations,
    customerComment,
    outcome,
    outcomes,
    hasDefects,
    onChange,
}: {
    findings: string;
    recommendations: string;
    customerComment: string;
    outcome: string;
    outcomes: PublicInspection['checklist']['outcomes'];
    hasDefects: boolean;
    onChange: (mutator: (draft: InspectionData) => void) => void;
}) {
    return (
        <Card
            id="outcome"
            icon={MessageSquare}
            title="What you found"
            description="In your own words, and the conclusion the office works from."
        >
            <div className="space-y-4">
                <Field label="Findings">
                    <textarea
                        value={findings}
                        onChange={(event) =>
                            onChange((draft) => {
                                draft.findings = event.target.value;
                            })
                        }
                        rows={3}
                        className="border-input focus-visible:border-ring focus-visible:ring-ring/50 min-h-20 w-full rounded-md border bg-transparent px-3 py-2 text-sm shadow-xs outline-none focus-visible:ring-[3px]"
                        placeholder="What you saw beyond the checklist: the state of the roof, the wiring, anything the next person should know."
                    />
                </Field>

                <Field label="Recommendations">
                    <textarea
                        value={recommendations}
                        onChange={(event) =>
                            onChange((draft) => {
                                draft.recommendations = event.target.value;
                            })
                        }
                        rows={2}
                        className="border-input focus-visible:border-ring focus-visible:ring-ring/50 min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-sm shadow-xs outline-none focus-visible:ring-[3px]"
                        placeholder="Work to book, maintenance to suggest, a follow-up visit."
                    />
                </Field>

                <Field
                    label="Customer comments"
                    hint="Anything the customer said on site that belongs on the record."
                >
                    <textarea
                        value={customerComment}
                        onChange={(event) =>
                            onChange((draft) => {
                                draft.customer = draft.customer ?? {};
                                draft.customer.comment = event.target.value;
                            })
                        }
                        rows={2}
                        className="border-input focus-visible:border-ring focus-visible:ring-ring/50 min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-sm shadow-xs outline-none focus-visible:ring-[3px]"
                    />
                </Field>

                <div>
                    <p className="text-xs font-medium">Outcome</p>
                    <div className="mt-2 grid gap-2">
                        {outcomes.map((option) => {
                            const selected = outcome === option.value;
                            const blocked = option.value === 'pass' && hasDefects;

                            return (
                                <button
                                    key={option.value}
                                    type="button"
                                    disabled={blocked}
                                    onClick={() =>
                                        onChange((draft) => {
                                            draft.outcome = option.value;
                                        })
                                    }
                                    className={cn(
                                        'flex items-start gap-3 rounded-lg px-3 py-2.5 text-left ring-1 transition-colors',
                                        selected
                                            ? 'bg-solar-soft/70 ring-solar/40'
                                            : 'ring-hairline hover:bg-muted/60',
                                        blocked && 'cursor-not-allowed opacity-50',
                                    )}
                                >
                                    <span
                                        className={cn(
                                            'mt-0.5 flex size-4 shrink-0 items-center justify-center rounded-full ring-1',
                                            selected
                                                ? 'bg-solar text-solar-ink ring-solar'
                                                : 'ring-hairline',
                                        )}
                                    >
                                        {selected ? <Check className="size-3" /> : null}
                                    </span>
                                    <span>
                                        <span className="block text-sm font-medium">
                                            {option.label}
                                        </span>
                                        <span className="text-muted-foreground mt-0.5 block text-[11px] leading-relaxed">
                                            {option.description}
                                        </span>
                                    </span>
                                </button>
                            );
                        })}
                    </div>
                </div>
            </div>
        </Card>
    );
}

function DeclarationSection({
    declaration,
    hasBattery,
    signature,
    signatureSaved,
    errors,
    onChange,
    onSignatureUpload,
}: {
    declaration: NonNullable<InspectionData['declaration']>;
    hasBattery: boolean;
    signature: InspectionAttachment | undefined;
    signatureSaved: boolean;
    errors: Record<string, string>;
    onChange: (mutator: (draft: InspectionData) => void) => void;
    onSignatureUpload: (file: File) => void;
}) {
    return (
        <Card
            id="declaration"
            icon={ShieldCheck}
            title="Declaration"
            description="Signed by the person who did the inspection. This is what makes the report a record."
            badge={
                signature || signatureSaved ? (
                    <span className="shrink-0 rounded-full bg-emerald-500/10 px-2 py-0.5 text-[11px] font-medium text-emerald-700 dark:text-emerald-400">
                        Signed
                    </span>
                ) : undefined
            }
        >
            <div className="grid gap-4 sm:grid-cols-2">
                <Field label="Your name" error={errors['declaration.name']}>
                    <Input
                        value={declaration.name ?? ''}
                        onChange={(event) =>
                            onChange((draft) => {
                                draft.declaration = draft.declaration ?? {};
                                draft.declaration.name = event.target.value;
                            })
                        }
                        placeholder="Who inspected the system"
                    />
                </Field>

                <Field label="Company" error={errors['declaration.company']}>
                    <Input
                        value={declaration.company ?? ''}
                        onChange={(event) =>
                            onChange((draft) => {
                                draft.declaration = draft.declaration ?? {};
                                draft.declaration.company = event.target.value;
                            })
                        }
                    />
                </Field>

                <Field
                    label="Electrical licence number"
                    error={errors['declaration.licence']}
                >
                    <Input
                        value={declaration.licence ?? ''}
                        onChange={(event) =>
                            onChange((draft) => {
                                draft.declaration = draft.declaration ?? {};
                                draft.declaration.licence = event.target.value;
                            })
                        }
                        placeholder="REC / EC number"
                    />
                </Field>

                <Field label="Date of inspection" error={errors['declaration.date']}>
                    <Input
                        type="date"
                        value={declaration.date ?? ''}
                        max={toDateInputValue(new Date().toISOString())}
                        onChange={(event) =>
                            onChange((draft) => {
                                draft.declaration = draft.declaration ?? {};
                                draft.declaration.date = event.target.value;
                            })
                        }
                    />
                </Field>
            </div>

            <div className="mt-4">
                <p className="text-xs font-medium">Signature</p>
                <p className="text-muted-foreground mt-0.5 text-[11px] leading-relaxed">
                    {hasBattery
                        ? 'Draw your signature below. By signing you confirm the battery installation has been checked against AS/NZS 5139.'
                        : 'Draw your signature below with your finger or the mouse.'}
                </p>

                <SignaturePad
                    onSave={onSignatureUpload}
                    saved={Boolean(signature)}
                />

                {errors.signature ? (
                    <p className="mt-1 text-[11px] text-red-600 dark:text-red-400">
                        {errors.signature}
                    </p>
                ) : null}
            </div>

            <label className="ring-hairline mt-4 flex cursor-pointer items-start gap-3 rounded-lg px-3 py-2.5 ring-1">
                <input
                    type="checkbox"
                    checked={Boolean(declaration.agreed)}
                    onChange={(event) =>
                        onChange((draft) => {
                            draft.declaration = draft.declaration ?? {};
                            draft.declaration.agreed = event.target.checked;
                        })
                    }
                    className="accent-solar mt-0.5 size-4"
                />
                <span className="text-[12px] leading-relaxed">
                    I certify that I carried out this inspection, that the results
                    recorded are the readings I measured on site, and that the work
                    complies with AS/NZS 5033, AS/NZS 4777.2, AS/NZS 5139 and
                    AS/NZS 3000 except where a defect is listed.
                </span>
            </label>

            {signature ? (
                <div className="mt-3 flex items-center gap-3">
                    <img
                        src={signature.url}
                        alt="Signature on file"
                        className="ring-hairline max-h-16 rounded-md bg-white p-1 ring-1"
                    />
                    <span className="text-muted-foreground text-[11px]">
                        Signature on file. Draw again and save to replace it.
                    </span>
                </div>
            ) : null}
        </Card>
    );
}

/**
 * A signature drawn on the screen and uploaded as a PNG.
 *
 * Canvas rather than a signature font: what a crew signs needs to be their
 * hand, and on a phone that means a finger on glass. The stroke is scaled by the
 * device pixel ratio so it does not come out blurry on a modern phone.
 */
function SignaturePad({
    onSave,
    saved,
}: {
    onSave: (file: File) => void;
    saved: boolean;
}) {
    const canvasRef = useRef<HTMLCanvasElement>(null);
    const drawing = useRef(false);
    const [hasInk, setHasInk] = useState(false);

    const resize = useCallback(() => {
        const canvas = canvasRef.current;

        if (canvas === null) {
            return;
        }

        const ratio = window.devicePixelRatio || 1;
        const rect = canvas.getBoundingClientRect();
        const snapshot = canvas.toDataURL();

        canvas.width = Math.max(Math.round(rect.width * ratio), 1);
        canvas.height = Math.max(Math.round(rect.height * ratio), 1);

        const context = canvas.getContext('2d');

        if (context === null) {
            return;
        }

        context.scale(ratio, ratio);
        context.lineWidth = 2;
        context.lineCap = 'round';
        context.lineJoin = 'round';
        context.strokeStyle = '#111827';

        if (snapshot !== 'data:,') {
            const image = new Image();
            image.onload = () => context.drawImage(image, 0, 0, rect.width, rect.height);
            image.src = snapshot;
        }
    }, []);

    useEffect(() => {
        resize();
        window.addEventListener('resize', resize);

        return () => window.removeEventListener('resize', resize);
    }, [resize]);

    const point = (event: React.PointerEvent<HTMLCanvasElement>) => {
        const canvas = canvasRef.current;
        const rect = canvas?.getBoundingClientRect();

        if (canvas === null || rect === undefined) {
            return null;
        }

        return { x: event.clientX - rect.left, y: event.clientY - rect.top };
    };

    const start = (event: React.PointerEvent<HTMLCanvasElement>) => {
        const canvas = canvasRef.current;
        const position = point(event);

        if (canvas === null || position === null) {
            return;
        }

        canvas.setPointerCapture(event.pointerId);
        drawing.current = true;

        const context = canvas.getContext('2d');

        if (context === null) {
            return;
        }

        context.beginPath();
        context.moveTo(position.x, position.y);
        setHasInk(true);
    };

    const move = (event: React.PointerEvent<HTMLCanvasElement>) => {
        if (!drawing.current) {
            return;
        }

        const context = canvasRef.current?.getContext('2d');
        const position = point(event);

        if (context === null || context === undefined || position === null) {
            return;
        }

        context.lineTo(position.x, position.y);
        context.stroke();
    };

    const stop = () => {
        drawing.current = false;
    };

    const clear = () => {
        const canvas = canvasRef.current;
        const context = canvas?.getContext('2d');

        if (canvas === null || context === undefined || context === null) {
            return;
        }

        context.clearRect(0, 0, canvas.width, canvas.height);
        setHasInk(false);
    };

    const save = () => {
        const canvas = canvasRef.current;

        if (canvas === null || !hasInk) {
            return;
        }

        canvas.toBlob((blob) => {
            if (blob === null) {
                return;
            }

            onSave(
                new File([blob], `signature-${Date.now()}.png`, {
                    type: 'image/png',
                }),
            );
        }, 'image/png');
    };

    return (
        <div className="mt-2">
            <div className="ring-hairline relative overflow-hidden rounded-lg bg-white ring-1">
                <canvas
                    ref={canvasRef}
                    onPointerDown={start}
                    onPointerMove={move}
                    onPointerUp={stop}
                    onPointerLeave={stop}
                    className="h-36 w-full touch-none"
                    style={{ touchAction: 'none' }}
                />
                {!hasInk ? (
                    <span className="text-muted-foreground/60 pointer-events-none absolute inset-0 flex items-center justify-center gap-2 text-xs">
                        <PenLine className="size-4" />
                        Sign here
                    </span>
                ) : null}
            </div>
            <div className="mt-2 flex flex-wrap items-center gap-2">
                <Button
                    type="button"
                    size="sm"
                    variant="outline"
                    onClick={save}
                    disabled={!hasInk}
                >
                    <Check />
                    {saved ? 'Replace signature' : 'Save signature'}
                </Button>
                <Button type="button" size="sm" variant="ghost" onClick={clear}>
                    <RotateCcw />
                    Clear
                </Button>
            </div>
        </div>
    );
}

/**
 * What the crew sees once the report is in — or once the link has run out.
 *
 * It is deliberately final: a submitted report is evidence, so it cannot be
 * edited here. Anything that has to change is a new report from the office.
 */
function SubmittedReport({ report }: { report: PublicInspection }) {
    const submitted = Boolean(report.submitted_at);
    const defects = report.data?.defects ?? [];
    const outcome = report.data?.outcome
        ? report.checklist.outcomes.find(
              (option) => option.value === report.data?.outcome,
          )
        : null;
    const photos = report.attachments.filter((file) => file.kind === 'photo');

    return (
        <>
            <Head title={`${report.reference} — inspection report`}>
                <meta name="robots" content="noindex,nofollow" />
            </Head>

            <div className="mx-auto flex max-w-2xl flex-col gap-4">
                <section className="bg-card shadow-card overflow-hidden rounded-xl ring-1 ring-hairline">
                    <div
                        className={cn(
                            'px-5 py-5',
                            submitted
                                ? 'bg-emerald-500/10'
                                : 'bg-muted/60',
                        )}
                    >
                        <p className="text-muted-foreground text-[11px] font-medium tracking-wide uppercase">
                            {report.reference}
                        </p>
                        <h1 className="mt-1 flex items-center gap-2 text-xl font-semibold tracking-tight">
                            {submitted ? (
                                <CheckCircle2 className="size-5 text-emerald-600 dark:text-emerald-400" />
                            ) : (
                                <Lock className="size-5 text-muted-foreground" />
                            )}
                            {submitted ? 'Report submitted' : 'This link has closed'}
                        </h1>
                        <p className="text-muted-foreground mt-1 text-xs leading-relaxed">
                            {submitted
                                ? `Received ${formatDateTime(report.submitted_at)}. The office has it and will be in touch if anything else is needed.`
                                : 'It ran out before the report was finished. The office can send a new link — nothing already typed has been lost.'}
                        </p>
                    </div>

                    {submitted ? (
                        <div className="space-y-4 px-5 py-4">
                            {outcome ? (
                                <div className="ring-hairline rounded-lg px-3 py-2.5 ring-1">
                                    <p className="text-muted-foreground text-[11px]">
                                        Outcome
                                    </p>
                                    <p className="mt-0.5 text-sm font-semibold">
                                        {outcome.label}
                                    </p>
                                    <p className="text-muted-foreground mt-0.5 text-[11px] leading-relaxed">
                                        {outcome.description}
                                    </p>
                                </div>
                            ) : null}

                            <div className="grid gap-2 text-xs sm:grid-cols-2">
                                <p>
                                    <span className="text-muted-foreground">
                                        Site:{' '}
                                    </span>
                                    {report.job.site?.address ?? '—'}
                                </p>
                                <p>
                                    <span className="text-muted-foreground">
                                        Customer:{' '}
                                    </span>
                                    {report.job.customer ?? '—'}
                                </p>
                                <p>
                                    <span className="text-muted-foreground">
                                        Inspector:{' '}
                                    </span>
                                    {report.data?.declaration?.name ?? '—'}
                                </p>
                                <p>
                                    <span className="text-muted-foreground">
                                        Photos:{' '}
                                    </span>
                                    {photos.length}
                                </p>
                            </div>

                            {defects.length > 0 ? (
                                <div>
                                    <p className="text-xs font-semibold">
                                        Defects recorded
                                    </p>
                                    <ul className="mt-1.5 space-y-1.5">
                                        {defects.map((defect, index) => (
                                            <li
                                                key={index}
                                                className="flex items-start gap-2 text-xs leading-relaxed"
                                            >
                                                <AlertTriangle className="mt-0.5 size-3.5 shrink-0 text-amber-600 dark:text-amber-400" />
                                                <span>
                                                    <span className="text-muted-foreground mr-1 uppercase">
                                                        {defect.severity}
                                                    </span>
                                                    {defect.title}
                                                </span>
                                            </li>
                                        ))}
                                    </ul>
                                </div>
                            ) : (
                                <p className="text-muted-foreground flex items-center gap-2 text-xs">
                                    <CheckCircle2 className="size-3.5 text-emerald-600 dark:text-emerald-400" />
                                    No defects were recorded.
                                </p>
                            )}

                            <p className="text-muted-foreground border-t border-hairline pt-3 text-[11px] leading-relaxed">
                                Need a copy of the report, or something is wrong on
                                it? Contact the office quoting {report.reference}.
                                A submitted report cannot be edited, so a
                                correction is a new report.
                            </p>
                        </div>
                    ) : null}
                </section>

                <p className="text-muted-foreground flex items-center justify-center gap-1.5 text-xs">
                    <Info className="size-3.5" />
                    This page is the crew's link, not the customer's. Customers
                    use the ticket portal.
                </p>
            </div>
        </>
    );
}
