import * as D from '@radix-ui/react-dialog';
import { router, usePage } from '@inertiajs/react';
import { CheckCircle2, Download, Eraser, FileSignature, HelpCircle, MessageCircle, Paperclip, Printer, ShieldCheck, X, XCircle } from 'lucide-react';
import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from 'react';
import type { Palette } from '@/components/proposal/palette';
import { SHADOW } from '@/components/proposal/elements';
import { dateTime } from '@/lib/format';
import type { SharedProps } from '@/types';

export type ClientMode = {
    render?: boolean;
    base: string;
    recipient: { name: string | null; email: string };
    status: string;
    blocked: string | null;
    expired: boolean;
    acceptance: { mode: 'checkbox' | 'statement'; signature: 'typed' | 'drawn' | 'none'; statement: string };
    decision: { decision: 'accepted' | 'declined'; signer_name: string | null; at: string; po_number: string | null; reason: string | null } | null;
    messages: { id: number; type: 'question' | 'answer' | 'comment'; parent_id: number | null; body: string; author: string; from_staff: boolean; at: string; files: { name: string; size: number; url: string }[] }[];
    staff: boolean;
    track: boolean;
    maxFileMb: number;
};

/* ---------------------------------------------------------------- tracking */

/**
 * Tracking beacon: view_start after 3 s on screen, then a heartbeat every 15 s while the tab is
 * visible, carrying seconds spent on each section (a section counts once it's ≥ 50% visible for 2 s).
 */
export function useProposalTracking(base: string | null, enabled: boolean) {
    useEffect(() => {
        if (!base || !enabled) return;
        const visit = (crypto as Crypto & { randomUUID?: () => string }).randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
        const url = `${base}/events`;
        let started = false;
        let secs = 0;
        let lastSent = 0;
        const sectionSecs: Record<string, number> = {};
        const onScreen = new Map<Element, string>();

        const send = (event: string, extra: Record<string, unknown> = {}, beacon = false) => {
            const body = JSON.stringify({ visit_id: visit, event, ...extra });
            if (beacon && navigator.sendBeacon) {
                navigator.sendBeacon(url, new Blob([body], { type: 'application/json' }));
                return;
            }
            fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', Accept: 'application/json' }, body, keepalive: true, credentials: 'same-origin' }).catch(() => {});
        };
        const flush = (beacon = false) => {
            if (!started) return;
            const s = secs - lastSent;
            lastSent = secs;
            const sections: Record<string, number> = {};
            for (const [k, v] of Object.entries(sectionSecs)) { if (v >= 2) { sections[k] = v; delete sectionSecs[k]; } }
            if (s <= 0 && Object.keys(sections).length === 0) return;
            send('heartbeat', { seconds: s, sections }, beacon);
        };
        const tick = window.setInterval(() => {
            if (document.visibilityState !== 'visible') return;
            secs += 1;
            new Set(onScreen.values()).forEach((k) => { sectionSecs[k] = (sectionSecs[k] ?? 0) + 1; });
            if (!started && secs >= 3) { started = true; lastSent = secs; send('view_start'); }
            else if (started && secs - lastSent >= 15) flush();
        }, 1000);

        const io = new IntersectionObserver((entries) => {
            for (const e of entries) {
                const key = (e.target as HTMLElement).dataset.sk;
                if (!key) continue;
                const half = e.intersectionRatio >= 0.5 || e.intersectionRect.height >= window.innerHeight * 0.5;
                if (e.isIntersecting && half) onScreen.set(e.target, key); else onScreen.delete(e.target);
            }
        }, { threshold: [0, 0.25, 0.5, 0.75, 1] });
        const observe = () => document.querySelectorAll('[data-sk]').forEach((el) => io.observe(el));
        observe();
        const again = window.setTimeout(observe, 1500);

        const hide = () => flush(true);
        const vis = () => { if (document.visibilityState === 'hidden') flush(true); };
        window.addEventListener('pagehide', hide);
        document.addEventListener('visibilitychange', vis);
        return () => {
            window.clearInterval(tick); window.clearTimeout(again); io.disconnect();
            window.removeEventListener('pagehide', hide); document.removeEventListener('visibilitychange', vis);
        };
    }, [base, enabled]);
}

