'use client';
import { useEffect, useState } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useSearchParams } from 'next/navigation';
import { api } from '@/lib/api';
import { useAuth } from '@/lib/auth-context';
import { CategoryPicker, DepartmentPicker } from '@/components/pickers';
import { Importer } from '@/components/importer';
import { DataTable, Pagination } from '@/components/data-table';
import { FloatInput, FloatSelect, FloatArea } from '@/components/float';
import { DatePicker } from '@/components/date-picker';
import { IconTrash, IconEdit, IconClose, CopyButton } from '@/components/ui';
import { formatDate } from '@/lib/format';
import { validate, v, Errors } from '@/lib/validation';
import { useDialog } from '@/components/dialog';
import { AttachmentComposer, AttachmentRow, useStagedAttachments, uploadStaged, linkRowErrors } from '@/components/attachments';
import { CommentThread, CommentBox } from '@/components/comments';
import { useCan } from '@/lib/permissions';

const PAGE_SIZE = 10;

export const dynamic = 'force-dynamic';

export default function SamplesPage() {
  const { user } = useAuth();
  const { confirmDialog, notify } = useDialog();
  const canDeleteSample = useCan('sample', 'delete');
  const [search, setSearch] = useState('');
  const [page, setPage] = useState(1);
  const [openId, setOpenId] = useState<number | null>(null);
  const sp = useSearchParams();
  // Deep link (?open=<id>) from shared sample URLs.
  useEffect(() => {
    const id = Number(sp.get('open'));
    if (Number.isInteger(id) && id > 0) setOpenId(id);
  }, [sp]);
  const [fStatus, setFStatus] = useState('');
  const [fDept, setFDept] = useState<number | null>(null);
  const [fFrom, setFFrom] = useState('');
  const [fTo, setFTo] = useState('');
  const resetFilters = () => { setFStatus(''); setFDept(null); setFFrom(''); setFTo(''); setSearch(''); setPage(1); };
  const q = useQuery({
    queryKey: ['samples', search, page, fStatus, fDept, fFrom, fTo],
    queryFn: () => api.get(`/api/v1/samples?search=${encodeURIComponent(search)}${fStatus ? `&status=${encodeURIComponent(fStatus)}` : ''}${fDept ? `&department_id=${fDept}` : ''}${fFrom ? `&from=${fFrom}` : ''}${fTo ? `&to=${fTo}` : ''}&page=${page}&limit=${PAGE_SIZE}`),
  });
  const canCreate = useCan('sample', 'create');
  const rows: any[] = q.data?.data ?? [];
  const [form, setForm] = useState({ sample_name: '', description: '', due_date: '', expiry_date: '', testing_requirement: '' });
  const [formErrs, setFormErrs] = useState<Errors>({});
  const [smCategory, setSmCategory] = useState<number | null>(null);
  const [smDept, setSmDept] = useState<number | ''>('');
  const [busy, setBusy] = useState(false);
  const staged = useStagedAttachments();
  const [recur, setRecur] = useState(false);
  const [freq, setFreq] = useState('weekly');
  const [showForm, setShowForm] = useState(false);
  const [showImport, setShowImport] = useState(false);
  // Repeat series runs from the testing date up to (and including) the expiry
  // date: every day / every 7 days / every 30 days. The available shelf-life
  // window sizes the series — Daily is only offered for windows up to 15 days,
  // Weekly needs at least 7 days, Monthly at least 30 days.
  const repeatWindowDays: number | null = form.due_date && form.expiry_date && form.expiry_date >= form.due_date
    ? diffDaysISO(form.due_date, form.expiry_date)
    : null;
  const dailyOk = repeatWindowDays !== null && repeatWindowDays <= 15;
  const weeklyOk = repeatWindowDays !== null && repeatWindowDays >= 7;
  const monthlyOk = repeatWindowDays !== null && repeatWindowDays >= 30;
  const repeatCounts = repeatWindowDays === null ? null : {
    daily: repeatWindowDays + 1,
    weekly: Math.floor(repeatWindowDays / 7) + 1,
    monthly: Math.floor(repeatWindowDays / 30) + 1,
  };
  const repeatPreview: string[] = !recur || !form.due_date ? [] : (() => {
    const step = freq === 'daily' ? 1 : freq === 'weekly' ? 7 : 30;
    const out: string[] = [];
    for (let i = 0; ; i++) {
      const d = addDaysISO(form.due_date, i * step);
      if (form.expiry_date && d > form.expiry_date) break;
      out.push(d);
      if (out.length >= 500 || (!form.expiry_date && out.length >= 15)) break;
    }
    return out;
  })();
  return (
    <div className="space-y-3">
      <div className="flex gap-2 items-center">
        <h1 className="page-title mr-auto">QC Samples & Testing</h1>
        {canCreate && <button className="btn-ghost text-sm" onClick={() => { setShowImport((v) => !v); setShowForm(false); }}>Bulk import</button>}
        {canCreate && (showForm
          ? <button className="btn-ghost btn-icon" aria-label="Close sample form" title="Close" onClick={() => setShowForm(false)}><IconClose /></button>
          : <button className="btn-primary" onClick={() => setShowForm(true)}>+ Add Sample</button>)}
      </div>
      {canCreate && showImport && !showForm && <Importer kind="samples" label="QC samples" onDone={() => q.refetch()} />}
      {canCreate && showForm && (
        <form className="card space-y-2 !mt-6" noValidate onSubmit={async (e) => {
          e.preventDefault();
          const fe = validate({
            sample_name: () => v.minLen(form.sample_name, 2, 'Sample name'),
            due_date: () => v.todayOrFuture(form.due_date, 'Testing date'),
            expiry_date: () => v.date(form.expiry_date, 'Expiry / shelf-life date'),
            window: () => v.afterOrEqual(form.expiry_date, form.due_date, 'Expiry date', 'testing date'),
            category: () => (!smCategory ? 'Category is required' : null),
            department: () => (smDept === '' ? 'Department is required' : null),
            repeat: () => {
              if (!recur) return null;
              if (!form.due_date || !form.expiry_date || form.expiry_date < form.due_date) return 'Repeating tests needs a testing date and an expiry date on or after it';
              if (freq === 'daily' && !dailyOk) return 'Daily repeat needs expiry within 15 days of the testing date — pick Weekly or Monthly';
              if (freq === 'weekly' && !weeklyOk) return 'Weekly repeat needs at least 7 days between testing date and expiry — pick Daily';
              if (freq === 'monthly' && !monthlyOk) return 'Monthly repeat needs at least 30 days between testing date and expiry — pick Daily or Weekly';
              return null;
            },
          });
          setFormErrs(fe);
          if (Object.keys(fe).length) return;
          const linkErrs = linkRowErrors(staged.links);
          if (linkErrs.length) { notify(linkErrs.join('\n'), { tone: 'error' }); return; }
          setBusy(true);
          try {
            const created: any = await api.post('/api/v1/samples', {
              ...form, category_id: smCategory, department_id: smDept,
            });
            if (recur && repeatPreview.length) {
              await api.post('/api/v1/schedules', {
                sample_id: created.id,
                activity: form.testing_requirement.trim() || 'Routine testing',
                scheduled_date: repeatPreview[0],
                recurrence: { freq, end_date: repeatPreview[repeatPreview.length - 1] },
              });
            }
            const failed = await uploadStaged({ sample_id: created.id }, staged.files, staged.complete);
            setForm({ sample_name: '', description: '', due_date: '', expiry_date: '', testing_requirement: '' });
            setSmCategory(null); setSmDept(''); setRecur(false); staged.clear();
            if (failed.length) notify(`Sample added, but some attachments failed:\n${failed.join('\n')}`, { title: 'Added with warnings', tone: 'error' });
            else notify(`Sample added${staged.count ? ` with ${staged.count} attachment${staged.count === 1 ? '' : 's'}` : ''}.`, { tone: 'success' });
            setShowForm(false);
            q.refetch();
          } catch (ex: any) { notify(ex.message, { tone: 'error' }); }
          finally { setBusy(false); }
        }}>
          <div className="grid md:grid-cols-2 gap-2">
            <div className="md:col-span-2"><FloatInput label="Sample name" id="sm-name" required error={formErrs.sample_name} value={form.sample_name} onChange={(e) => setForm({ ...form, sample_name: e.target.value })} /></div>
            <div className="md:col-span-2"><FloatArea label="Description" id="sm-desc" rows={2} value={form.description} onChange={(e) => setForm({ ...form, description: e.target.value })} /></div>
            <DatePicker label="Testing date" id="sm-due" required error={formErrs.due_date} value={form.due_date} onChange={(val) => setForm({ ...form, due_date: val })} min={new Date().toISOString().slice(0, 10)} />
            <DatePicker label="Expiry / shelf-life date" id="sm-exp" required error={formErrs.expiry_date} value={form.expiry_date} onChange={(val) => setForm({ ...form, expiry_date: val })} />
            <CategoryPicker type="sample" value={smCategory} onChange={setSmCategory} />
            <DepartmentPicker value={smDept === '' ? null : smDept} onChange={(id) => setSmDept(id ?? '')} noneLabel="Select department" />
          </div>
          <FloatArea label="Testing requirement" id="sm-req" rows={2} value={form.testing_requirement} onChange={(e) => setForm({ ...form, testing_requirement: e.target.value })} />
          <div className="flex flex-wrap gap-2 items-start text-sm">
            <label className="flex gap-1 items-center pt-3"><input type="checkbox" checked={recur} onChange={(e) => setRecur(e.target.checked)} /> Repeat tests</label>
            {recur && (
              <>
                <select className="input h-14 text-sm max-w-40" aria-label="Repeat frequency" value={freq} onChange={(e) => setFreq(e.target.value)}>
                  <option value="daily" disabled={!dailyOk}>Daily{repeatCounts && dailyOk ? ` (${repeatCounts.daily})` : ''}</option>
                  <option value="weekly" disabled={!weeklyOk}>Weekly{repeatCounts && weeklyOk ? ` (${repeatCounts.weekly})` : ''}</option>
                  <option value="monthly" disabled={!monthlyOk}>Monthly{repeatCounts && monthlyOk ? ` (${repeatCounts.monthly})` : ''}</option>
                </select>
                <span className="hint pt-3">
                  {!repeatCounts
                    ? 'pick testing + expiry dates to size the series'
                    : freq === 'daily'
                      ? (dailyOk ? `next ${repeatCounts.daily} day${repeatCounts.daily === 1 ? '' : 's'} · every day till expiry` : 'expiry is over 15 days out — Daily not allowed')
                      : freq === 'weekly'
                        ? (weeklyOk ? `next ${repeatCounts.weekly} week${repeatCounts.weekly === 1 ? '' : 's'} · every 7 days till expiry` : 'less than 7 days of shelf life — Weekly not allowed')
                        : (monthlyOk ? `next ${repeatCounts.monthly} month${repeatCounts.monthly === 1 ? '' : 's'} · every 30 days till expiry` : 'less than 30 days of shelf life — Monthly not allowed')}
                </span>
              </>
            )}
          </div>
          {recur && repeatPreview.length > 0 && (
            <div className="flex flex-wrap gap-1.5" aria-live="polite">
              {repeatPreview.slice(0, 60).map((d) => (
                <span key={d} className="badge bg-slate-100 whitespace-nowrap">{formatDate(d)}</span>
              ))}
              {repeatPreview.length > 60 && <span className="badge bg-slate-100">+{repeatPreview.length - 60} more</span>}
            </div>
          )}
          {(formErrs.expiry_date || formErrs.window || formErrs.category || formErrs.department || formErrs.repeat) && (
            <p role="alert" className="field-error">{formErrs.expiry_date ?? formErrs.window ?? formErrs.category ?? formErrs.department ?? formErrs.repeat}</p>
          )}
          <div className="rounded-xl border border-slate-200 px-3 py-2 text-xs text-slate-600"><span className="font-semibold">Attachments (Optional)</span> — attach files or link existing Drive documents.</div>
          <AttachmentComposer idPrefix="sm" files={staged.files} setFiles={staged.setFiles} links={staged.links} setLinks={staged.setLinks} />
          <div className="flex">
            <button className="btn-primary btn-tall justify-self-start px-8" disabled={busy}>{busy ? 'Creating…' : 'Create Sample'}</button>
          </div>
        </form>
      )}
      <div className="grid grid-cols-2 md:grid-cols-4 xl:grid-cols-8 gap-2 items-start">
        <FloatSelect label="Status" id="sm-fstatus" value={fStatus} onChange={(e) => { setFStatus(e.target.value); setPage(1); }}>
          <option value="">All statuses</option>
          <option>Registered</option><option>Testing Scheduled</option><option>In Testing</option>
          <option>Testing Completed</option><option>Expired</option><option>Cancelled</option>
        </FloatSelect>
        <DepartmentPicker value={fDept} noneLabel="All departments" onChange={(v) => { setFDept(v); setPage(1); }} />
        <DatePicker label="Testing from" id="sm-ffrom" value={fFrom} onChange={(v) => { setFFrom(v); setPage(1); }} />
        <DatePicker label="Testing to" id="sm-fto" value={fTo} onChange={(v) => { setFTo(v); setPage(1); }} />
        <input
          className="input h-14 text-sm w-full md:col-span-3 xl:col-span-3"
          type="search"
          placeholder="Search QC samples"
          aria-label="Search QC samples"
          value={search}
          onChange={(e) => { setSearch(e.target.value); setPage(1); }}
        />
        <button className="btn-ghost text-sm btn-tall w-full" onClick={resetFilters}>Clear</button>
      </div>
      <DataTable
        caption="Samples"
        rows={rows}
        keyOf={(s) => s.id}
        searchValue={search}
        onSearchChange={(v) => { setSearch(v); setPage(1); }}
        searchPlaceholder="Search QC samples"
        hideSearch
        empty={q.isLoading ? 'Loading…' : 'No QC samples found.'}
        columns={[
          { header: 'Sample No', render: (s: any) => <b className="text-xs font-mono whitespace-nowrap">{s.sample_number}</b> },
          { header: 'Name', render: (s: any) => <span className="font-medium">{s.sample_name}</span> },
          { header: 'Status', render: (s: any) => <span className="badge bg-slate-100 whitespace-nowrap">{s.status}</span> },
          { header: 'Testing date', render: (s: any) => <span className="whitespace-nowrap">{formatDate(s.due_date)}</span> },
          { header: 'Shelf life', render: (s: any) => <span className="whitespace-nowrap">{formatDate(s.expiry_date)}</span> },
          { header: 'Category', render: (s: any) => s.cat_name ?? '—' },
          { header: 'Tests', render: (s: any) => <span className="whitespace-nowrap">{s.sched_done}/{s.sched_total}</span> },
          { header: 'Next test', render: (s: any) => <span className="whitespace-nowrap">{s.next_test ? formatDate(s.next_test) : '—'}</span> },
          {
            header: 'Actions', render: (s: any) => (
              <span className="flex gap-1 items-center">
                <button type="button" className="btn-plain" aria-label={`Edit ${s.sample_number}`} aria-expanded={openId === s.id} title="Manage sample" onClick={() => setOpenId(openId === s.id ? null : s.id)}><IconEdit /></button>
                <CopyButton url={typeof window !== 'undefined' ? `${window.location.origin}/samples?open=${s.id}` : `/samples?open=${s.id}`} label={s.sample_number} />
                {canDeleteSample && (
                  <button type="button" className="btn-plain" aria-label={`Delete ${s.sample_number}`} title="Delete sample" onClick={async () => {
                    if (!await confirmDialog(`Delete sample ${s.sample_number}? Its test schedules go with it.`, { title: 'Delete sample', confirmLabel: 'Delete' })) return;
                    try { await api.del(`/api/v1/samples/${s.id}`); notify('Sample deleted.', { tone: 'success' }); q.refetch(); }
                    catch (e: any) { notify(e.message, { tone: 'error' }); }
                  }}><IconTrash /></button>
                )}
              </span>
            ),
          },
        ]}
      />
      {rows.filter((s) => s.id === openId).map((s: any) => (
        <div key={`manage-${s.id}`} className="card space-y-2">
          <div className="flex flex-wrap gap-2 items-center">
            <p className="text-sm font-medium mb-1 mr-auto">{s.sample_number} — {s.sample_name}</p>
            {canDeleteSample && (
              <button type="button" className="btn-plain" aria-label={`Delete ${s.sample_number}`} title="Delete sample" onClick={async () => {
                if (!await confirmDialog(`Delete sample ${s.sample_number}? Its test schedules go with it.`, { title: 'Delete sample', confirmLabel: 'Delete' })) return;
                try { await api.del(`/api/v1/samples/${s.id}`); notify('Sample deleted.', { tone: 'success' }); setOpenId(null); q.refetch(); }
                catch (e: any) { notify(e.message, { tone: 'error' }); }
              }}><IconTrash /></button>
            )}
          </div>
          <div className="table-wrap">
            <table className="table">
              <caption className="sr-only">Sample details</caption>
              <tbody>
                <tr><th scope="row" className="!py-2 w-36">Category</th><td className="!py-2">{s.cat_name ?? '—'}</td></tr>
                <tr><th scope="row" className="!py-2 w-36">Department</th><td className="!py-2">{s.dept_name ?? '—'}</td></tr>
                <tr><th scope="row" className="!py-2 w-36">Testing date</th><td className="!py-2">{formatDate(s.due_date)}</td></tr>
                <tr><th scope="row" className="!py-2 w-36">Shelf-life date</th><td className="!py-2">{formatDate(s.expiry_date)}</td></tr>
                <tr><th scope="row" className="!py-2 w-36">Status</th><td className="!py-2">{s.status}</td></tr>
              </tbody>
            </table>
          </div>
          {canCreate && <SampleEdit sample={s} refresh={() => q.refetch()} />}
          <SampleActions sample={s} refresh={() => q.refetch()} canEdit={canCreate} />
          <SampleDocs sampleId={s.id} />
          <SampleComments sampleId={s.id} />
        </div>
      ))}
      <Pagination page={page} total={q.data?.total ?? 0} limit={PAGE_SIZE} onPage={setPage} />
    </div>
  );
}

