// Fingerprint devices — what the terminals actually sent, and who it belongs to.
//
// Punches reach us through the droplet relay (fingerprint-relay/ at the repo
// root) and are stored raw. The point of this screen is the join: every punch
// carries an enrolment number from the device, which means nothing on its own.
// A number nobody has claimed is attendance being recorded against *nobody*, so
// it is pulled to the top as work to do rather than left to be discovered at
// the end of the month.
//
// Linking is retroactive — the match happens when the log is read, so claiming
// a number resolves every punch it ever sent, including the ones from before
// anyone noticed. Nothing is rewritten and nothing needs re-importing.
//
// Every module script shares one global scope (Babel standalone rewrites
// top-level const/let to var), so helpers here carry a Dev prefix rather than
// silently replacing an identically-named component from another module.

// The device's own clock, shown exactly as it sent it. Deliberately not passed
// through a timezone: a punch is whatever the terminal on the wall believed the
// time was, and quietly "correcting" that would hide a misconfigured clock.
function devPunchTime(iso) {
  if (!iso) return "—";
  return String(iso).replace("T", " ");
}

// Arrival time at the server, which *is* a real instant and is localised.
function devArrivedAt(iso) {
  if (!iso) return "—";
  const d = new Date(iso);
  if (isNaN(d.getTime())) return iso;
  return d.toLocaleString(document.documentElement.lang || undefined, {
    month: "short", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit"
  });
}

function DevLinkModal({ open, onClose, deviceUserId, employees, onDone }) {
  const { useState, useEffect } = React;
  const [employee, setEmployee] = useState("");
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState("");

  useEffect(() => { setEmployee(""); setError(""); }, [deviceUserId]);

  const submit = () => {
    if (!employee || busy) return;
    setBusy(true);
    setError("");
    API.devices.map(deviceUserId, employee)
      .then(() => { setBusy(false); onDone(); })
      .catch((e) => {
        setBusy(false);
        // The server refuses a number already held by somebody else, and its
        // message names them — far more useful than a generic failure.
        setError((e && (e.error || e.message)) || T("hrdev.linkFailed"));
      });
  };

  const options = employees.map((e) => ({
    value: e.id,
    label: e.number ? `#${e.number} · ${e.name}` : e.name
  }));

  return (
    <Modal open={open} onClose={onClose} width={480}
      title={T("hrdev.linkTitle")}
      sub={T("hrdev.linkSub").replace("{id}", deviceUserId || "")}
      footer={
        <>
          <Btn onClick={onClose}>{T("common.cancel")}</Btn>
          <Btn variant="primary" onClick={submit} disabled={!employee || busy}>
            {busy ? T("hrdev.saving") : T("hrdev.linkAction")}
          </Btn>
        </>
      }>
      <Field label={T("hrdev.employee")} error={error}>
        <Select options={options} placeholder={T("hrdev.chooseEmployee")}
          value={employee} onChange={(e) => setEmployee(e.target.value)} />
      </Field>
      <p className="muted" style={{ marginTop: 10 }}>{T("hrdev.linkRetroactive")}</p>
    </Modal>
  );
}