/* ---------------------------------------------------------------- shell */

function Modal({ p, hf, open, onOpenChange, title, description, children, testId }: {
    p: Palette; hf: CSSProperties; open: boolean; onOpenChange: (o: boolean) => void; title: string; description?: string; children: ReactNode; testId?: string;
}) {
    return (
        <D.Root open={open} onOpenChange={onOpenChange}>
            <D.Portal>
                <D.Overlay className="motion-overlay fixed inset-0 z-40 bg-black/45" />
                <D.Content data-testid={testId} className="motion-dialog fixed left-1/2 top-1/2 z-50 max-h-[92vh] w-[calc(100%-1.5rem)] max-w-lg -translate-x-1/2 -translate-y-1/2 overflow-y-auto rounded-[18px] p-6 sm:p-7"
                    style={{ background: p.card, color: p.text, boxShadow: SHADOW }}>
                    <D.Title className="text-[20px] font-bold" style={hf}>{title}</D.Title>
                    {description ? <D.Description className="mt-1 text-[13px]" style={{ color: p.muted }}>{description}</D.Description> : <D.Description className="sr-only">{title}</D.Description>}
                    <div className="mt-5">{children}</div>
                    <D.Close className="absolute right-4 top-4 rounded-full p-1.5 hover:bg-black/5" aria-label="Close"><X className="h-4 w-4" /></D.Close>
                </D.Content>
            </D.Portal>
        </D.Root>
    );
}

const inputCls = 'w-full rounded-[10px] border px-3 py-2.5 text-[14px] outline-none focus:ring-2';
const fieldStyle = (p: Palette): CSSProperties => ({ borderColor: p.cardLine, background: p.card, color: p.text, '--tw-ring-color': p.accent } as CSSProperties);

function Label({ p, htmlFor, children, hint }: { p: Palette; htmlFor?: string; children: ReactNode; hint?: string }) {
    return (
        <label htmlFor={htmlFor} className="mb-1.5 block text-[12.5px] font-semibold">
            {children}{hint && <span className="ml-1 font-normal" style={{ color: p.muted }}>{hint}</span>}
        </label>
    );
}

function Err({ msg }: { msg?: string }) {
    return msg ? <p className="mt-1.5 text-[12.5px] font-medium text-red-700" role="alert">{msg}</p> : null;
}

function PrimaryButton({ p, children, ...rest }: { p: Palette; children: ReactNode } & React.ButtonHTMLAttributes<HTMLButtonElement>) {
    return (
        <button {...rest} className={`inline-flex items-center justify-center gap-2 rounded-full px-6 py-2.5 text-[13.5px] font-bold disabled:opacity-60 ${rest.className ?? ''}`}
            style={{ background: p.grad, color: p.onAccent, boxShadow: `0 8px 20px -10px ${p.accent}`, ...rest.style }}>{children}</button>
    );
}

function GhostButton({ p, children, ...rest }: { p: Palette; children: ReactNode } & React.ButtonHTMLAttributes<HTMLButtonElement>) {
    return (
        <button {...rest} className={`inline-flex items-center justify-center gap-2 rounded-full border px-5 py-2.5 text-[13.5px] font-semibold hover:bg-black/[0.03] ${rest.className ?? ''}`}
            style={{ borderColor: p.cardLine, color: p.text, background: p.card, ...rest.style }}>{children}</button>
    );
}

/* ---------------------------------------------------------------- banners + bar */