function SampleEdit({ sample, refresh }: { sample: any; refresh: () => void }) {
  const { notify } = useDialog();
  const [name, setName] = useState(sample.sample_name ?? '');
  const [desc, setDesc] = useState(sample.description ?? '');
  const [cat, setCat] = useState<number | null>(sample.category_id ?? null);
  const [dept, setDept] = useState<number | null>(sample.department_id ?? null);
  const [due, setDue] = useState(sample.due_date ? String(sample.due_date).slice(0, 10) : '');
  const [exp, setExp] = useState(sample.expiry_date ? String(sample.expiry_date).slice(0, 10) : '');
  const [req, setReq] = useState(sample.testing_requirement ?? '');
  const [errs, setErrs] = useState<Errors>({});
  const [busy, setBusy] = useState(false);
  return (
    <div className="border rounded-xl p-3 space-y-2">
      <p className="font-medium text-sm">Edit sample</p>
      <div className="grid md:grid-cols-2 gap-2">
        <div className="md:col-span-2"><FloatInput label="Sample name" id={`se-name-${sample.id}`} error={errs.sample_name} value={name} onChange={(e) => setName(e.target.value)} /></div>
        <div className="md:col-span-2"><FloatArea label="Description" id={`se-desc-${sample.id}`} rows={2} value={desc} onChange={(e) => setDesc(e.target.value)} /></div>
        <CategoryPicker type="sample" value={cat} onChange={setCat} />
        <DepartmentPicker value={dept} onChange={setDept} />
        <DatePicker label="Testing date" id={`se-due-${sample.id}`} required error={errs.due_date} value={due} onChange={setDue} />
        <DatePicker label="Expiry / shelf-life date" id={`se-exp-${sample.id}`} required error={errs.expiry_date} value={exp} onChange={setExp} />
      </div>
      <FloatArea label="Testing requirement" id={`se-req-${sample.id}`} rows={2} value={req} onChange={(e) => setReq(e.target.value)} />
      {(errs.sample_name || errs.expiry_date || errs.window || errs.category || errs.department) && (
        <p role="alert" className="field-error">{errs.sample_name ?? errs.expiry_date ?? errs.window ?? errs.category ?? errs.department}</p>
      )}
      <button className="btn-primary btn-sm" disabled={busy} onClick={async () => {
        const fe = validate({
          sample_name: () => v.minLen(name, 2, 'Sample name'),
          due_date: () => v.todayOrFuture(due, 'Testing date'),
          expiry_date: () => v.date(exp, 'Expiry / shelf-life date'),
          window: () => v.afterOrEqual(exp, due, 'Expiry date', 'testing date'),
          category: () => (!cat ? 'Category is required' : null),
          department: () => (!dept ? 'Department is required' : null),
        });
        setErrs(fe);
        if (Object.keys(fe).length) return;
        setBusy(true);
        try {
          await api.patch(`/api/v1/samples/${sample.id}`, {
            sample_name: name.trim(), description: desc.trim() || null, category_id: cat, department_id: dept,
            due_date: due, expiry_date: exp, testing_requirement: req.trim() || null,
          });
          notify('Sample updated.', { tone: 'success' });
          refresh();
        } catch (e: any) { notify(e.message, { tone: 'error' }); }
        finally { setBusy(false); }
      }}>{busy ? 'Saving…' : 'Save changes'}</button>
    </div>
  );
}

