"use client";

import { useEffect, useRef, useState } from "react";
import { useLang } from "@/context/LanguageContext";
import { Alert, Button, Card, EmptyState, Input, Modal, PageLoader, Select } from "@/components/ui";
import { api, useApi } from "@/lib/client";
import { formatDateTime } from "@/lib/utils";
import {
  IconCamera, IconCard, IconCheck, IconClock, IconEdit, IconKey, IconPlus, IconTrash,
  IconUpload, IconUserCheck, IconUsers, IconWatch,
} from "@/components/icons";
import { emsConfirm } from "@/components/ConfirmDialog";

/* Control Panel → Attendance Machines (ZKTeco & universal).
   - Registry with per-device config (brand presets, capabilities, mode)
   - ZK: Test Connection / Sync Now / auto-poll minutes
   - CSV import wizard (works with every brand's export)
   - Punch feed + unmatched queue with person mapping */

interface Machine {
  id: number;
  name: string;
  brand: string;
  mode: string;
  hasCard: boolean;
  hasFinger: boolean;
  hasFace: boolean;
  ipAddress: string | null;
  port: number;
  serialNumber: string | null;
  location: string | null;
  apiKey: string;
  pollMinutes: number | null;
  isActive: boolean;
  lastSeenAt: string | null;
  lastPunchAt: string | null;
  note: string | null;
  totalPunches: number;
  todayPunches: number;
}

interface PunchRow {
  id: number;
  time: string;
  source: string;
  machine: string | null;
  identifier: string;
  matchStatus: string;
  student: { roll: string; name: string; nameBn: string | null } | null;
  staff: { employeeCode: string; name: string; nameBn: string | null } | null;
}

interface CredentialRow {
  id: number;
  identifier: string;
  machineUserId: string | null;
  source: string;
  studentId: number | null;
  staffId: number | null;
  student: { roll: string; name: string; nameBn: string | null; rfidCardNo: string | null } | null;
  staff: { employeeCode: string; name: string; nameBn: string | null } | null;
}

const emptyForm = {
  name: "",
  brand: "ZKTECO",
  mode: "ZK_POLL",
  hasCard: true,
  hasFinger: false,
  hasFace: false,
  ipAddress: "",
  port: "4370",
  serialNumber: "",
  location: "",
  pollMinutes: "5",
  isActive: true,
  note: "",
};