export function ClientBanner({ p, hf, mode, label }: { p: Palette; hf: CSSProperties; mode: ClientMode; label: string }) {
    const { flash } = usePage<SharedProps>().props;
    const d = mode.decision;
    const items: { tone: 'good' | 'bad' | 'info'; icon: ReactNode; text: ReactNode; id: string }[] = [];
    if (flash?.success) items.push({ id: 'flash', tone: 'good', icon: <CheckCircle2 className="h-4 w-4" />, text: flash.success });
    if (flash?.error) items.push({ id: 'flash-e', tone: 'bad', icon: <XCircle className="h-4 w-4" />, text: flash.error });
    if (d?.decision === 'accepted') items.push({ id: 'acc', tone: 'good', icon: <FileSignature className="h-4 w-4" />, text: <>Accepted by <b>{d.signer_name}</b> on {dateTime(d.at)}{d.po_number ? ` · PO ${d.po_number}` : ''}.</> });
    else if (d?.decision === 'declined') items.push({ id: 'dec', tone: 'bad', icon: <XCircle className="h-4 w-4" />, text: <>This {label.toLowerCase()} was declined on {dateTime(d.at)}.</> });
    else if (mode.blocked) items.push({ id: 'blk', tone: 'info', icon: <HelpCircle className="h-4 w-4" />, text: mode.blocked });
    if (mode.staff) items.push({ id: 'staff', tone: 'info', icon: <ShieldCheck className="h-4 w-4" />, text: 'You\'re viewing as a team member: this visit isn\'t counted in the client\'s activity.' });
    if (!items.length) return null;
    const tones = { good: { background: '#ecfdf3', color: '#065f46', borderColor: '#a7f3d0' }, bad: { background: '#fef2f2', color: '#991b1b', borderColor: '#fecaca' }, info: { background: p.tint, color: p.text, borderColor: p.cardLine } };
    return (
        <div className="mx-auto max-w-[1080px] space-y-2 px-4 pt-4 sm:px-8 print:hidden" style={hf}>
            {items.map((i) => (
                <div key={i.id} role="status" data-testid={`client-banner-${i.id}`} className="flex items-start gap-2.5 rounded-[12px] border px-4 py-3 text-[13px]" style={tones[i.tone]}>
                    <span className="mt-0.5 shrink-0">{i.icon}</span><span>{i.text}</span>
                </div>
            ))}
        </div>
    );
}

/** Sticky action bar at the bottom of the client page. */
export function ClientBar({ p, hf, mode, label, total, onAccept, onDecline, pdfUrl, paper, onPrint }: {
    p: Palette; hf: CSSProperties; mode: ClientMode; label: string; total: string | null; onAccept: () => void; onDecline: () => void;
    pdfUrl: string | null; paper: string; onPrint: () => void;
}) {
    const open = !mode.blocked;
    return (
        <div className="sticky bottom-0 z-30 border-t print:hidden" style={{ background: `${p.card}f2`, borderColor: p.cardLine, backdropFilter: 'blur(8px)' }} data-testid="client-bar">
            <div className="mx-auto flex max-w-[1080px] flex-wrap items-center gap-x-4 gap-y-2 px-4 py-3 sm:px-8">
                {total && (
                    <div className="mr-auto leading-tight">
                        <div className="text-[11px]" style={{ color: p.muted }}>{open ? 'Your total' : 'Total'}</div>
                        <div className="text-[18px] font-bold tabular-nums" style={hf} data-testid="client-total">{total}</div>
                    </div>
                )}
                <div className={`flex flex-wrap items-center gap-2 ${total ? '' : 'ml-auto'}`}>
                    <a href="#questions" className="inline-flex items-center gap-1.5 rounded-full px-3 py-2 text-[13px] font-semibold hover:bg-black/[0.04]" style={{ color: p.text }}>
                        <MessageCircle className="h-4 w-4" aria-hidden /> <span className="hidden sm:inline">Ask a question</span><span className="sm:hidden">Ask</span>
                    </a>
                    {pdfUrl && <a href={`${pdfUrl}?paper=${paper}`} className="inline-flex items-center gap-1.5 rounded-full px-3 py-2 text-[13px] font-semibold hover:bg-black/[0.04]" style={{ color: p.text }} data-testid="client-pdf"><Download className="h-4 w-4" aria-hidden /> PDF</a>}
                    <button type="button" onClick={onPrint} className="hidden items-center gap-1.5 rounded-full px-3 py-2 text-[13px] font-semibold hover:bg-black/[0.04] sm:inline-flex" style={{ color: p.text }}><Printer className="h-4 w-4" aria-hidden /> Print</button>
                    {open && <GhostButton p={p} onClick={onDecline} data-testid="decline-open">Decline</GhostButton>}
                    {open && <PrimaryButton p={p} onClick={onAccept} data-testid="accept-bar">Accept {label.toLowerCase()}</PrimaryButton>}
                </div>
            </div>
        </div>
    );
}