function todayStr() {
  return new Date().toISOString().slice(0, 10);
}
function addDaysISO(dateStr: string, days: number): string {
  const [y, m, d] = dateStr.split('-').map(Number);
  return new Date(Date.UTC(y, m - 1, d + days)).toISOString().slice(0, 10);
}
function diffDaysISO(a: string, b: string): number {
  const [ay, am, ad] = a.split('-').map(Number);
  const [by, bm, bd] = b.split('-').map(Number);
  return Math.round((Date.UTC(by, bm - 1, bd) - Date.UTC(ay, am - 1, ad)) / 86400000);
}

const SCHED_STATUSES = ['Scheduled', 'Upcoming', 'In Progress', 'Completed', 'Missed', 'Overdue', 'Cancelled'];

/**
 * Test planner for one sample: pick Daily (today, no activity needed),
 * Weekly (next ~month) or Monthly (coming year, 30-day steps), preview the
 * dates and schedule them in one tap. Existing entries carry a status
 * dropdown and per-test proof upload below.
 */
function SampleActions({ sample, refresh, canEdit }: { sample: any; refresh: () => void; canEdit: boolean }) {
  const { notify } = useDialog();
  const [freq, setFreq] = useState<'daily' | 'weekly' | 'monthly'>('weekly');
  const [busy, setBusy] = useState(false);
  const sq = useQuery({
    queryKey: ['sample-schedules', sample.id],
    queryFn: () => api.get(`/api/v1/schedules?sample_id=${sample.id}&limit=100`),
  });
  const scheduled: any[] = sq.data?.data ?? [];
  const scheduledDates = new Set(scheduled.map((t: any) => String(t.scheduled_date).slice(0, 10)));
  const anchor = sample.due_date && String(sample.due_date).slice(0, 10) >= todayStr()
    ? String(sample.due_date).slice(0, 10) : todayStr();
  const preview: string[] = freq === 'daily'
    ? [todayStr()]
    : freq === 'weekly'
      ? [0, 1, 2, 3].map((w) => addDaysISO(anchor, w * 7))
      : Array.from({ length: 12 }, (_, i) => addDaysISO(anchor, i * 30));
  const activity = sample.testing_requirement?.trim() || 'Routine testing';

  const scheduleAll = async () => {
    setBusy(true);
    try {
      const r = await api.post('/api/v1/schedules', freq === 'daily'
        ? { sample_id: sample.id, activity, scheduled_date: preview[0] }
        : { sample_id: sample.id, activity, scheduled_date: preview[0], recurrence: { freq, end_date: preview[preview.length - 1] } });
      notify(r.ids?.length > 1 ? `${r.ids.length} tests scheduled${r.skippedOutsideWindow ? ` (${r.skippedOutsideWindow} skipped outside window)` : ''}.` : 'Test scheduled for today.', { tone: 'success' });
      sq.refetch(); refresh();
    } catch (e: any) { notify(e.message, { tone: 'error' }); }
    finally { setBusy(false); }
  };

  const setStatus = async (id: number, status: string) => {
    try {
      await api.patch(`/api/v1/schedules/${id}`, { status });
      notify('Status updated.', { tone: 'success' });
      sq.refetch(); refresh();
    } catch (e: any) { notify(e.message, { tone: 'error' }); }
  };

  const uploadProof = async (scheduleId: number, files: FileList | null) => {
    if (!files?.length) return;
    const failed: string[] = [];
    for (const f of Array.from(files)) {
      const fd = new FormData();
      fd.append('file', f);
      fd.append('sample_id', String(sample.id));
      fd.append('testing_schedule_id', String(scheduleId));
      try { await api.upload('/api/v1/documents', fd); }
      catch (ex: any) { failed.push(`${f.name}: ${ex.message}`); }
    }
    if (failed.length) notify(failed.join('\n'), { title: 'Some uploads failed', tone: 'error' });
    else notify(`${files.length} proof file${files.length === 1 ? '' : 's'} attached (see Attachments).`, { tone: 'success' });
  };

  return (
    <div className="space-y-2 mt-2">
      <h3 className="font-medium text-sm">Schedule tests</h3>
      {canEdit && (
        <>
          <div className="flex gap-1" role="tablist" aria-label="Schedule frequency">
            {(['daily', 'weekly', 'monthly'] as const).map((f) => (
              <button key={f} type="button" role="tab" aria-selected={freq === f}
                className={`btn-ghost btn-sm capitalize ${freq === f ? '!border-slate-900 font-bold' : ''}`}
                onClick={() => setFreq(f)}>
                {f}
              </button>
            ))}
          </div>
          <p className="hint">
            {freq === 'daily' && `Today (${formatDate(todayStr())}) — no activity needed, uses “${activity}”.`}
            {freq === 'weekly' && 'Next 4 weekly slots (≈ one month).'}
            {freq === 'monthly' && 'Next 12 monthly slots (30-day steps, one year).'}
          </p>
          <div className="flex flex-wrap gap-1.5">
            {preview.map((d) => {
              const done = scheduledDates.has(d);
              return (
                <span key={d} className={`badge whitespace-nowrap ${done ? 'bg-green-100 text-green-800' : 'bg-slate-100'}`} title={done ? 'Already scheduled' : 'Planned'}>
                  {done ? '✓ ' : ''}{formatDate(d)}
                </span>
              );
            })}
          </div>
          <button className="btn-primary btn-sm" disabled={busy} onClick={scheduleAll}>
            {busy ? 'Scheduling…' : freq === 'daily' ? 'Schedule for today' : `Schedule all ${freq === 'weekly' ? '4' : '12'}`}
          </button>
        </>
      )}
      <div className="table-wrap">
        <table className="table">
          <caption className="sr-only">Scheduled tests</caption>
          <thead><tr><th scope="col">Date</th><th scope="col">Status</th><th scope="col">Proof</th></tr></thead>
          <tbody>
            {scheduled.map((t: any) => (
              <tr key={t.id} className="border-t">
                <td className="whitespace-nowrap">{formatDate(t.scheduled_date)}</td>
                <td>
                  {canEdit ? (
                    <select className="input !min-h-0 !h-9 text-sm max-w-44" aria-label={`Status for test on ${t.scheduled_date}`}
                      value={t.status} onChange={(e) => setStatus(t.id, e.target.value)}>
                      {SCHED_STATUSES.map((s) => <option key={s}>{s}</option>)}
                    </select>
                  ) : (
                    <span className="badge bg-slate-100 whitespace-nowrap">{t.status}</span>
                  )}
                </td>
                <td>
                  <label className="text-xs underline cursor-pointer whitespace-nowrap">Upload proof
                    <input type="file" multiple className="hidden" onChange={async (e) => { await uploadProof(t.id, e.target.files); e.target.value = ''; }} />
                  </label>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
        {!scheduled.length && <p className="text-sm text-center px-3 py-4" style={{ color: 'var(--muted)' }}>{sq.isLoading ? 'Loading…' : 'No tests scheduled yet.'}</p>}
      </div>
    </div>
  );
}

/** Discussion thread on a sample (same chat experience as deviations). */
function SampleComments({ sampleId }: { sampleId: number }) {
  const { notify } = useDialog();
  const cq = useQuery({
    queryKey: ['sample-comments', sampleId],
    queryFn: () => api.get(`/api/v1/samples/${sampleId}/comments`),
  });
  return (
    <div className="border-t pt-2 space-y-3">
      <h3 className="font-medium text-sm">Comments</h3>
      {cq.isLoading
        ? <p className="text-sm text-slate-500">Loading comments…</p>
        : <CommentThread comments={cq.data ?? []} />}
      <CommentBox onSubmit={async (body) => {
        try { await api.post(`/api/v1/samples/${sampleId}/comments`, { body }); cq.refetch(); }
        catch (e: any) { notify(e.message, { tone: 'error' }); }
      }} />
    </div>
  );
}

/** Supporting attachments for a sample: file list + multi-file upload + Drive links. */
function SampleDocs({ sampleId }: { sampleId: number }) {
  const { notify } = useDialog();
  const docsQ = useQuery({
    queryKey: ['sample-docs', sampleId],
    queryFn: () => api.get(`/api/v1/documents?sample_id=${sampleId}&limit=100`),
  });
  const [busy, setBusy] = useState(false);
  const staged = useStagedAttachments();
  const docs: any[] = docsQ.data?.data ?? [];
  const attach = async () => {
    if (!staged.count) { notify('Choose files or fill a link row first.', { tone: 'error' }); return; }
    const linkErrs = linkRowErrors(staged.links);
    if (linkErrs.length) { notify(linkErrs.join('\n'), { tone: 'error' }); return; }
    setBusy(true);
    try {
      const failed = await uploadStaged({ sample_id: sampleId }, staged.files, staged.complete);
      staged.clear(); docsQ.refetch();
      if (failed.length) notify(failed.join('\n'), { title: 'Some attachments failed', tone: 'error' });
      else notify('Attachments added.', { tone: 'success' });
    } finally { setBusy(false); }
  };
  return (
    <div className="border-t pt-2 space-y-2">
      <h3 className="font-medium text-sm">Attachments <span className="text-xs font-normal text-slate-400">files + Drive links ({docs.length})</span></h3>
      {docs.map((d: any) => <AttachmentRow key={d.id} d={d} onChanged={() => docsQ.refetch()} />)}
      {docsQ.isLoading && <p className="text-sm text-slate-500">Loading attachments…</p>}
      {!docsQ.isLoading && docs.length === 0 && <p className="text-sm text-slate-500">No attachments yet.</p>}
      <div className="rounded-xl border border-slate-200 px-3 py-2 text-xs text-slate-600"><span className="font-semibold">Attachments (Optional)</span> — attach files or link existing Drive documents.</div>
      <AttachmentComposer idPrefix={`sm-${sampleId}`} files={staged.files} setFiles={staged.setFiles} links={staged.links} setLinks={staged.setLinks} />
      <button type="button" className="btn-primary text-sm btn-tall" disabled={busy || !staged.count} onClick={attach}>
        {busy ? 'Attaching…' : `Attach${staged.count ? ` (${staged.count})` : ''}`}
      </button>
    </div>
  );
}
