"use client";

import { useMemo, useState } from "react";
import { useLang } from "@/context/LanguageContext";
import {
  Alert,
  Button,
  Card,
  EmptyState,
  Input,
  Modal,
  PageHeader,
  PageLoader,
  Pagination,
  Select,
  StatusBadge,
  usePagination,
} from "@/components/ui";
import { api, useApi } from "@/lib/client";
import { formatDate } from "@/lib/utils";
import { IconCamera, IconCheck, IconClipboard, IconLayers, IconPrinter, IconTrash, IconUpload } from "@/components/icons";
import { emsConfirm } from "@/components/ConfirmDialog";
import { renderOmrSheet } from "@/lib/omr-render";
import { scanOmrCanvas, imageToCanvas } from "@/lib/omr-scan";

/* OMR Sheet Management — answer keys per exam paper + question set, scanned
   response import (roll,set,responses) with auto MCQ scoring, and pushing
   the computed marks into the exam's marks input. */

interface PaperRow {
  id: number;
  subject: string;
  subjectBn: string | null;
  date: string;
  fullMarks: number;
  mcqMarks: number | null;
  cqMarks: number | null;
  keys: number;
  responses: number;
  matched: number;
}

interface OverviewData {
  exams: {
    id: number;
    name: string;
    nameBn: string | null;
    status: string;
    resultPublished: boolean;
    papers: PaperRow[];
  }[];
}

interface SheetDetail {
  id: number;
  setLabel: string | null;
  questions: number;
  markPerQuestion: number;
  negativeMark: number;
  answerKey: string;
  responses: {
    id: number;
    rawRoll: string;
    studentId: number | null;
    studentName: string | null;
    responses: string;
    correct: number;
    wrong: number;
    blank: number;
    mcqMarks: number;
    status: string;
    statusNote: string | null;
  }[];
}

interface DetailData {
  schedule: {
    id: number;
    mcqMarks: number | null;
    cqMarks: number | null;
    fullMarks: number;
    subjectItem: { name: string; nameBn: string | null };
    exam: { id: number; name: string; resultPublished: boolean };
  };
  sheets: SheetDetail[];
}

interface PrintStudents {
  examName: string;
  examNameBn: string | null;
  subject: string;
  subjectBn: string | null;
  students: { roll: string; name: string }[];
}

interface ScanRow {
  roll: string;
  set: string | null;
  responses: string;
  quality: number;
}

interface ImportPreviewRow {
  roll: string;
  set: string | null;
  status: string;
  note: string | null;
  correct: number;
  wrong: number;
  blank: number;
  marks: number;
}

const emptyKeyForm = {
  setLabel: "",
  questions: "25",
  markPerQuestion: "1",
  negativeMark: "0",
  answerKey: "",
};