/* ---------------------------------------------------------------- accept */

function SignaturePad({ p, onChange }: { p: Palette; onChange: (dataUrl: string | null) => void }) {
    const ref = useRef<HTMLCanvasElement>(null);
    const drawing = useRef(false);
    const drawn = useRef(false);
    useEffect(() => {
        const c = ref.current!;
        const ratio = window.devicePixelRatio || 1;
        c.width = c.offsetWidth * ratio;
        c.height = c.offsetHeight * ratio;
        const ctx = c.getContext('2d')!;
        ctx.scale(ratio, ratio);
        ctx.lineWidth = 2.2; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.strokeStyle = '#111827';
    }, []);
    const pos = (e: React.PointerEvent) => { const r = ref.current!.getBoundingClientRect(); return [e.clientX - r.left, e.clientY - r.top]; };
    const down = (e: React.PointerEvent) => { drawing.current = true; ref.current!.setPointerCapture(e.pointerId); const ctx = ref.current!.getContext('2d')!; const [x, y] = pos(e); ctx.beginPath(); ctx.moveTo(x, y); };
    const move = (e: React.PointerEvent) => { if (!drawing.current) return; const ctx = ref.current!.getContext('2d')!; const [x, y] = pos(e); ctx.lineTo(x, y); ctx.stroke(); drawn.current = true; };
    const up = () => { if (!drawing.current) return; drawing.current = false; if (drawn.current) onChange(ref.current!.toDataURL('image/png')); };
    const clear = () => { const c = ref.current!; c.getContext('2d')!.clearRect(0, 0, c.width, c.height); drawn.current = false; onChange(null); };
    return (
        <div>
            <canvas ref={ref} className="h-36 w-full touch-none rounded-[10px] border" style={{ borderColor: p.cardLine, background: '#fff', cursor: 'crosshair' }}
                onPointerDown={down} onPointerMove={move} onPointerUp={up} onPointerLeave={up} aria-label="Signature pad: draw your signature" data-testid="signature-pad" />
            <div className="mt-1.5 flex justify-between text-[12px]" style={{ color: p.muted }}>
                <span>Sign with your mouse, finger or stylus.</span>
                <button type="button" onClick={clear} className="inline-flex items-center gap-1 font-semibold"><Eraser className="h-3.5 w-3.5" /> Clear</button>
            </div>
        </div>
    );
}

export type Selection = { id: number; selected: boolean; qty: string };