function DeviceLogs() {
  const { useState, useEffect } = React;
  const [logs, setLogs] = useState([]);
  const [unmapped, setUnmapped] = useState([]);
  const [employees, setEmployees] = useState([]);
  const [serial, setSerial] = useState("");
  const [punchesOnly, setPunchesOnly] = useState(true);
  const [unmappedOnly, setUnmappedOnly] = useState(false);
  const [open, setOpen] = useState(null);   // expanded log row id
  const [linking, setLinking] = useState(null); // device_user_id being linked
  const [meta, setMeta] = useState({ retentionDays: null, secretRequired: false });
  const [loading, setLoading] = useState(true);

  const reload = () => {
    setLoading(true);
    const opts = { limit: 200, unmappedOnly: unmappedOnly };
    if (serial) opts.serial = serial;
    if (punchesOnly) opts.table = "ATTLOG";
    Promise.all([
      API.devices.logs(opts),
      API.devices.unmapped().catch(() => [])
    ]).then(([d, u]) => {
      setLogs(d.entries);
      setMeta({ retentionDays: d.retentionDays, secretRequired: d.secretRequired });
      setUnmapped(u);
      setLoading(false);
    }).catch(() => setLoading(false));
  };

  useEffect(() => { reload(); }, [serial, punchesOnly, unmappedOnly]);
  useEffect(() => { API.employees.listAll().then(setEmployees).catch(() => {}); }, []);

  // Serials are discovered from what has arrived; there is no register of
  // devices to read them from, and a terminal nobody added still shows up here.
  const serials = Array.from(new Set(logs.map((l) => l.serial).filter(Boolean))).sort();
  const punchCount = logs.reduce((n, l) => n + l.punches.filter((p) => p.deviceUserId).length, 0);
  const lastAt = logs.length ? logs[0].at : null;

  const afterLink = () => { setLinking(null); reload(); };

  return (
    <>
      <KpiBar>
        <Stat label={T("hrdev.punchesShown")} value={String(punchCount)}
          sub={lastAt ? T("hrdev.lastAt").replace("{t}", devArrivedAt(lastAt)) : T("hrdev.nothingYet")} />
        <Stat label={T("hrdev.devicesSeen")} value={String(serials.length)}
          sub={serials.join(", ") || T("hrdev.noDeviceYet")} />
        <Stat label={T("hrdev.unclaimed")} value={String(unmapped.length)}
          sub={unmapped.length ? T("hrdev.unclaimedSub") : T("hrdev.allLinked")} />
      </KpiBar>

      {/* The to-do list, first because it is the only part that needs a decision. */}
      {unmapped.length ? (
        <div className="card warn-card" style={{ marginBottom: 14 }}>
          <SectionHead title={T("hrdev.unclaimedTitle")} />
          <p className="muted" style={{ margin: "0 0 10px" }}>{T("hrdev.unclaimedIntro")}</p>
          <DataTable dense tableId="hr-dev-unmapped" rowKey={(r) => r.deviceUserId} rows={unmapped}
            empty={T("hrdev.allLinked")}
            cols={[
              { k: "id", label: T("hrdev.enrolmentNo"), render: (r) => <b>{r.deviceUserId}</b> },
              { k: "punches", label: T("hrdev.punches"), render: (r) => String(r.punches) },
              { k: "device", label: T("hrdev.device"), render: (r) => r.serials.join(", ") || "—" },
              { k: "last", label: T("hrdev.lastSeen"), render: (r) => devArrivedAt(r.lastSeen) },
              {
                k: "act", label: "", render: (r) => (
                  <Btn small variant="primary" icon="user"
                    onClick={() => setLinking(r.deviceUserId)}>{T("hrdev.link")}</Btn>
                )
              }
            ]} />
        </div>
      ) : null}

      <div className="toolbar page-toolbar">
        <Select options={serials} placeholder={T("hrdev.allDevices")}
          value={serial} onChange={(e) => setSerial(e.target.value)} />
        <Chips active={punchesOnly ? "punches" : "everything"}
          onChange={(v) => setPunchesOnly(v === "punches")}
          options={[
            { id: "punches", label: T("hrdev.punchesOnly") },
            { id: "everything", label: T("hrdev.everything") }
          ]} />
        <label className="switchrow-inline">
          <input type="checkbox" checked={unmappedOnly}
            onChange={(e) => setUnmappedOnly(e.target.checked)} />
          <span>{T("hrdev.showUnclaimedOnly")}</span>
        </label>
        <div style={{ flex: 1 }} />
        <Btn onClick={reload}>{T("hrdev.refresh")}</Btn>
      </div>

      <DataTable tableId="hr-dev-logs" rowKey={(r) => r.id} rows={logs}
        onRow={(r) => setOpen(open === r.id ? null : r.id)}
        empty={loading ? T("hrdev.loading") : T("hrdev.noLogs")}
        cols={[
          { k: "at", label: T("hrdev.arrived"), render: (r) => devArrivedAt(r.at) },
          { k: "device", label: T("hrdev.device"), render: (r) => r.serial || "—" },
          {
            k: "who", label: T("hrdev.who"), render: (r) => {
              const named = r.punches.filter((p) => p.employee);
              if (!r.punches.length) return <span className="muted">{T("hrdev.noPunches")}</span>;
              return (
                <span className="badge-row">
                  {Array.from(new Set(named.map((p) => p.employee.name))).map((n) => (
                    <Badge key={n} tone="green">{n}</Badge>
                  ))}
                  {r.unmapped ? <Badge tone="amber">{T("hrdev.nUnclaimed").replace("{n}", r.unmapped)}</Badge> : null}
                </span>
              );
            }
          },
          { k: "n", label: T("hrdev.punches"), render: (r) => String(r.punches.filter((p) => p.deviceUserId).length) },
          { k: "what", label: T("hrdev.request"), render: (r) => `${r.method} ${r.path}${r.table ? " · " + r.table : ""}` }
        ]}
        expanded={(r) => open !== r.id ? null : (
          <div className="dev-detail">
            {r.punches.length ? (
              <table className="mini-table">
                <thead>
                  <tr>
                    <th>{T("hrdev.enrolmentNo")}</th>
                    <th>{T("hrdev.deviceClock")}</th>
                    <th>{T("hrdev.employee")}</th>
                    <th></th>
                  </tr>
                </thead>
                <tbody>
                  {r.punches.map((p, i) => (
                    <tr key={i}>
                      <td><b>{p.deviceUserId || "—"}</b></td>
                      <td>{p.unparsed ? <code>{p.unparsed}</code> : devPunchTime(p.at)}</td>
                      <td>
                        {p.employee
                          ? <>{p.employee.number ? `#${p.employee.number} · ` : ""}{p.employee.name}
                            {p.employee.dept ? <span className="muted"> · {p.employee.dept}</span> : null}</>
                          : p.deviceUserId
                            ? <Badge tone="amber">{T("hrdev.notLinked")}</Badge>
                            : <span className="muted">—</span>}
                      </td>
                      <td>
                        {p.deviceUserId && !p.employee ? (
                          <Btn small variant="primary" icon="user"
                            onClick={() => setLinking(p.deviceUserId)}>{T("hrdev.link")}</Btn>
                        ) : null}
                      </td>
                    </tr>
                  ))}
                </tbody>
              </table>
            ) : <p className="muted">{T("hrdev.noPunchesInBody")}</p>}

            <SectionHead title={T("hrdev.rawBody")} />
            <pre className="dev-raw">{r.body || T("hrdev.emptyBody")}</pre>
            <p className="muted">
              {T("hrdev.rawMeta")
                .replace("{ip}", r.ip || "—")
                .replace("{bytes}", String(r.bytes))
                .replace("{query}", r.query || "—")}
            </p>
          </div>
        )} />

      <p className="muted" style={{ marginTop: 10 }}>
        {meta.retentionDays ? T("hrdev.retention").replace("{n}", String(meta.retentionDays)) : ""}
        {" "}
        {meta.secretRequired ? T("hrdev.secretOn") : T("hrdev.secretOff")}
      </p>

      <DevLinkModal open={!!linking} onClose={() => setLinking(null)}
        deviceUserId={linking} employees={employees} onDone={afterLink} />
    </>
  );
}