export default function PortalOmrPage() {
  const { t, locale } = useLang();
  const o = t.portal.omrPage;

  const overview = useApi<OverviewData>("/api/portal/omr");
  const [examId, setExamId] = useState("");
  const [openPaper, setOpenPaper] = useState<number | null>(null);
  const detail = useApi<DetailData>(openPaper ? `/api/portal/omr?examScheduleId=${openPaper}` : null);

  const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null);
  const [keyOpen, setKeyOpen] = useState(false);
  const [keyPaper, setKeyPaper] = useState<PaperRow | null>(null);
  const [keyForm, setKeyForm] = useState(emptyKeyForm);
  const [keySaving, setKeySaving] = useState(false);
  const [importOpen, setImportOpen] = useState(false);
  const [importText, setImportText] = useState("");
  const [importBusy, setImportBusy] = useState(false);
  const [importPreview, setImportPreview] = useState<ImportPreviewRow[] | null>(null);
  const [importSummary, setImportSummary] = useState<{ saved: number; matched: number; unmatched: number; invalid: number } | null>(null);

  // print view
  const [printOpen, setPrintOpen] = useState(false);
  const [printSet, setPrintSet] = useState("A");
  const [printQuestions, setPrintQuestions] = useState("25");
  const [printData, setPrintData] = useState<PrintStudents | null>(null);
  const [printUrls, setPrintUrls] = useState<string[]>([]);
  const [printBusy, setPrintBusy] = useState(false);

  // scan (camera / upload / sample)
  const [scanOpen, setScanOpen] = useState(false);
  const [scanBusy, setScanBusy] = useState(false);
  const [scanRows, setScanRows] = useState<ScanRow[]>([]);
  const [scanLog, setScanLog] = useState<string[]>([]);

  const exams = overview.data?.exams ?? [];
  const selectedExam = exams.find((e) => String(e.id) === examId) ?? null;
  const papers = selectedExam?.papers ?? [];
  const papersPager = usePagination(papers, 10);
  const allResponses = useMemo(
    () => (detail.data?.sheets ?? []).flatMap((s) => s.responses),
    [detail.data]
  );
  const respPager = usePagination(allResponses, 10);

  function notify(type: "success" | "error", text: string) {
    setMessage({ type, text });
    window.scrollTo({ top: 0, behavior: "smooth" });
  }

  function openKeyModal(p: PaperRow) {
    setKeyPaper(p);
    setKeyForm(emptyKeyForm);
    setKeyOpen(true);
  }

  function openKeyEdit(sh: SheetDetail) {
    setKeyPaper(null);
    setKeyForm({
      setLabel: sh.setLabel ?? "",
      questions: String(sh.questions),
      markPerQuestion: String(sh.markPerQuestion),
      negativeMark: String(sh.negativeMark),
      answerKey: sh.answerKey,
    });
    setKeyOpen(true);
  }

  async function saveKey() {
    setKeySaving(true);
    try {
      await api("/api/portal/omr", {
        method: "PUT",
        body: JSON.stringify({
          examScheduleId: keyPaper ? keyPaper.id : openPaper,
          setLabel: keyForm.setLabel.trim() || null,
          questions: Number(keyForm.questions),
          markPerQuestion: Number(keyForm.markPerQuestion),
          negativeMark: Number(keyForm.negativeMark),
          answerKey: keyForm.answerKey,
        }),
      });
      notify("success", o.keySaved);
      setKeyOpen(false);
      overview.reload();
      detail.reload();
    } catch (e) {
      const msg = e instanceof Error ? e.message : "";
      notify("error", msg === "KEY_LENGTH" ? o.errKeyLength : o.errInvalidSetup);
    } finally {
      setKeySaving(false);
    }
  }

  async function deleteKey(sh: SheetDetail) {
    if (!(await emsConfirm({ message: o.keyDeleteConfirm, danger: true }))) return;
    try {
      await api(`/api/portal/omr?id=${sh.id}`, { method: "DELETE" });
      notify("success", o.keyDeleted);
      overview.reload();
      detail.reload();
    } catch {
      notify("error", t.common.tryAgain);
    }
  }

  function parseImportText(): { roll: string; set: string; responses: string }[] {
    return importText
      .split(/\r?\n/)
      .map((line) => line.trim())
      .filter(Boolean)
      .map((line) => {
        const parts = line.split(/[;,\t]/).map((x) => x.trim());
        // roll,set,responses — or roll,responses when the paper has one set
        if (parts.length >= 3) return { roll: parts[0], set: parts[1], responses: parts.slice(2).join("") };
        if (parts.length === 2) return { roll: parts[0], set: "", responses: parts[1] };
        return { roll: parts[0] ?? "", set: "", responses: "" };
      });
  }

  async function runImport() {
    setImportBusy(true);
    setImportPreview(null);
    setImportSummary(null);
    try {
      const res = await api<{ saved: number; matched: number; unmatched: number; invalid: number; preview: ImportPreviewRow[] }>(
        "/api/portal/omr",
        {
          method: "POST",
          body: JSON.stringify({
            action: "import",
            examScheduleId: openPaper,
            rows: parseImportText(),
          }),
        }
      );
      setImportPreview(res.preview);
      setImportSummary({ saved: res.saved, matched: res.matched, unmatched: res.unmatched, invalid: res.invalid });
      overview.reload();
      detail.reload();
    } catch (e) {
      const msg = e instanceof Error ? e.message : "";
      notify("error", msg === "NO_ROWS" ? o.errNoRows : t.common.tryAgain);
    } finally {
      setImportBusy(false);
    }
  }

  async function pushToMarks() {
    if (!(await emsConfirm({ message: o.pushConfirm, danger: false }))) return;
    try {
      const res = await api<{ updated: number }>("/api/portal/omr", {
        method: "POST",
        body: JSON.stringify({ action: "push", examScheduleId: openPaper }),
      });
      notify("success", o.pushDone.replace("{n}", String(res.updated)));
    } catch (e) {
      const msg = e instanceof Error ? e.message : "";
      notify("error", msg === "RESULT_PUBLISHED" ? o.errPublished : msg === "NOTHING_TO_PUSH" ? o.errNothing : t.common.tryAgain);
    }
  }


  /** ---- print: render one sheet per student, show a printable gallery ---- */
  async function openPrint() {
    if (!openPaper || !detail.data) return;
    setPrintOpen(true);
    setPrintUrls([]);
    setPrintBusy(true);
    try {
      const res = await api<PrintStudents>(`/api/portal/omr/students?examScheduleId=${openPaper}`);
      setPrintData(res);
      const inst = await renderOmrSheet(
        {
          institutionName: "IncrelogEMS Institution",
          examName: res.examName,
          subject: res.subject,
          setLabel: printSet || null,
          roll: "SAMPLE",
          questions: Number(printQuestions) || 25,
          scheduleId: openPaper,
          filled: "AB-DCA" + "B".repeat(Math.max(0, (Number(printQuestions) || 25) - 5)),
        },
        150
      );
      const urls: string[] = [inst.toDataURL("image/jpeg", 0.92)];
      for (const st of res.students.slice(0, 60)) {
        const c = await renderOmrSheet(
          {
            institutionName: "IncrelogEMS Institution",
            examName: res.examName,
            subject: res.subject,
            setLabel: printSet || null,
            roll: st.roll,
            questions: Number(printQuestions) || 25,
            scheduleId: openPaper,
          },
          200
        );
        urls.push(c.toDataURL("image/jpeg", 0.92));
      }
      setPrintUrls(urls);
    } catch {
      notify("error", t.common.tryAgain);
    } finally {
      setPrintBusy(false);
    }
  }

  /** ---- scan: camera/upload photos → QR + bubbles → rows for import ---- */
  async function scanFiles(files: FileList | null) {
    if (!files?.length || !detail.data) return;
    setScanBusy(true);
    setScanLog([]);
    const rows: ScanRow[] = [];
    const log: string[] = [];
    const questions = detail.data.sheets[0]?.questions ?? 25;
    for (const file of Array.from(files)) {
      try {
        const url = URL.createObjectURL(file);
        const img = new Image();
        await new Promise<void>((res, rej) => {
          img.onload = () => res();
          img.onerror = () => rej(new Error("img"));
          img.src = url;
        });
        const canvas = imageToCanvas(img);
        URL.revokeObjectURL(url);
        const result = await scanOmrCanvas(canvas, questions);
        if (!result || !result.roll) {
          log.push(`${file.name}: ${o.scanNoQr}`);
          continue;
        }
        rows.push({ roll: result.roll, set: result.setLabel, responses: result.responses, quality: result.quality });
        log.push(`${file.name} → ${result.roll} · ${result.responses.slice(0, 12)}… (${Math.round(result.quality * 100)}%)`);
      } catch {
        log.push(`${file.name}: ${o.scanFailed}`);
      }
    }
    setScanRows(rows);
    setScanLog(log);
    setScanBusy(false);
  }

  /** demo: synthesize a filled sheet and scan it — proves the pipeline end-to-end */
  async function scanSample() {
    if (!detail.data || !openPaper) return;
    setScanBusy(true);
    setScanLog([]);
    try {
      const questions = detail.data.sheets[0]?.questions ?? 25;
      const filled = Array.from({ length: questions }, (_, i) => "ABCD"[i % 4 === 3 ? 1 : i % 4]).join("");
      const canvas = await renderOmrSheet(
        {
          institutionName: "IncrelogEMS Institution",
          examName: detail.data.schedule.exam.name,
          subject: detail.data.schedule.subjectItem?.name ?? "",
          setLabel: detail.data.sheets[0]?.setLabel ?? null,
          roll: "1001",
          questions,
          scheduleId: openPaper,
          filled,
        },
        200
      );
      const result = await scanOmrCanvas(canvas, questions);
      if (result?.roll && result.responses) {
        const match = result.responses === filled;
        setScanRows([{ roll: result.roll, set: result.setLabel, responses: result.responses, quality: result.quality }]);
        setScanLog([
          `${o.scanSample} → ${result.roll} · ${result.responses.slice(0, 12)}… (${Math.round(result.quality * 100)}%)`,
          `${o.scanMatchNote}: ${match ? "100%" : `${[...filled].filter((c, i) => c === result.responses[i]).length}/${questions}`}`,
        ]);
      } else if (result?.roll) {
        setScanLog([`${result.roll}: ${o.scanNoQr}`]);
      } else {
        setScanLog([o.scanNoQr]);
      }
    } finally {
      setScanBusy(false);
    }
  }

  /** send scanned rows through the regular import pipeline */
  async function importScanned() {
    if (!scanRows.length || !openPaper) return;
    setImportBusy(true);
    setImportPreview(null);
    setImportSummary(null);
    try {
      const res = await api<{ saved: number; matched: number; unmatched: number; invalid: number; preview: ImportPreviewRow[] }>(
        "/api/portal/omr",
        {
          method: "POST",
          body: JSON.stringify({
            action: "import",
            examScheduleId: openPaper,
            rows: scanRows.map((r) => ({ roll: r.roll, set: r.set ?? "", responses: r.responses })),
          }),
        }
      );
      setImportPreview(res.preview);
      setImportSummary({ saved: res.saved, matched: res.matched, unmatched: res.unmatched, invalid: res.invalid });
      setScanRows([]);
      setScanOpen(false);
      overview.reload();
      detail.reload();
    } catch {
      notify("error", t.common.tryAgain);
    } finally {
      setImportBusy(false);
    }
  }

  if (overview.loading) return <PageLoader text={t.common.loading} />;

  return (
    <div>
      <PageHeader title={o.title} subtitle={o.subtitle} />

      {message && (
        <div className="mb-4">
          <Alert type={message.type} onClose={() => setMessage(null)}>{message.text}</Alert>
        </div>
      )}

      {/* how it works */}
      <Card className="mb-4">
        <div className="card-body d-flex flex-wrap gap-3 align-items-center">
          <span className="ems-guide-chip start">{t.guide.startLabel}</span>
          <span className="ems-fs-12 text-muted">1. {o.stepKey}</span>
          <span className="text-muted">→</span>
          <span className="ems-fs-12 text-muted">2. {o.stepImport}</span>
          <span className="text-muted">→</span>
          <span className="ems-fs-12 text-muted">3. {o.stepReview}</span>
          <span className="text-muted">→</span>
          <span className="ems-fs-12 text-muted">4. {o.stepPush}</span>
          <span className="ems-guide-chip done">{t.guide.doneLabel}</span>
        </div>
      </Card>

      <div className="card mb-4">
        <div className="card-header d-flex flex-wrap align-items-center justify-content-between gap-2">
          <div style={{ maxWidth: 360, width: "100%" }}>
            <Select label={t.portal.examsPage.title} value={examId} onChange={(e) => setExamId(e.target.value)} wrapperClassName="form-group mb-0">
              <option value="">— {t.common.filter} —</option>
              {exams.map((e) => (
                <option key={e.id} value={e.id}>
                  {locale === "bn" && e.nameBn ? e.nameBn : e.name}
                </option>
              ))}
            </Select>
          </div>
        </div>
        {!selectedExam ? (
          <div className="card-body">
            <EmptyState icon={<IconLayers />} title={o.pickExam} subtitle={o.pickExamHint} />
          </div>
        ) : papers.length === 0 ? (
          <div className="card-body">
            <EmptyState icon={<IconLayers />} title={o.noPapers} />
          </div>
        ) : (
          <>
            <div className="table-responsive">
              <table className="table table-hover align-middle mb-0">
                <thead className="ems-table-head">
                  <tr>
                    <th>{t.common.subject}</th>
                    <th>{t.common.date}</th>
                    <th className="text-center">{o.mcqFull}</th>
                    <th className="text-center">{o.keyCol}</th>
                    <th className="text-center">{o.responsesCol}</th>
                    <th className="text-end">{t.common.actions}</th>
                  </tr>
                </thead>
                <tbody>
                  {papersPager.paged.map((p) => (
                    <tr key={p.id} className={openPaper === p.id ? "table-success" : ""}>
                      <td className="f-w-600">{locale === "bn" && p.subjectBn ? p.subjectBn : p.subject}</td>
                      <td className="ems-fs-12">{formatDate(p.date, locale)}</td>
                      <td className="text-center f-w-700">{p.mcqMarks ?? "—"}</td>
                      <td className="text-center">
                        <span className={`badge ${p.keys ? "badge-success" : "badge-secondary"}`}>{p.keys}</span>
                      </td>
                      <td className="text-center">
                        <span className={`badge ${p.responses ? "badge-info" : "badge-secondary"}`}>
                          {p.responses}
                          {p.responses > 0 && <span className="ms-1">({p.matched} ✓)</span>}
                        </span>
                      </td>
                      <td className="text-end">
                        <div className="d-inline-flex gap-1">
                          <Button size="sm" variant="secondary" onClick={() => openKeyModal(p)}>
                            {o.addKey}
                          </Button>
                          <Button
                            size="sm"
                            variant={openPaper === p.id ? "primary" : "secondary"}
                            onClick={() => setOpenPaper(openPaper === p.id ? null : p.id)}
                          >
                            <IconClipboard className="svg-14" />
                            <span className="ms-1 d-none d-lg-inline">{o.manage}</span>
                          </Button>
                        </div>
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
            <Pagination {...papersPager} onPageChange={papersPager.setPage} onSizeChange={papersPager.setSize} />
          </>
        )}
      </div>

      {/* ---- selected paper management ---- */}
      {openPaper && (
        <Card title={`${o.manage} — ${detail.data?.schedule.subjectItem?.name ?? ""}`} icon={<IconLayers className="svg-20" />}>
          {detail.loading || !detail.data ? (
            <div className="card-body text-center py-4">
              <span className="spinner-border text-primary" role="status" />
            </div>
          ) : (
            <div className="card-body">
              {/* answer keys */}
              <h6 className="f-w-700 mb-3">{o.keysTitle}</h6>
              {detail.data.sheets.length === 0 ? (
                <EmptyState title={o.noKeys} subtitle={o.noKeysHint} />
              ) : (
                <div className="table-responsive mb-4">
                  <table className="table table-sm table-hover align-middle mb-0">
                    <thead className="ems-table-head">
                      <tr>
                        <th>{o.setCol}</th>
                        <th>{o.questionsCol}</th>
                        <th>{o.markEachCol}</th>
                        <th>{o.negativeCol}</th>
                        <th>{o.keyCol}</th>
                        <th className="text-center">{o.responsesCol}</th>
                        <th className="text-end">{t.common.actions}</th>
                      </tr>
                    </thead>
                    <tbody>
                      {detail.data.sheets.map((sh) => (
                        <tr key={sh.id}>
                          <td>
                            <span className="ems-type-pill">{sh.setLabel ?? "—"}</span>
                          </td>
                          <td className="f-w-700">{sh.questions}</td>
                          <td>{sh.markPerQuestion}</td>
                          <td>{sh.negativeMark}</td>
                          <td>
                            <code style={{ fontSize: 11, letterSpacing: 1 }}>
                              {sh.answerKey.length > 24 ? sh.answerKey.slice(0, 24) + "…" : sh.answerKey}
                            </code>
                          </td>
                          <td className="text-center">
                            <span className={`badge ${sh.responses.length ? "badge-info" : "badge-secondary"}`}>{sh.responses.length}</span>
                          </td>
                          <td className="text-end">
                            <div className="d-inline-flex gap-1">
                              <Button size="sm" variant="secondary" onClick={() => openKeyEdit(sh)}>
                                {t.common.edit}
                              </Button>
                              <Button size="sm" variant="secondary" onClick={() => deleteKey(sh)}>
                                <IconTrash className="svg-14" />
                              </Button>
                            </div>
                          </td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>
              )}

              {/* responses */}
              <div className="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
                <h6 className="f-w-700 mb-0">{o.responsesTitle}</h6>
                <div className="d-flex gap-2">
                  <Button size="sm" variant="primary" onClick={() => { setScanRows([]); setScanLog([]); setScanOpen(true); }} disabled={detail.data.sheets.length === 0}>
                    <IconCamera className="svg-14" />
                    <span className="ms-1">{o.scanBtn}</span>
                  </Button>
                  <Button size="sm" variant="secondary" onClick={() => { setPrintOpen(true); openPrint(); }} disabled={detail.data.sheets.length === 0}>
                    <IconPrinter className="svg-14" />
                    <span className="ms-1">{o.printBtn}</span>
                  </Button>
                  <Button size="sm" variant="secondary" onClick={() => { setImportText(""); setImportPreview(null); setImportSummary(null); setImportOpen(true); }} disabled={detail.data.sheets.length === 0}>
                    <IconUpload className="svg-14" />
                    <span className="ms-1">{o.importBtn}</span>
                  </Button>
                  <Button size="sm" variant="success" onClick={pushToMarks} disabled={!allResponses.some((r) => r.status === "MATCHED")}>
                    <IconCheck className="svg-14" />
                    <span className="ms-1">{o.pushBtn}</span>
                  </Button>
                </div>
              </div>
              {allResponses.length === 0 ? (
                <EmptyState title={o.noResponses} subtitle={o.noResponsesHint} />
              ) : (
                <>
                  <div className="table-responsive">
                    <table className="table table-sm table-hover align-middle mb-0">
                      <thead className="ems-table-head">
                        <tr>
                          <th>{t.portal.studentsPage.rollNo}</th>
                          <th>{t.common.name}</th>
                          <th>{o.setCol}</th>
                          <th className="text-center">{o.correctCol}</th>
                          <th className="text-center">{o.wrongCol}</th>
                          <th className="text-center">{o.blankCol}</th>
                          <th className="text-center">{o.marksCol}</th>
                          <th className="text-center">{t.common.status}</th>
                        </tr>
                      </thead>
                      <tbody>
                        {respPager.paged.map((r) => {
                          const set = detail.data!.sheets.find((s) => s.responses.some((x) => x.id === r.id))?.setLabel ?? null;
                          return (
                            <tr key={r.id}>
                              <td>
                                <span className="ems-roll-badge">{r.rawRoll}</span>
                              </td>
                              <td className="f-w-600">{r.studentName ?? <span className="text-danger">{r.rawRoll}</span>}</td>
                              <td>{set ? <span className="ems-type-pill">{set}</span> : "—"}</td>
                              <td className="text-center text-success f-w-700">{r.correct}</td>
                              <td className="text-center text-danger f-w-700">{r.wrong}</td>
                              <td className="text-center text-muted">{r.blank}</td>
                              <td className="text-center f-w-800">{r.mcqMarks}</td>
                              <td className="text-center">
                                <StatusBadge
                                  status={r.status === "MATCHED" ? "APPROVED" : r.status === "UNMATCHED" ? "PENDING" : "REJECTED"}
                                  label={r.status === "MATCHED" ? o.stMatched : r.status === "UNMATCHED" ? o.stUnmatched : o.stInvalid}
                                />
                                {r.statusNote && <p className="mb-0 ems-fs-10 text-muted">{r.statusNote}</p>}
                              </td>
                            </tr>
                          );
                        })}
                      </tbody>
                    </table>
                  </div>
                  <Pagination {...respPager} onPageChange={respPager.setPage} onSizeChange={respPager.setSize} />
                </>
              )}
            </div>
          )}
        </Card>
      )}

      {/* ---- key modal ---- */}
      <Modal
        open={keyOpen}
        onClose={() => setKeyOpen(false)}
        title={keyPaper ? `${o.addKey} — ${keyPaper.subject}` : o.editKey}
      >
        <div className="row g-2">
          <div className="col-6">
            <Input
              label={`${o.setCol} (A–Z)`}
              value={keyForm.setLabel}
              placeholder="A"
              maxLength={1}
              onChange={(e) => setKeyForm({ ...keyForm, setLabel: e.target.value.toUpperCase().replace(/[^A-Z]/g, "") })}
            />
          </div>
          <div className="col-6">
            <Input
              label={o.questionsCol}
              type="number"
              min="1"
              max="300"
              value={keyForm.questions}
              onChange={(e) => setKeyForm({ ...keyForm, questions: e.target.value })}
            />
          </div>
          <div className="col-6">
            <Input
              label={o.markEachCol}
              type="number"
              min="0.25"
              step="0.25"
              value={keyForm.markPerQuestion}
              onChange={(e) => setKeyForm({ ...keyForm, markPerQuestion: e.target.value })}
            />
          </div>
          <div className="col-6">
            <Input
              label={o.negativeCol}
              type="number"
              min="0"
              step="0.25"
              value={keyForm.negativeMark}
              onChange={(e) => setKeyForm({ ...keyForm, negativeMark: e.target.value })}
            />
          </div>
          <div className="col-12">
            <div className="form-group mb-3">
              <label className="col-form-label pt-0 f-w-600">{o.keyCol} (A/B/C/D, - = skipped)</label>
              <textarea
                className="form-control"
                rows={3}
                style={{ fontFamily: "Rubik, Consolas, monospace", letterSpacing: 2 }}
                value={keyForm.answerKey}
                placeholder="BACDA..."
                onChange={(e) => setKeyForm({ ...keyForm, answerKey: e.target.value.toUpperCase().replace(/[^ABCD-]/g, "") })}
              />
              <p className="mb-0 mt-1 ems-fs-11 text-muted">
                {keyForm.answerKey.length}/{keyForm.questions || "?"} {o.keyLenHint}
              </p>
            </div>
          </div>
        </div>
        <div className="d-flex justify-content-end gap-2">
          <Button variant="secondary" onClick={() => setKeyOpen(false)}>
            {t.common.cancel}
          </Button>
          <Button
            onClick={saveKey}
            loading={keySaving}
            disabled={
              !Number(keyForm.questions) ||
              keyForm.answerKey.length !== Number(keyForm.questions) ||
              !(Number(keyForm.markPerQuestion) > 0)
            }
          >
            {t.common.save}
          </Button>
        </div>
      </Modal>

      {/* ---- scan modal ---- */}
      <Modal open={scanOpen} onClose={() => setScanOpen(false)} title={o.scanTitle} wide>
        <p className="ems-fs-12 text-muted mb-2">{o.scanHint}</p>
        <div className="d-flex flex-wrap gap-2 mb-3">
          <label className="btn btn-sm btn-primary f-w-700 mb-0">
            <IconCamera className="svg-14" />
            <span className="ms-1">{o.scanPick}</span>
            <input
              type="file"
              accept="image/*"
              multiple
              capture="environment"
              className="d-none"
              onChange={(e) => { scanFiles(e.target.files); e.target.value = ""; }}
            />
          </label>
          <Button size="sm" variant="secondary" onClick={scanSample} loading={scanBusy}>
            {o.scanSampleBtn}
          </Button>
        </div>
        {scanBusy && (
          <div className="text-center py-3">
            <span className="spinner-border text-primary" role="status" />
            <p className="ems-fs-12 text-muted mt-2 mb-0">{o.scanWorking}</p>
          </div>
        )}
        {scanLog.length > 0 && (
          <pre className="p-2 rounded mb-2" style={{ background: "rgba(36,105,92,0.06)", fontSize: 11.5, maxHeight: 140, overflowY: "auto" }}>
{scanLog.join(String.fromCharCode(10))}
          </pre>
        )}
        {scanRows.length > 0 && (
          <>
            <div className="table-responsive mb-3" style={{ maxHeight: 200, overflowY: "auto" }}>
              <table className="table table-sm table-hover align-middle mb-0">
                <thead className="ems-table-head">
                  <tr>
                    <th>Roll</th>
                    <th>{o.setCol}</th>
                    <th>{o.responsesCol}</th>
                    <th className="text-center">{o.scanQuality}</th>
                  </tr>
                </thead>
                <tbody>
                  {scanRows.map((r, i) => (
                    <tr key={i}>
                      <td><code className="ems-roll-badge">{r.roll}</code></td>
                      <td>{r.set ?? "—"}</td>
                      <td><code style={{ fontSize: 11 }}>{r.responses}</code></td>
                      <td className="text-center f-w-700">{Math.round(r.quality * 100)}%</td>
                    </tr>
                  ))}
                </tbody>
              </table>
            </div>
            <div className="d-flex justify-content-end gap-2">
              <Button variant="secondary" onClick={() => setScanRows([])}>{t.common.cancel}</Button>
              <Button onClick={importScanned} loading={importBusy}>
                <IconCheck className="svg-16" />
                <span className="ms-1">{o.importBtn}</span>
              </Button>
            </div>
          </>
        )}
      </Modal>

      {/* ---- print modal ---- */}
      <Modal open={printOpen} onClose={() => setPrintOpen(false)} title={o.printTitle} wide>
        <div className="row g-2 mb-3">
          <div className="col-6 col-md-3">
            <Input label={o.setCol} value={printSet} maxLength={1} placeholder="A" onChange={(e) => setPrintSet(e.target.value.toUpperCase())} />
          </div>
          <div className="col-6 col-md-3">
            <Input label={o.questionsCol} type="number" min="1" max="30" value={printQuestions} onChange={(e) => setPrintQuestions(e.target.value)} />
          </div>
          <div className="col-md-6 d-flex align-items-end gap-2">
            <Button variant="secondary" onClick={openPrint} loading={printBusy}>
              <IconCheck className="svg-14" />
              <span className="ms-1">{o.printRegenerate}</span>
            </Button>
            <Button onClick={() => window.print()} disabled={!printUrls.length}>
              <IconPrinter className="svg-14" />
              <span className="ms-1">{o.printBtn}</span>
            </Button>
          </div>
        </div>
        <p className="ems-fs-11 text-muted mb-2">{o.printHint}</p>
        {printBusy ? (
          <div className="text-center py-4"><span className="spinner-border text-primary" role="status" /></div>
        ) : (
          <div style={{ maxHeight: 420, overflowY: "auto" }} className="ems-omr-print-area">
            {printUrls.map((url, i) => (
              <img key={i} src={url} alt={`sheet-${i}`} style={{ width: "100%", maxWidth: 480, display: "block", margin: "0 auto 12px", border: "1px solid #e6edef" }} />
            ))}
          </div>
        )}
      </Modal>

      {/* ---- import modal ---- */}
      <Modal open={importOpen} onClose={() => setImportOpen(false)} title={o.importTitle} wide>
        <p className="ems-fs-12 text-muted mb-2">{o.importHint}</p>
        <code className="ems-invoice-badge d-block mb-2" style={{ whiteSpace: "pre" }}>{"roll,set,responses\n1001,A,BADCA-CB...\n1002,B,ABDC--CA..."}</code>
        <div className="form-group mb-3">
          <textarea
            className="form-control"
            rows={7}
            style={{ fontFamily: "Rubik, Consolas, monospace" }}
            value={importText}
            placeholder={"1001,A,BADCA-CB\n1002,B,ABDC--CA"}
            onChange={(e) => setImportText(e.target.value)}
          />
        </div>
        {importSummary && (
          <div className="mb-3 d-flex flex-wrap gap-2">
            <span className="badge badge-info">{o.savedCol}: {importSummary.saved}</span>
            <span className="badge badge-success">{o.stMatched}: {importSummary.matched}</span>
            <span className="badge badge-warning">{o.stUnmatched}: {importSummary.unmatched}</span>
            <span className="badge badge-danger">{o.stInvalid}: {importSummary.invalid}</span>
          </div>
        )}
        {importPreview && importPreview.length > 0 && (
          <div className="table-responsive mb-3" style={{ maxHeight: 220, overflowY: "auto" }}>
            <table className="table table-sm table-hover align-middle mb-0">
              <thead className="ems-table-head">
                <tr>
                  <th>Roll</th>
                  <th>{o.setCol}</th>
                  <th className="text-center">{o.correctCol}</th>
                  <th className="text-center">{o.wrongCol}</th>
                  <th className="text-center">{o.marksCol}</th>
                  <th className="text-center">{t.common.status}</th>
                </tr>
              </thead>
              <tbody>
                {importPreview.map((p, i) => (
                  <tr key={i}>
                    <td><code className="ems-roll-badge">{p.roll}</code></td>
                    <td>{p.set ?? "—"}</td>
                    <td className="text-center text-success f-w-700">{p.correct}</td>
                    <td className="text-center text-danger f-w-700">{p.wrong}</td>
                    <td className="text-center f-w-800">{p.marks}</td>
                    <td className="text-center">
                      <span className={`badge ${p.status === "MATCHED" ? "badge-success" : p.status === "UNMATCHED" ? "badge-warning" : "badge-danger"}`}>
                        {p.status}
                      </span>
                      {p.note && <p className="mb-0 ems-fs-10 text-muted">{p.note}</p>}
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        )}
        <div className="d-flex justify-content-end gap-2">
          <Button variant="secondary" onClick={() => setImportOpen(false)}>
            {t.common.close}
          </Button>
          <Button onClick={runImport} loading={importBusy} disabled={!importText.trim()}>
            <IconUpload className="svg-16" />
            <span className="ms-1">{o.importBtn}</span>
          </Button>
        </div>
      </Modal>
    </div>
  );
}