export function AcceptDialog({ p, hf, open, onOpenChange, mode, label, total, selections, expectedTotal, summary }: {
    p: Palette; hf: CSSProperties; open: boolean; onOpenChange: (o: boolean) => void; mode: ClientMode; label: string;
    total: string | null; selections: Selection[]; expectedTotal: number | null; summary: string[];
}) {
    const a = mode.acceptance;
    const [name, setName] = useState(mode.recipient.name ?? '');
    const [typed, setTyped] = useState('');
    const [drawn, setDrawn] = useState<string | null>(null);
    const [ticked, setTicked] = useState(false);
    const [po, setPo] = useState('');
    const [comments, setComments] = useState('');
    const [errors, setErrors] = useState<Record<string, string>>({});
    const [busy, setBusy] = useState(false);

    const submit = (e: React.FormEvent) => {
        e.preventDefault();
        setBusy(true);
        router.post(`${mode.base}/accept`, {
            lines: selections, signer_name: name, signature: a.signature === 'drawn' ? drawn : a.signature === 'typed' ? typed : null,
            statement_ticked: a.mode === 'checkbox' ? ticked : true, po_number: po || null, comments: comments || null, expected_total_cents: expectedTotal,
        }, {
            preserveScroll: true,
            onError: (errs) => setErrors(errs as Record<string, string>),
            onSuccess: () => { setErrors({}); onOpenChange(false); window.scrollTo({ top: 0, behavior: 'smooth' }); },
            onFinish: () => setBusy(false),
        });
    };

    return (
        <Modal p={p} hf={hf} open={open} onOpenChange={onOpenChange} title={`Accept ${label.toLowerCase()}`} testId="accept-dialog"
            description="Your acceptance is recorded with the date, time and your email address.">
            <form onSubmit={submit} className="space-y-4" noValidate>
                {total && (
                    <div className="rounded-[12px] px-4 py-3" style={{ background: p.tint }}>
                        <div className="flex items-baseline justify-between gap-3"><span className="text-[13px] font-semibold">Total</span><span className="text-[20px] font-bold tabular-nums" style={hf}>{total}</span></div>
                        {summary.length > 0 && <ul className="mt-1.5 space-y-0.5 text-[12px]" style={{ color: p.muted }}>{summary.map((s) => <li key={s}>{s}</li>)}</ul>}
                    </div>
                )}
                {(errors.quote || errors.lines) && <p className="rounded-[10px] bg-red-50 px-3 py-2 text-[13px] font-medium text-red-800" role="alert">{errors.quote ?? errors.lines}</p>}

                <div>
                    <Label p={p} htmlFor="signer_name">Your full name</Label>
                    <input id="signer_name" className={inputCls} style={fieldStyle(p)} value={name} onChange={(e) => setName(e.target.value)} autoComplete="name" />
                    <Err msg={errors.signer_name} />
                </div>

                {a.signature === 'typed' && (
                    <div>
                        <Label p={p} htmlFor="signature_typed" hint="(type your name)">Signature</Label>
                        <input id="signature_typed" className={inputCls} style={fieldStyle(p)} value={typed} onChange={(e) => setTyped(e.target.value)} autoComplete="off" />
                        {typed && <div className="mt-2 rounded-[10px] border px-4 py-2 text-[28px] leading-tight" style={{ borderColor: p.cardLine, fontFamily: "'Segoe Script', 'Brush Script MT', 'Snell Roundhand', cursive" }} aria-hidden>{typed}</div>}
                        <Err msg={errors.signature} />
                    </div>
                )}
                {a.signature === 'drawn' && (
                    <div>
                        <Label p={p}>Signature</Label>
                        <SignaturePad p={p} onChange={setDrawn} />
                        <Err msg={errors.signature} />
                    </div>
                )}

                <div className="grid gap-4 sm:grid-cols-2">
                    <div>
                        <Label p={p} htmlFor="po_number" hint="(optional)">PO number</Label>
                        <input id="po_number" className={inputCls} style={fieldStyle(p)} value={po} onChange={(e) => setPo(e.target.value)} />
                    </div>
                    <div className="sm:col-span-2">
                        <Label p={p} htmlFor="accept_comments" hint="(optional)">Comments</Label>
                        <textarea id="accept_comments" rows={2} className={inputCls} style={fieldStyle(p)} value={comments} onChange={(e) => setComments(e.target.value)} />
                    </div>
                </div>

                {a.mode === 'checkbox' ? (
                    <label className="flex items-start gap-2.5 text-[13px] leading-[1.55]">
                        <input type="checkbox" checked={ticked} onChange={(e) => setTicked(e.target.checked)} className="mt-0.5 h-4 w-4 shrink-0" style={{ accentColor: p.button }} data-testid="accept-statement" />
                        <span>{a.statement}</span>
                    </label>
                ) : <p className="text-[13px] leading-[1.55]" style={{ color: p.muted }}>By accepting, you agree: {a.statement}</p>}
                <Err msg={errors.statement_ticked} />

                <div className="flex flex-wrap justify-end gap-2 pt-1">
                    <GhostButton p={p} type="button" onClick={() => onOpenChange(false)}>Cancel</GhostButton>
                    <PrimaryButton p={p} type="submit" disabled={busy} data-testid="accept-submit"><FileSignature className="h-4 w-4" /> {busy ? 'Accepting…' : `Accept ${label.toLowerCase()}`}</PrimaryButton>
                </div>
            </form>
        </Modal>
    );
}