export default function AttendanceMachinesCard() {
  const { t, locale } = useLang();
  const m = t.portal.controlPanelPage;
  const { data, loading, reload } = useApi<{ machines: Machine[]; unmatched: number }>(
    "/api/portal/attendance-machines"
  );
  const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null);

  // registry modal
  const [modal, setModal] = useState(false);
  const [editing, setEditing] = useState<Machine | null>(null);
  const [form, setForm] = useState(emptyForm);
  const [saving, setSaving] = useState(false);
  const [testing, setTesting] = useState(false);
  const [syncing, setSyncing] = useState(false);

  // punch feed / mapping
  const [feedOpen, setFeedOpen] = useState(false);
  const [feedTab, setFeedTab] = useState<"recent" | "unmatched">("recent");
  const [feed, setFeed] = useState<PunchRow[] | null>(null);
  const [mapPunch, setMapPunch] = useState<PunchRow | null>(null);
  const [mapKind, setMapKind] = useState<"STUDENT" | "STAFF">("STUDENT");
  const [mapPick, setMapPick] = useState("");
  const [mapSaving, setMapSaving] = useState(false);
  const students = useApi<{ students: { id: number; roll: string; name: string; rfidCardNo?: string | null }[] }>("/api/portal/students");
  const staff = useApi<{ staff: { id: number; employeeCode: string; name: string }[] }>("/api/portal/hr");

  // CSV wizard
  const [csvOpen, setCsvOpen] = useState(false);
  const [csvText, setCsvText] = useState("");
  const [csvMap, setCsvMap] = useState<{ idColumn: string; dateColumn: string; timeColumn: string; dateOrder: string } | null>(null);
  const [csvPreview, setCsvPreview] = useState<{ columns: string[]; rows: number; detected: { idColumn: string; dateColumn: string; timeColumn?: string; dateOrder?: string } | null; preview: Record<string, string>[] } | null>(null);
  const [csvMachine, setCsvMachine] = useState("");
  const [csvBusy, setCsvBusy] = useState(false);
  const csvFile = useRef<HTMLInputElement | null>(null);

  // RFID card registry (pre-registered device users)
  const [regOpen, setRegOpen] = useState(false);
  const [regRows, setRegRows] = useState<CredentialRow[] | null>(null);
  const [regSearch, setRegSearch] = useState("");
  const [regFormOpen, setRegFormOpen] = useState(false);
  const [regEdit, setRegEdit] = useState<CredentialRow | null>(null);
  const [regKind, setRegKind] = useState<"STUDENT" | "STAFF">("STUDENT");
  const [regPick, setRegPick] = useState("");
  const [regIdentifier, setRegIdentifier] = useState("");
  const [regMachineUserId, setRegMachineUserId] = useState("");
  const [regSaveToStudent, setRegSaveToStudent] = useState(true);
  const [regSaving, setRegSaving] = useState(false);

  useEffect(() => {
    if (editing) {
      setForm({
        name: editing.name,
        brand: editing.brand,
        mode: editing.mode,
        hasCard: editing.hasCard,
        hasFinger: editing.hasFinger,
        hasFace: editing.hasFace,
        ipAddress: editing.ipAddress ?? "",
        port: String(editing.port),
        serialNumber: editing.serialNumber ?? "",
        location: editing.location ?? "",
        pollMinutes: editing.pollMinutes ? String(editing.pollMinutes) : "5",
        isActive: editing.isActive,
        note: editing.note ?? "",
      });
    } else {
      setForm(emptyForm);
    }
  }, [editing, modal]);

  function pickBrand(brand: string) {
    const mode = brand === "ZKTECO" ? "ZK_POLL" : brand === "GENERIC_HTTP" ? "HTTP_PUSH" : "CSV_ONLY";
    setForm((f) => ({ ...f, brand, mode }));
  }

  async function save(e: React.FormEvent) {
    e.preventDefault();
    setSaving(true);
    setMessage(null);
    const body = {
      ...form,
      port: Number(form.port) || 4370,
      pollMinutes: Number(form.pollMinutes) || null,
      serialNumber: form.serialNumber || undefined,
    };
    try {
      if (editing) {
        await api(`/api/portal/attendance-machines/${editing.id}`, { method: "PUT", body: JSON.stringify(body) });
        setMessage({ type: "success", text: m.amUpdated });
      } else {
        await api("/api/portal/attendance-machines", { method: "POST", body: JSON.stringify(body) });
        setMessage({ type: "success", text: m.amCreated });
      }
      setModal(false);
      reload();
    } catch {
      setMessage({ type: "error", text: t.common.tryAgain });
    } finally {
      setSaving(false);
    }
  }

  async function removeMachine(machine: Machine) {
    if (!(await emsConfirm({ message: t.common.confirmDelete, danger: true }))) return;
    try {
      await api(`/api/portal/attendance-machines/${machine.id}`, { method: "DELETE" });
      reload();
    } catch {
      setMessage({ type: "error", text: t.common.tryAgain });
    }
  }

  async function machineAction(machine: Machine, kind: "test" | "sync") {
    kind === "test" ? setTesting(true) : setSyncing(true);
    setMessage(null);
    try {
      const res = await api<{
        ok: boolean; serialNumber?: string; deviceUsers?: number;
        created?: number; matched?: number; unmatched?: number; error?: string;
      }>(`/api/portal/attendance-machines/${machine.id}/${kind}`, { method: "POST" });
      if (kind === "test") {
        setMessage({
          type: "success",
          text: `${m.amTestOk} — SN ${res.serialNumber ?? "?"} · ${res.deviceUsers ?? 0} ${m.amUsers}`,
        });
      } else {
        setMessage({
          type: "success",
          text: `${m.amSynced}: +${res.created ?? 0} (${m.amMatched} ${res.matched ?? 0}, ${m.amUnmatched} ${res.unmatched ?? 0})`,
        });
      }
      reload();
    } catch (err) {
      const code = err instanceof Error ? err.message : "";
      setMessage({
        type: "error",
        text: code === "DEVICE_UNREACHABLE" ? m.amUnreachable : code === "IP_REQUIRED" ? m.amIpRequired : t.common.tryAgain,
      });
    } finally {
      kind === "test" ? setTesting(false) : setSyncing(false);
    }
  }

  async function openFeed(tab: "recent" | "unmatched") {
    setFeedTab(tab);
    setFeedOpen(true);
    setFeed(null);
    await loadFeed(tab);
  }

  async function loadFeed(tab: "recent" | "unmatched") {
    try {
      const res = await api<{ punches: PunchRow[] }>(`/api/portal/attendance-machines/punches?filter=${tab}`);
      setFeed(res.punches);
    } catch {
      setFeed([]);
    }
  }

  async function submitMap() {
    if (!mapPunch || !mapPick) return;
    setMapSaving(true);
    try {
      const res = await api<{ applied: number }>("/api/portal/attendance-machines/punches/match", {
        method: "POST",
        body: JSON.stringify({
          punchId: mapPunch.id,
          studentId: mapKind === "STUDENT" ? Number(mapPick) : null,
          staffId: mapKind === "STAFF" ? Number(mapPick) : null,
        }),
      });
      setMessage({ type: "success", text: m.amMapped.replace("{n}", String(res.applied)) });
      setMapPunch(null);
      setMapPick("");
      await loadFeed(feedTab);
      reload();
    } catch {
      setMessage({ type: "error", text: t.common.tryAgain });
    } finally {
      setMapSaving(false);
    }
  }

  /* ---------------- CSV wizard ---------------- */

  function onCsvPick(e: React.ChangeEvent<HTMLInputElement>) {
    const file = e.target.files?.[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = () => {
      setCsvText(String(reader.result ?? ""));
      setCsvPreview(null);
      setCsvMap(null);
      analyze(String(reader.result ?? ""));
    };
    reader.readAsText(file);
  }

  async function analyze(text: string) {
    setCsvBusy(true);
    try {
      const res = await api<{ columns: string[]; rows: number; detected: { idColumn: string; dateColumn: string; timeColumn?: string; dateOrder?: string } | null; preview: Record<string, string>[] }>(
        "/api/portal/attendance-machines/import",
        { method: "POST", body: JSON.stringify({ csvText: text, dryRun: true }) }
      );
      setCsvPreview(res);
      if (res.detected) {
        setCsvMap({
          idColumn: res.detected.idColumn,
          dateColumn: res.detected.dateColumn,
          timeColumn: res.detected.timeColumn ?? "",
          dateOrder: res.detected.dateOrder ?? "DMY",
        });
      }
    } catch {
      setMessage({ type: "error", text: t.common.tryAgain });
    } finally {
      setCsvBusy(false);
    }
  }

  async function runImport() {
    if (!csvMap) return;
    setCsvBusy(true);
    try {
      const res = await api<{ created: number; matched: number; unmatched: number; duplicates: number }>(
        "/api/portal/attendance-machines/import",
        {
          method: "POST",
          body: JSON.stringify({
            csvText,
            mapping: csvMap,
            machineId: csvMachine ? Number(csvMachine) : undefined,
          }),
        }
      );
      setMessage({
        type: "success",
        text: m.amImported
          .replace("{created}", String(res.created))
          .replace("{matched}", String(res.matched))
          .replace("{unmatched}", String(res.unmatched))
          .replace("{duplicates}", String(res.duplicates)),
      });
      setCsvOpen(false);
      reload();
    } catch (err) {
      const code = err instanceof Error ? err.message : "";
      setMessage({ type: "error", text: code === "NO_VALID_ROWS" ? m.amNoRows : t.common.tryAgain });
    } finally {
      setCsvBusy(false);
    }
  }

  /* ---------------- Card & ID registry ---------------- */

  async function loadRegistry() {
    setRegRows(null);
    try {
      const res = await api<{ credentials: CredentialRow[] }>("/api/portal/attendance-machines/credentials");
      setRegRows(res.credentials);
    } catch {
      setRegRows([]);
    }
  }

  function openRegistry() {
    setRegOpen(true);
    setRegSearch("");
    setRegFormOpen(false);
    setRegEdit(null);
    loadRegistry();
  }

  /** start an add/edit entry: pre-fill from the person's saved card no */
  function startRegEdit(row: CredentialRow | null) {
    setRegEdit(row);
    if (row) {
      setRegKind(row.studentId ? "STUDENT" : "STAFF");
      setRegPick(String(row.studentId ?? row.staffId ?? ""));
      setRegIdentifier(row.identifier);
      setRegMachineUserId(row.machineUserId ?? "");
    } else {
      setRegKind("STUDENT");
      setRegPick("");
      setRegIdentifier("");
      setRegMachineUserId("");
    }
    setRegSaveToStudent(true);
    setRegFormOpen(true);
  }

  /** picking a person pre-fills the identifier with their saved RFID card no */
  function pickRegPerson(value: string) {
    setRegPick(value);
    if (regKind === "STUDENT") {
      const s = (students.data?.students ?? []).find((x) => x.id === Number(value));
      if (s && s.rfidCardNo) setRegIdentifier(s.rfidCardNo);
    }
  }

  async function saveCredential(e: React.FormEvent) {
    e.preventDefault();
    if (!regPick || !regIdentifier.trim()) return;
    setRegSaving(true);
    try {
      const res = await api<{ applied: number }>("/api/portal/attendance-machines/credentials", {
        method: "POST",
        body: JSON.stringify({
          identifier: regIdentifier.trim(),
          studentId: regKind === "STUDENT" ? Number(regPick) : null,
          staffId: regKind === "STAFF" ? Number(regPick) : null,
          machineUserId: regMachineUserId.trim() || null,
          saveToStudent: regSaveToStudent,
        }),
      });
      setMessage({
        type: "success",
        text: (regEdit ? m.amCardUpdated : m.amCardSaved) +
          (res.applied ? ` ${m.amAppliedPunches.replace("{n}", String(res.applied))}` : ""),
      });
      setRegEdit(null);
      setRegFormOpen(false);
      await loadRegistry();
      reload();
    } catch (err) {
      const code = err instanceof Error ? err.message : "";
      setMessage({
        type: "error",
        text: code === "IDENTIFIER_TAKEN" ? m.amIdentifierTaken : t.common.tryAgain,
      });
    } finally {
      setRegSaving(false);
    }
  }

  async function removeCredential(row: CredentialRow) {
    if (!(await emsConfirm({ message: t.common.confirmDelete, danger: true }))) return;
    try {
      await api(`/api/portal/attendance-machines/credentials/${row.id}`, { method: "DELETE" });
      await loadRegistry();
    } catch {
      setMessage({ type: "error", text: t.common.tryAgain });
    }
  }

  const machines = data?.machines ?? [];
  const brandLabel = (b: string) => (b === "ZKTECO" ? "ZKTeco" : b === "GENERIC_HTTP" ? m.amGenericHttp : m.amCsvOnly);

  return (
    <Card>
      <div className="card-header d-flex flex-wrap align-items-center justify-content-between gap-2">
        <div>
          <h5 className="mb-0 f-w-700">{m.amTitle}</h5>
          <p className="mb-0 ems-fs-12 text-muted">{m.amSubtitle}</p>
        </div>
        <div className="d-flex gap-2 flex-wrap">
          <Button size="sm" variant="secondary" onClick={openRegistry}>
            <IconCard className="svg-14" />
            <span className="ms-1">{m.amRegistry}</span>
          </Button>
          <Button size="sm" variant="secondary" onClick={() => { setCsvOpen(true); setCsvText(""); setCsvPreview(null); setCsvMap(null); }}>
            <IconUpload className="svg-14" />
            <span className="ms-1">{m.amImportCsv}</span>
          </Button>
          <Button size="sm" variant="secondary" onClick={() => openFeed("recent")}>
            <IconWatch className="svg-14" />
            <span className="ms-1">{m.amFeed}</span>
            {!!data?.unmatched && (
              <span className="badge badge-danger ms-1">{data.unmatched}</span>
            )}
          </Button>
          <Button size="sm" onClick={() => { setEditing(null); setModal(true); }}>
            <IconPlus className="svg-14" />
            <span className="ms-1">{m.amAdd}</span>
          </Button>
        </div>
      </div>

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

      <div className="card-body">
        {loading ? (
          <PageLoader text={t.common.loading} />
        ) : machines.length === 0 ? (
          <EmptyState icon={<IconWatch />} title={m.amEmpty} subtitle={m.amEmptyHint} />
        ) : (
          <div className="table-responsive">
            <table className="table table-hover align-middle mb-0">
              <thead className="ems-table-head">
                <tr>
                  <th>{t.common.name}</th>
                  <th>{m.amCapabilities}</th>
                  <th>{m.amMode}</th>
                  <th className="text-center">{m.amToday}</th>
                  <th>{m.amLastSeen}</th>
                  <th className="text-center">{t.common.status}</th>
                  <th className="text-end">{t.common.actions}</th>
                </tr>
              </thead>
              <tbody>
                {machines.map((mac) => (
                  <tr key={mac.id} className={mac.isActive ? "" : "opacity-50"}>
                    <td>
                      <div className="f-w-700">{mac.name}</div>
                      <div className="ems-fs-11 text-muted">
                        {brandLabel(mac.brand)}
                        {mac.ipAddress ? ` · ${mac.ipAddress}:${mac.port}` : ""}
                        {mac.serialNumber ? ` · SN ${mac.serialNumber}` : ""}
                      </div>
                    </td>
                    <td>
                      <div className="d-flex gap-1">
                        {mac.hasCard && <span className="badge badge-soft-teal">{t.common.card}</span>}
                        {mac.hasFinger && <span className="badge badge-info">{m.amFinger}</span>}
                        {mac.hasFace && <span className="badge badge-secondary">{m.amFace}</span>}
                      </div>
                    </td>
                    <td>
                      <span className="badge badge-secondary">
                        {mac.mode === "ZK_POLL" ? m.amModePoll : mac.mode === "HTTP_PUSH" ? m.amModePush : m.amModeCsv}
                      </span>
                    </td>
                    <td className="text-center f-w-700">{mac.todayPunches}</td>
                    <td className="ems-fs-12 text-muted">
                      {mac.lastSeenAt ? formatDateTime(mac.lastSeenAt, locale) : "—"}
                    </td>
                    <td className="text-center">
                      <span className={`badge ${mac.isActive ? "badge-success" : "badge-secondary"}`}>
                        {mac.isActive ? t.common.active : t.common.inactive}
                      </span>
                    </td>
                    <td className="text-end">
                      <div className="d-inline-flex gap-1 flex-wrap justify-content-end">
                        {mac.mode === "ZK_POLL" && (
                          <>
                            <Button size="sm" variant="secondary" loading={testing} onClick={() => machineAction(mac, "test")}>
                              {m.amTest}
                            </Button>
                            <Button size="sm" variant="secondary" loading={syncing} onClick={() => machineAction(mac, "sync")}>
                              {m.amSync}
                            </Button>
                          </>
                        )}
                        <Button size="sm" variant="secondary" onClick={() => { setEditing(mac); setModal(true); }}>
                          <IconEdit className="svg-14" />
                        </Button>
                        <Button size="sm" variant="secondary" onClick={() => removeMachine(mac)}>
                          <IconTrash className="svg-14" />
                        </Button>
                      </div>
                    </td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        )}
        {!!data?.unmatched && machines.length > 0 && (
          <div className="alert alert-warning ems-fs-12 mt-3 mb-0 d-flex align-items-center justify-content-between">
            <span>{m.amUnmatchedHint.replace("{n}", String(data.unmatched))}</span>
            <button type="button" className="btn btn-sm btn-primary" onClick={() => openFeed("unmatched")}>
              {m.amMapNow}
            </button>
          </div>
        )}
      </div>

      {/* add / edit machine */}
      <Modal open={modal} onClose={() => setModal(false)} title={editing ? m.amEdit : m.amAdd}>
        <form className="theme-form" onSubmit={save}>
          <Input label={t.common.name} required value={form.name} placeholder="Gate 1 — ZKTeco K40" onChange={(e) => setForm({ ...form, name: e.target.value })} />
          <div className="row g-2">
            <div className="col-6">
              <Select label={m.amBrand} value={form.brand} onChange={(e) => pickBrand(e.target.value)}>
                <option value="ZKTECO">ZKTeco</option>
                <option value="GENERIC_HTTP">{m.amGenericHttp}</option>
                <option value="CSV_ONLY">{m.amCsvOnly}</option>
              </Select>
            </div>
            <div className="col-6">
              <Select label={m.amMode} value={form.mode} onChange={(e) => setForm({ ...form, mode: e.target.value })}>
                <option value="ZK_POLL">{m.amModePoll}</option>
                <option value="HTTP_PUSH">{m.amModePush}</option>
                <option value="CSV_ONLY">{m.amModeCsv}</option>
              </Select>
            </div>
          </div>

          <label className="col-form-label pt-0 f-w-600 ems-fs-12">{m.amCapabilities}</label>
          <div className="d-flex gap-3 mb-3">
            {([["hasCard", t.common.card], ["hasFinger", m.amFinger], ["hasFace", m.amFace]] as const).map(([key, label]) => (
              <div className="form-check" key={key}>
                <input
                  className="form-check-input"
                  type="checkbox"
                  id={"cap-" + key}
                  checked={form[key]}
                  onChange={(e) => setForm({ ...form, [key]: e.target.checked })}
                />
                <label className="form-check-label ems-fs-12" htmlFor={"cap-" + key}>{label}</label>
              </div>
            ))}
          </div>

          {form.mode !== "CSV_ONLY" && (
            <div className="row g-2">
              <div className="col-6">
                <Input label={m.amIp} value={form.ipAddress} placeholder="192.168.1.201" onChange={(e) => setForm({ ...form, ipAddress: e.target.value })} />
              </div>
              <div className="col-6">
                <Input label={m.amPort} type="number" value={form.port} onChange={(e) => setForm({ ...form, port: e.target.value })} />
              </div>
            </div>
          )}
          {form.mode === "ZK_POLL" && (
            <Input label={m.amPollMinutes} type="number" min="1" value={form.pollMinutes} onChange={(e) => setForm({ ...form, pollMinutes: e.target.value })} />
          )}
          <div className="row g-2">
            <div className="col-6">
              <Input label={m.amLocation} value={form.location} placeholder="Main Gate" onChange={(e) => setForm({ ...form, location: e.target.value })} />
            </div>
            <div className="col-6">
              <Input label={m.amSerial} value={form.serialNumber} onChange={(e) => setForm({ ...form, serialNumber: e.target.value })} />
            </div>
          </div>
          <Input label={t.common.note} value={form.note} onChange={(e) => setForm({ ...form, note: e.target.value })} />

          {editing && (
            <div className="p-2 mb-3 rounded d-flex align-items-center gap-2" style={{ background: "rgba(36,105,92,0.06)" }}>
              <IconKey className="svg-14 text-muted" />
              <code className="ems-invoice-badge" style={{ fontSize: 10 }}>{editing.apiKey}</code>
              <button
                type="button"
                className="btn btn-sm btn-outline-secondary ems-fs-11"
                onClick={() => navigator.clipboard?.writeText(editing.apiKey)}
              >
                {t.notify.testSend === "Test" ? "Copy" : "Copy"}
              </button>
            </div>
          )}
          <p className="ems-fs-11 text-muted">{m.amApiKeyHint}</p>

          <div className="form-check form-switch mb-3">
            <input className="form-check-input" type="checkbox" role="switch" id="am-active" checked={form.isActive} onChange={(e) => setForm({ ...form, isActive: e.target.checked })} />
            <label className="form-check-label ems-fs-12 f-w-600" htmlFor="am-active">{t.common.active}</label>
          </div>

          <div className="d-flex justify-content-end gap-2">
            <Button type="button" variant="secondary" onClick={() => setModal(false)}>{t.common.cancel}</Button>
            <Button type="submit" loading={saving}>{t.common.save}</Button>
          </div>
        </form>
      </Modal>

      {/* punch feed + unmatched */}
      <Modal open={feedOpen} onClose={() => setFeedOpen(false)} title={m.amFeed} wide>
        <ul className="nav nav-tabs mb-3">
          <li className="nav-item flex-fill text-center">
            <button type="button" className={`nav-link w-100 ${feedTab === "recent" ? "active" : ""}`} onClick={() => { setFeedTab("recent"); loadFeed("recent"); }}>
              <IconClock className="svg-14" /> {m.amRecent}
            </button>
          </li>
          <li className="nav-item flex-fill text-center">
            <button type="button" className={`nav-link w-100 ${feedTab === "unmatched" ? "active" : ""}`} onClick={() => { setFeedTab("unmatched"); loadFeed("unmatched"); }}>
              <IconUserCheck className="svg-14" /> {m.amUnmatched} {!!data?.unmatched && <span className="badge badge-danger ms-1">{data.unmatched}</span>}
            </button>
          </li>
        </ul>
        {feed === null ? (
          <PageLoader text={t.common.loading} />
        ) : feed.length === 0 ? (
          <EmptyState icon={<IconWatch />} title={t.common.noData} />
        ) : (
          <div className="table-responsive" style={{ maxHeight: 420, overflowY: "auto" }}>
            <table className="table table-hover align-middle mb-0 ems-fs-12">
              <thead className="ems-table-head">
                <tr>
                  <th>{m.amPunchTime}</th>
                  <th>{m.amIdentifier}</th>
                  <th>{m.amPerson}</th>
                  <th>{t.common.type}</th>
                  {feedTab === "unmatched" && <th className="text-end">{t.common.actions}</th>}
                </tr>
              </thead>
              <tbody>
                {feed.map((p) => (
                  <tr key={p.id}>
                    <td className="text-nowrap">{formatDateTime(p.time, locale)}</td>
                    <td><code className="ems-invoice-badge">{p.identifier}</code></td>
                    <td>
                      {p.student ? (
                        <span><span className="ems-roll-badge me-1">{p.student.roll}</span>{locale === "bn" && p.student.nameBn ? p.student.nameBn : p.student.name}</span>
                      ) : p.staff ? (
                        <span><span className="ems-roll-badge me-1">{p.staff.employeeCode}</span>{locale === "bn" && p.staff.nameBn ? p.staff.nameBn : p.staff.name}</span>
                      ) : (
                        <span className="text-danger f-w-600">{m.amUnmatchedShort}</span>
                      )}
                    </td>
                    <td>
                      <span className="badge badge-secondary">{p.source === "ZK_POLL" ? "ZKTeco" : p.source === "CSV" ? "CSV" : "HTTP"}</span>
                      {p.machine && <span className="ems-fs-11 text-muted ms-1">{p.machine}</span>}
                    </td>
                    {feedTab === "unmatched" && (
                      <td className="text-end">
                        <Button size="sm" onClick={() => { setMapPunch(p); setMapKind("STUDENT"); setMapPick(""); }}>
                          <IconUsers className="svg-14" />
                          <span className="ms-1">{m.amMap}</span>
                        </Button>
                      </td>
                    )}
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        )}
      </Modal>

      {/* map a punch to a person */}
      <Modal open={!!mapPunch} onClose={() => setMapPunch(null)} title={`${m.amMap} — ${mapPunch?.identifier ?? ""}`}>
        {mapPunch && (
          <div>
            <p className="ems-fs-12 text-muted">
              {formatDateTime(mapPunch.time, locale)} · {m.amMapHint}
            </p>
            <div className="btn-group w-100 mb-3">
              <button type="button" className={`btn btn-sm f-w-600 ${mapKind === "STUDENT" ? "btn-primary" : "btn-outline-secondary"}`} onClick={() => { setMapKind("STUDENT"); setMapPick(""); }}>
                {t.portal.studentsPage.title}
              </button>
              <button type="button" className={`btn btn-sm f-w-600 ${mapKind === "STAFF" ? "btn-primary" : "btn-outline-secondary"}`} onClick={() => { setMapKind("STAFF"); setMapPick(""); }}>
                {t.portal.hrPage.title}
              </button>
            </div>
            {mapKind === "STUDENT" ? (
              <Select label={t.portal.studentsPage.title} value={mapPick} onChange={(e) => setMapPick(e.target.value)}>
                <option value="">—</option>
                {(students.data?.students ?? []).map((s) => (
                  <option key={s.id} value={s.id}>{s.roll} — {s.name}</option>
                ))}
              </Select>
            ) : (
              <Select label={t.portal.hrPage.title} value={mapPick} onChange={(e) => setMapPick(e.target.value)}>
                <option value="">—</option>
                {(staff.data?.staff ?? []).map((s) => (
                  <option key={s.id} value={s.id}>{s.employeeCode} — {s.name}</option>
                ))}
              </Select>
            )}
            <div className="d-flex justify-content-end gap-2 pt-3">
              <Button variant="secondary" onClick={() => setMapPunch(null)}>{t.common.cancel}</Button>
              <Button onClick={submitMap} loading={mapSaving} disabled={!mapPick}>
                <IconCheck className="svg-14" />
                <span className="ms-1">{m.amMap}</span>
              </Button>
            </div>
          </div>
        )}
      </Modal>

      {/* Card & ID registry — pre-registered device users */}
      <Modal open={regOpen && !regFormOpen} onClose={() => setRegOpen(false)} title={m.amRegistry} wide>
        <p className="ems-fs-12 text-muted mb-3">{m.amRegistryHint}</p>
        <div className="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3">
          <div className="has-search" style={{ maxWidth: 320 }}>
            <span className="fa fa-search form-control-feedback" />
            <input
              className="form-control"
              placeholder={t.common.search}
              value={regSearch}
              onChange={(e) => setRegSearch(e.target.value)}
            />
          </div>
          <Button onClick={() => startRegEdit(null)}>
            <IconPlus className="svg-14" />
            <span className="ms-1">{m.amAddCard}</span>
          </Button>
        </div>
        {regRows === null ? (
          <PageLoader text={t.common.loading} />
        ) : regRows.length === 0 ? (
          <EmptyState icon={<IconCard />} title={m.amRegistryEmpty} subtitle={m.amRegistryEmptyHint} />
        ) : (
          <div className="table-responsive" style={{ maxHeight: 420, overflowY: "auto" }}>
            <table className="table table-hover align-middle mb-0 ems-fs-12">
              <thead className="ems-table-head">
                <tr>
                  <th>{m.amIdentifier}</th>
                  <th>{m.amPerson}</th>
                  <th>{t.common.card}</th>
                  <th className="text-end">{t.common.actions}</th>
                </tr>
              </thead>
              <tbody>
                {regRows
                  .filter((r) => {
                    const q = regSearch.trim().toLowerCase();
                    if (!q) return true;
                    const person = r.student ?? r.staff;
                    return (
                      r.identifier.toLowerCase().includes(q) ||
                      (person?.name ?? "").toLowerCase().includes(q) ||
                      (r.student?.roll ?? r.staff?.employeeCode ?? "").toLowerCase().includes(q)
                    );
                  })
                  .map((r) => (
                    <tr key={r.id}>
                      <td className="text-nowrap">
                        <code className="ems-invoice-badge">{r.identifier}</code>
                        {r.machineUserId && (
                          <span className="ems-fs-11 text-muted ms-1">· UID {r.machineUserId}</span>
                        )}
                      </td>
                      <td>
                        {r.student ? (
                          <span><span className="ems-roll-badge me-1">{r.student.roll}</span>{locale === "bn" && r.student.nameBn ? r.student.nameBn : r.student.name}</span>
                        ) : r.staff ? (
                          <span><span className="ems-roll-badge me-1">{r.staff.employeeCode}</span>{locale === "bn" && r.staff.nameBn ? r.staff.nameBn : r.staff.name}</span>
                        ) : (
                          <span className="text-danger f-w-600">—</span>
                        )}
                      </td>
                      <td>
                        <span className={`badge ${r.source === "MANUAL" ? "badge-soft-teal" : "badge-secondary"}`}>
                          {r.source === "MANUAL" ? m.amSourceManual : m.amSourceAuto}
                        </span>
                      </td>
                      <td className="text-end">
                        <div className="d-inline-flex gap-1">
                          <Button size="sm" variant="secondary" onClick={() => startRegEdit(r)}>
                            <IconEdit className="svg-14" />
                          </Button>
                          <Button size="sm" variant="secondary" onClick={() => removeCredential(r)}>
                            <IconTrash className="svg-14" />
                          </Button>
                        </div>
                      </td>
                    </tr>
                  ))}
              </tbody>
            </table>
          </div>
        )}
      </Modal>

      {/* add / edit a card registration */}
      <Modal open={regFormOpen} onClose={() => setRegFormOpen(false)} title={regEdit ? m.amEditCard : m.amAddCard}>
        <form className="theme-form" onSubmit={saveCredential}>
          <div className="btn-group w-100 mb-3">
            <button type="button" className={`btn btn-sm f-w-600 ${regKind === "STUDENT" ? "btn-primary" : "btn-outline-secondary"}`} onClick={() => { setRegKind("STUDENT"); setRegPick(""); }}>
              {t.portal.studentsPage.title}
            </button>
            <button type="button" className={`btn btn-sm f-w-600 ${regKind === "STAFF" ? "btn-primary" : "btn-outline-secondary"}`} onClick={() => { setRegKind("STAFF"); setRegPick(""); }}>
              {t.portal.hrPage.title}
            </button>
          </div>
          {regKind === "STUDENT" ? (
            <Select label={t.portal.studentsPage.title} value={regPick} onChange={(e) => pickRegPerson(e.target.value)}>
              <option value="">—</option>
              {(students.data?.students ?? []).map((s) => (
                <option key={s.id} value={s.id}>{s.roll} — {s.name}</option>
              ))}
            </Select>
          ) : (
            <Select label={t.portal.hrPage.title} value={regPick} onChange={(e) => setRegPick(e.target.value)}>
              <option value="">—</option>
              {(staff.data?.staff ?? []).map((s) => (
                <option key={s.id} value={s.id}>{s.employeeCode} — {s.name}</option>
              ))}
            </Select>
          )}
          <Input
            label={m.amCardNo}
            required
            value={regIdentifier}
            placeholder="0001234567"
            onChange={(e) => setRegIdentifier(e.target.value)}
          />
          <p className="ems-fs-11 text-muted">{m.amCardNoHint}</p>
          <Input
            label={m.amMachineUserId}
            value={regMachineUserId}
            placeholder="1"
            onChange={(e) => setRegMachineUserId(e.target.value)}
          />
          {regKind === "STUDENT" && (
            <div className="form-check mb-3">
              <input
                className="form-check-input"
                type="checkbox"
                id="reg-save-student"
                checked={regSaveToStudent}
                onChange={(e) => setRegSaveToStudent(e.target.checked)}
              />
              <label className="form-check-label ems-fs-12" htmlFor="reg-save-student">
                {m.amSaveToStudent}
              </label>
            </div>
          )}
          <div className="d-flex justify-content-end gap-2">
            <Button type="button" variant="secondary" onClick={() => setRegFormOpen(false)}>{t.common.cancel}</Button>
            <Button type="submit" loading={regSaving} disabled={!regPick || !regIdentifier.trim()}>
              <IconCheck className="svg-14" />
              <span className="ms-1">{t.common.save}</span>
            </Button>
          </div>
        </form>
      </Modal>

      {/* CSV import wizard */}
      <Modal open={csvOpen} onClose={() => setCsvOpen(false)} title={m.amImportCsv} wide>
        <input ref={csvFile} type="file" accept=".csv,text/csv" className="d-none" onChange={onCsvPick} />
        {!csvText ? (
          <div className="text-center py-4">
            <IconUpload className="svg-32 text-muted mb-2" />
            <p className="ems-fs-13 text-muted mb-3">{m.amCsvHint}</p>
            <Button onClick={() => csvFile.current?.click()}>
              <IconUpload className="svg-14" />
              <span className="ms-1">{m.amChooseFile}</span>
            </Button>
          </div>
        ) : csvBusy ? (
          <PageLoader text={t.common.loading} />
        ) : (
          <div>
            <p className="ems-fs-12 text-muted">
              {m.amRowsFound.replace("{n}", String(csvPreview?.rows ?? 0))}
              {csvPreview?.detected ? ` · ${m.amAutoMapped}` : ` · ${m.amMapColumns}`}
            </p>
            <div className="row g-2">
              <div className="col-md-4">
                <Select label={m.amColId} value={csvMap?.idColumn ?? ""} onChange={(e) => setCsvMap((c) => (c ? { ...c, idColumn: e.target.value } : c))}>
                  {(csvPreview?.columns ?? []).map((c) => <option key={c} value={c}>{c}</option>)}
                </Select>
              </div>
              <div className="col-md-4">
                <Select label={m.amColDate} value={csvMap?.dateColumn ?? ""} onChange={(e) => setCsvMap((c) => (c ? { ...c, dateColumn: e.target.value } : c))}>
                  {(csvPreview?.columns ?? []).map((c) => <option key={c} value={c}>{c}</option>)}
                </Select>
              </div>
              <div className="col-md-4">
                <Select label={m.amColTime} value={csvMap?.timeColumn ?? ""} onChange={(e) => setCsvMap((c) => (c ? { ...c, timeColumn: e.target.value } : c))}>
                  <option value="">—</option>
                  {(csvPreview?.columns ?? []).map((c) => <option key={c} value={c}>{c}</option>)}
                </Select>
              </div>
            </div>
            {csvPreview?.preview?.length ? (
              <div className="table-responsive mt-2" style={{ maxHeight: 180, overflowY: "auto" }}>
                <table className="table table-sm table-hover ems-fs-11 mb-0">
                  <thead className="ems-table-head"><tr>{Object.keys(csvPreview.preview[0]).slice(0, 6).map((c) => <th key={c}>{c}</th>)}</tr></thead>
                  <tbody>
                    {csvPreview.preview.map((row, i) => (
                      <tr key={i}>{Object.values(row).slice(0, 6).map((v, j) => <td key={j}>{String(v)}</td>)}</tr>
                    ))}
                  </tbody>
                </table>
              </div>
            ) : null}
            <div className="d-flex align-items-center justify-content-between pt-3">
              <Select wrapperClassName="form-group mb-0" value={csvMachine} onChange={(e) => setCsvMachine(e.target.value)}>
                <option value="">{m.amNoMachine}</option>
                {machines.map((mac) => <option key={mac.id} value={mac.id}>{mac.name}</option>)}
              </Select>
              <div className="d-flex gap-2">
                <Button variant="secondary" onClick={() => { setCsvText(""); setCsvPreview(null); setCsvMap(null); csvFile.current && (csvFile.current.value = ""); }}>
                  {m.amChooseFile}
                </Button>
                <Button onClick={runImport} disabled={!csvMap?.idColumn || !csvMap?.dateColumn}>
                  <IconCheck className="svg-14" />
                  <span className="ms-1">{m.amImport}</span>
                </Button>
              </div>
            </div>
          </div>
        )}
      </Modal>
    </Card>
  );
}