export function DeclineDialog({ p, hf, open, onOpenChange, mode, label }: { p: Palette; hf: CSSProperties; open: boolean; onOpenChange: (o: boolean) => void; mode: ClientMode; label: string }) {
    const [reason, setReason] = useState('');
    const [errors, setErrors] = useState<Record<string, string>>({});
    const [busy, setBusy] = useState(false);
    return (
        <Modal p={p} hf={hf} open={open} onOpenChange={onOpenChange} title={`Decline ${label.toLowerCase()}`} description="Please let us know why. It helps us improve." testId="decline-dialog">
            <form className="space-y-4" noValidate onSubmit={(e) => {
                e.preventDefault(); setBusy(true);
                router.post(`${mode.base}/decline`, { reason }, { preserveScroll: true, onError: (x) => setErrors(x as Record<string, string>), onSuccess: () => onOpenChange(false), onFinish: () => setBusy(false) });
            }}>
                {errors.quote && <p className="rounded-[10px] bg-red-50 px-3 py-2 text-[13px] text-red-800" role="alert">{errors.quote}</p>}
                <div>
                    <Label p={p} htmlFor="decline_reason">Reason</Label>
                    <textarea id="decline_reason" rows={4} className={inputCls} style={fieldStyle(p)} value={reason} onChange={(e) => setReason(e.target.value)} />
                    <Err msg={errors.reason} />
                </div>
                <div className="flex justify-end gap-2">
                    <GhostButton p={p} type="button" onClick={() => onOpenChange(false)}>Cancel</GhostButton>
                    <PrimaryButton p={p} type="submit" disabled={busy} data-testid="decline-submit">{busy ? 'Sending…' : 'Decline'}</PrimaryButton>
                </div>
            </form>
        </Modal>
    );
}

/* ---------------------------------------------------------------- questions */

const kb = (n: number) => (n > 1024 * 1024 ? `${(n / 1024 / 1024).toFixed(1)} MB` : `${Math.max(1, Math.round(n / 1024))} KB`);

export function QuestionsSection({ p, hf, mode }: { p: Palette; hf: CSSProperties; mode: ClientMode }) {
    const [body, setBody] = useState('');
    const [files, setFiles] = useState<File[]>([]);
    const [errors, setErrors] = useState<Record<string, string>>({});
    const [busy, setBusy] = useState(false);
    const fileRef = useRef<HTMLInputElement>(null);
    const top = mode.messages.filter((m) => m.type !== 'answer' || !m.parent_id);
    const replies = (id: number) => mode.messages.filter((m) => m.parent_id === id);
    const tooBig = files.find((f) => f.size > mode.maxFileMb * 1024 * 1024);

    const submit = (e: React.FormEvent) => {
        e.preventDefault();
        if (tooBig) return;
        setBusy(true);
        router.post(`${mode.base}/questions`, { body, files }, {
            forceFormData: true, preserveScroll: true,
            onError: (x) => setErrors(x as Record<string, string>),
            onSuccess: () => { setBody(''); setFiles([]); setErrors({}); if (fileRef.current) fileRef.current.value = ''; },
            onFinish: () => setBusy(false),
        });
    };
    const fileError = errors.files ?? Object.entries(errors).find(([k]) => k.startsWith('files.'))?.[1];

    const Msg = ({ m }: { m: ClientMode['messages'][number] }) => (
        <div className="flex gap-3">
            <span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-[11px] font-bold" style={m.from_staff ? { background: p.grad, color: p.onAccent, ...hf } : { background: p.tint, color: p.accentInk, ...hf }}>
                {m.author.split(/\s+/).map((w) => w[0]).slice(0, 2).join('').toUpperCase()}
            </span>
            <div className="min-w-0 flex-1">
                <div className="text-[12px]"><b style={hf}>{m.author}</b> <span style={{ color: p.muted }}>· {dateTime(m.at)}</span></div>
                <p className="mt-0.5 whitespace-pre-line text-[13.5px] leading-[1.6]">{m.body}</p>
                {m.files.length > 0 && (
                    <div className="mt-1.5 flex flex-wrap gap-2">
                        {m.files.map((f) => <a key={f.url} href={f.url} className="inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-[11.5px] font-medium" style={{ background: p.page }}><Paperclip className="h-3 w-3" />{f.name} <span style={{ color: p.muted }}>{kb(f.size)}</span></a>)}
                    </div>
                )}
            </div>
        </div>
    );

    return (
        <section id="questions" className="pt-10 print:hidden" data-testid="questions" data-sk="Questions">
            <div className="rounded-[18px] px-6 py-7 @lg:px-8" style={{ background: p.card, boxShadow: SHADOW }}>
                <h2 className="flex items-center gap-2 text-[22px] font-bold" style={hf}><MessageCircle className="h-5 w-5" style={{ color: p.accent }} /> Questions</h2>
                <p className="mt-1 text-[13px]" style={{ color: p.muted }}>Ask us anything about this proposal. We'll reply by email and here.</p>
                {top.length > 0 && (
                    <div className="mt-5 space-y-5">
                        {top.map((m) => (
                            <div key={m.id} className="space-y-3" data-testid="thread">
                                <Msg m={m} />
                                {replies(m.id).map((r) => <div key={r.id} className="ml-11 border-l-2 pl-4" style={{ borderColor: p.accent }}><Msg m={r} /></div>)}
                            </div>
                        ))}
                    </div>
                )}
                <form onSubmit={submit} className="mt-6 space-y-3" noValidate>
                    <label htmlFor="question_body" className="sr-only">Your question</label>
                    <textarea id="question_body" rows={3} className={inputCls} style={fieldStyle(p)} placeholder="Type your question…" value={body} onChange={(e) => setBody(e.target.value)} />
                    <Err msg={errors.body} />
                    <div className="flex flex-wrap items-center justify-between gap-3">
                        <div className="flex flex-wrap items-center gap-2 text-[12px]">
                            <input ref={fileRef} id="question_files" type="file" multiple className="sr-only" onChange={(e) => setFiles([...(e.target.files ?? [])].slice(0, 5))} />
                            <label htmlFor="question_files" className="inline-flex cursor-pointer items-center gap-1.5 rounded-full border px-3 py-1.5 font-semibold" style={{ borderColor: p.cardLine }}><Paperclip className="h-3.5 w-3.5" /> Attach files</label>
                            {files.map((f) => <span key={f.name} style={{ color: f.size > mode.maxFileMb * 1048576 ? '#b91c1c' : p.muted }}>{f.name} ({kb(f.size)})</span>)}
                            <span style={{ color: p.muted }}>Up to 5 files, {mode.maxFileMb} MB each.</span>
                        </div>
                        <PrimaryButton p={p} type="submit" disabled={busy || !body.trim() || !!tooBig} data-testid="question-submit">{busy ? 'Sending…' : 'Send question'}</PrimaryButton>
                    </div>
                    <Err msg={tooBig ? `${tooBig.name} is larger than ${mode.maxFileMb} MB.` : fileError} />
                </form>
            </div>
        </section>
    );
}
