/* ==================================================================
   Admin → Parents
   ------------------------------------------------------------------
   Approval queue + lifecycle controls for the Parent app.

   Parents self-register and land here with status='pending'. The admin
   reviews each parent's claimed child(ren) against the school register
   and clicks Approve — that flips status to 'active' and stamps
   `verified_at` on every link row.

   Tabs:
     • Pending   — review queue, [Approve] / [Reject]
     • Active    — approved parents, [Suspend], last-login info
     • Suspended — paused or rejected, [Restore]

   All endpoints live under /api/admin/parents/* and require admin.
   ================================================================== */

const { Icon: PAIcon } = window.AppUI;

const AdminParentsScreen = () => {
  const [tab, setTab]             = React.useState("pending");
  const [rows, setRows]           = React.useState([]);
  const [loading, setLoading]     = React.useState(true);
  const [err, setErr]             = React.useState(null);
  const [openId, setOpenId]       = React.useState(null);    // parent currently expanded
  const [openDetail, setOpenDetail] = React.useState(null);  // { ...parent, links: [] }
  const [busy, setBusy]           = React.useState({});
  // Issued sign-in link, keyed by parent id. Shown once — the raw token is
  // never stored server-side, so navigating away loses it for good.
  const [signinLink, setSigninLink] = React.useState(null);
  // Invite composer — for guardians with NO account yet. Generated against
  // students, not a parent row, so it lives outside the account list.
  const [inviteOpen, setInviteOpen]   = React.useState(false);
  const [invQuery, setInvQuery]       = React.useState("");
  const [invResults, setInvResults]   = React.useState([]);
  const [invPicked, setInvPicked]     = React.useState([]);
  const [invPhone, setInvPhone]       = React.useState("");
  const [invRelation, setInvRelation] = React.useState("guardian");
  const [invBusy, setInvBusy]         = React.useState(false);
  const [invite, setInvite]           = React.useState(null);
  const [invCopied, setInvCopied]     = React.useState(false);
  const [linkCopied, setLinkCopied] = React.useState(false);      // { [parentId]: true }

  const load = React.useCallback(async () => {
    setLoading(true); setErr(null);
    try {
      setRows(await window.AppApi.get(`/admin/parents?status=${tab}`));
    } catch (e) {
      setErr(String(e.message || e));
    } finally {
      setLoading(false);
    }
  }, [tab]);

  React.useEffect(() => { load(); }, [load]);

  const openDetails = async (parentId) => {
    if (openId === parentId) { setOpenId(null); setOpenDetail(null); return; }
    setOpenId(parentId); setOpenDetail(null);
    try {
      setOpenDetail(await window.AppApi.get(`/admin/parents/${parentId}`));
    } catch (e) {
      window.alert("Could not load parent: " + (e.message || e));
      setOpenId(null);
    }
  };

  const act = async (parentId, action, prompt) => {
    if (prompt && !window.confirm(prompt)) return;
    setBusy(b => ({ ...b, [parentId]: true }));
    try {
      await window.AppApi.post(`/admin/parents/${parentId}/${action}`, {});
      if (openId === parentId) setOpenId(null);
      await load();
    } catch (e) {
      window.alert(`Could not ${action}: ` + (e.message || e));
    } finally {
      setBusy(b => { const n = { ...b }; delete n[parentId]; return n; });
    }
  };

  // The phone is the parent's login identity — it's what they type on the sign-in
  // screen and where the code lands — so this is the repair path when a family
  // changes number. Reloads the list too, since the row shows the phone.
  const savePhone = async (parentId, phone) => {
    await window.AppApi.patch(`/admin/parents/${parentId}`, { phone });
    setOpenDetail(await window.AppApi.get(`/admin/parents/${parentId}`));
    await load();
  };

  // Debounced student typeahead for the invite composer.
  React.useEffect(() => {
    if (!inviteOpen || invQuery.trim().length < 2) { setInvResults([]); return; }
    const t = setTimeout(async () => {
      try {
        setInvResults(await window.AppApi.get(
          `/admin/parents/student-search?q=${encodeURIComponent(invQuery.trim())}`));
      } catch { setInvResults([]); }
    }, 250);
    return () => clearTimeout(t);
  }, [invQuery, inviteOpen]);

  const pickStudent = (st) => {
    setInvPicked(ps => ps.some(p => p.id === st.id) ? ps : [...ps, st]);
    setInvQuery(""); setInvResults([]);
  };
  const unpickStudent = (id) => setInvPicked(ps => ps.filter(p => p.id !== id));

  const createInvite = async () => {
    setInvBusy(true); setInvite(null); setInvCopied(false);
    try {
      const out = await window.AppApi.post("/admin/parents/invites", {
        phone: invPhone.trim(),
        student_ids: invPicked.map(p => p.id),
        relation: invRelation,
      });
      setInvite(out);
    } catch (e) {
      window.alert("Could not create the invitation: " + (e.message || e));
    } finally {
      setInvBusy(false);
    }
  };

  const copyInvite = async () => {
    try {
      await navigator.clipboard.writeText(invite.url);
      setInvCopied(true); setTimeout(() => setInvCopied(false), 2000);
    } catch { /* clipboard blocked — the input is selectable as a fallback */ }
  };

  const resetInvite = () => {
    setInviteOpen(false); setInvite(null); setInvPicked([]);
    setInvPhone(""); setInvQuery(""); setInvResults([]); setInvRelation("guardian");
  };

  const issueSigninLink = async (parentId) => {
    setSigninLink(null); setLinkCopied(false);
    setBusy(b => ({ ...b, [parentId]: true }));
    try {
      const out = await window.AppApi.post(`/admin/parents/${parentId}/signin-link`, {});
      setSigninLink({ parentId, ...out });
    } catch (e) {
      window.alert("Could not create a sign-in link: " + (e.message || e));
    } finally {
      setBusy(b => { const n = { ...b }; delete n[parentId]; return n; });
    }
  };

  const copySigninLink = async () => {
    try {
      await navigator.clipboard.writeText(signinLink.url);
      setLinkCopied(true);
      setTimeout(() => setLinkCopied(false), 2000);
    } catch { /* clipboard blocked — the input below is selectable as a fallback */ }
  };

  const removeLink = async (parentId, linkId, studentName) => {
    if (!window.confirm(`Remove ${studentName} from this parent's claimed children?`)) return;
    try {
      await window.AppApi.del(`/admin/parents/${parentId}/links/${linkId}`);
      // refresh the expanded detail
      setOpenDetail(await window.AppApi.get(`/admin/parents/${parentId}`));
    } catch (e) {
      window.alert("Could not remove link: " + (e.message || e));
    }
  };

  const tabs = [
    { id: "pending",   label: "Pending",   icon: "inbox" },
    { id: "active",    label: "Active",    icon: "check" },
    { id: "suspended", label: "Suspended", icon: "x" },
  ];

  const fmt = (iso) => iso ? new Date(iso).toLocaleString(undefined, {
    month: "short", day: "numeric", hour: "2-digit", minute: "2-digit",
  }) : "—";

  const PABadge = window.AppUI.StatusBadge;
  const tabTone = { pending: "amber", active: "green", suspended: "grey" };

  return (
    <div style={{ overflow: "auto", height: "100%" }}>
    <div className="cx-page" style={{ maxWidth: 1200 }}>
      <window.AppUI.PageHeader
        eyebrow="Admin · parents"
        title="Parent accounts"
        meta={["Parents self-register and wait here. Verify the claimed child(ren) against the school register, then approve. Approved parents can log in and see their child's daily briefing."]}
        actions={<>
          <button className="btn" onClick={load} disabled={loading}>
            <PAIcon name="refresh" size={15}/>Refresh
          </button>
          <button className={`btn ${inviteOpen ? "" : "primary"}`}
            onClick={() => (inviteOpen ? resetInvite() : setInviteOpen(true))}>
            <PAIcon name={inviteOpen ? "x" : "userPlus"} size={15}/>{inviteOpen ? "Close invite" : "Invite a parent"}
          </button>
        </>}
      />

      {/* Tabs */}
      <window.AppUI.Tabs full value={tab} onChange={setTab} ariaLabel="Parent account status"
        items={tabs.map(t => ({ value: t.id, label: t.label, icon: t.icon }))}/>

      {/* Invitation composer — for a guardian with NO account yet. Generated
          against the students, so it lives above the account list rather than
          inside a parent row. */}
      {inviteOpen && (
        <div className="cx-card" style={{ marginTop: 20 }}>
          <div className="cx-card-head">
            <div>
              <div className="cx-card-title">Invite a parent who hasn't registered</div>
              <div className="cx-card-sub">
                Pick the child (or siblings), type the guardian's mobile, and send them the link.
                Opening it asks only for their name — no registration form, no approval needed.
              </div>
            </div>
            <window.AppUI.IconButton icon="x" label="Close" onClick={resetInvite}/>
          </div>

          <div className="field" style={{ maxWidth: 480 }}>
            <label className="label">Find the student</label>
            <div className="ui-search">
              <PAIcon name="search" size={14}/>
              <input className="input"
                value={invQuery} onChange={e => setInvQuery(e.target.value)}
                placeholder="Type a student's name…" autoComplete="off"/>
            </div>
          </div>

          {invResults.length > 0 && (
            <div className="ui-list" style={{ marginTop: 6, border: "1px solid var(--line)", borderRadius: "var(--radius-tile)", maxWidth: 480, overflow: "hidden" }}>
              {invResults.map(r => {
                // A student may have at most two verified parent accounts.
                // Showing this here means the office never picks a full student
                // and then gets refused at "Create invitation".
                const full = r.linked_parents >= 2;
                return (
                  <button key={r.id} type="button" disabled={full}
                    className={`ui-row${full ? "" : " clickable"}`}
                    style={{
                      padding: "10px 14px", background: "transparent", border: 0, borderBottom: "1px solid var(--line)",
                      opacity: full ? 0.5 : 1, cursor: full ? "not-allowed" : "pointer",
                    }}
                    title={full ? "Already has two parent accounts — remove one first" : ""}
                    onClick={() => !full && pickStudent(r)}>
                    <window.AppUI.Avatar name={r.name} size="sm"/>
                    <div className="ui-row-main">
                      <div className="ui-row-title">{r.name}</div>
                      <div className="ui-row-sub">
                        {r.class_label} · roll <span className="mono">{r.roll_no}</span>
                        {full
                          ? " · both parent slots taken"
                          : r.linked_parents > 0 && ` · ${r.linked_parents} of 2 slots used`}
                      </div>
                    </div>
                    {!full && <PAIcon name="plus" size={14} style={{ color: "var(--ink-3)" }}/>}
                  </button>
                );
              })}
            </div>
          )}

          {invPicked.length > 0 && (
            <div style={{ marginTop: 12, display: "flex", flexWrap: "wrap", gap: 6 }}>
              {invPicked.map(st => (
                <span key={st.id} className="ui-badge tint blue lg" style={{ paddingRight: 4 }}>
                  {st.name} <span style={{ opacity: 0.75 }}>{st.class_label}</span>
                  <button type="button" className="btn ghost xs icon-only round" style={{ color: "inherit" }}
                    aria-label={`Remove ${st.name}`} title="Remove"
                    onClick={() => unpickStudent(st.id)}><PAIcon name="x" size={12}/></button>
                </span>
              ))}
            </div>
          )}

          <div style={{ display: "flex", gap: 12, alignItems: "flex-end", marginTop: 14, flexWrap: "wrap" }}>
            <div className="field" style={{ margin: 0 }}>
              <label className="label">Guardian's mobile</label>
              <input className="input tabular" style={{ width: 200 }}
                value={invPhone} onChange={e => setInvPhone(e.target.value)}
                placeholder="10-digit number" inputMode="numeric"/>
            </div>
            <div className="field" style={{ margin: 0 }}>
              <label className="label">Relationship</label>
              <select className="select" style={{ width: 140 }}
                value={invRelation} onChange={e => setInvRelation(e.target.value)}>
                <option value="father">Father</option>
                <option value="mother">Mother</option>
                <option value="guardian">Guardian</option>
              </select>
            </div>
            <button className="btn primary" disabled={invBusy || !invPicked.length || !invPhone.trim()}
              onClick={createInvite}>
              <PAIcon name="mail" size={15}/>{invBusy ? "Creating…" : "Create invitation"}
            </button>
          </div>

          {invite && (
            <div className="ui-alert green" style={{ marginTop: 16 }}>
              <div style={{ marginBottom: 8 }}><b>Invitation link — copy now, shown once</b></div>
              <div style={{ display: "flex", gap: 6 }}>
                <input className="input mono" readOnly value={invite.url}
                  onFocus={e => e.target.select()}
                  style={{ flex: 1, minWidth: 0, fontSize: 12 }}/>
                <button className="btn" onClick={copyInvite}>
                  <PAIcon name={invCopied ? "check" : "copy"} size={14}/>{invCopied ? "Copied" : "Copy"}
                </button>
              </div>
              <div style={{ fontSize: 12, marginTop: 8, color: "var(--ink-2)" }}>
                Send only to {invite.phone_mask}. Works once, expires in {invite.expires_in_hours} hours,
                and links {invite.students.map(s => s.name).join(", ")} to whoever opens it —
                so it must reach the right person.
              </div>
            </div>
          )}
        </div>
      )}

      {err && <div className="ui-alert red" style={{ marginTop: 20 }}>{err}</div>}

      <div className="cx-card flush" style={{ marginTop: 20 }}>
          {loading && <div style={{ padding: 20, color: "var(--ink-3)", fontSize: 13 }}>Loading…</div>}
          {!loading && rows.length === 0 && (
            <window.AppUI.EmptyState compact icon="users" title={`No ${tab} parents`}/>
          )}
          {rows.map(p => (
            <div key={p.id} style={{ borderBottom: "1px solid var(--line)" }}>
              <div
                onClick={() => openDetails(p.id)}
                className="ui-row clickable"
                style={{
                  padding: "14px 20px", border: 0, flexWrap: "wrap", rowGap: 10,
                  background: openId === p.id ? "var(--surface-active)" : undefined,
                }}>
                <window.AppUI.Avatar name={p.name}/>
                <div className="ui-row-main" style={{ minWidth: 180 }}>
                  <div className="ui-row-title">{p.name}</div>
                  <div className="ui-row-sub">
                    <span className="mono">{p.phone}</span>
                    {p.email && <> · {p.email}</>}
                  </div>
                </div>
                <div className="ui-row-end" style={{ flexWrap: "wrap", gap: 14 }}>
                  <PABadge tone={p.link_count > 0 && p.verified_count === p.link_count ? "green" : tabTone[tab]}>
                    {p.verified_count}/{p.link_count} child{p.link_count === 1 ? "" : "ren"} verified
                  </PABadge>
                  <span className="muted tabular" style={{ fontSize: 12.5, minWidth: 150 }}>
                    {tab === "pending"   && <>requested {fmt(p.requested_at)}</>}
                    {tab === "active"    && <>last login {fmt(p.last_login_at)}</>}
                    {tab === "suspended" && <>since {fmt(p.requested_at)}</>}
                  </span>
                  <div style={{ display: "flex", gap: 6 }}>
                    {tab === "pending" && (
                      <>
                        <button className="btn sm" disabled={!!busy[p.id]}
                          onClick={(e) => { e.stopPropagation(); act(p.id, "reject",
                            `Reject ${p.name}? They will not be able to log in. (You can restore later.)`); }}>
                          <PAIcon name="xCircle" size={14}/>Reject
                        </button>
                        <button className="btn sm primary" disabled={!!busy[p.id]}
                          onClick={(e) => { e.stopPropagation(); act(p.id, "approve",
                            `Approve ${p.name}? They'll be able to log in and see their child's briefing.`); }}>
                          <PAIcon name="check" size={14}/>Approve
                        </button>
                      </>
                    )}
                    {tab === "active" && (
                      <button className="btn sm" disabled={!!busy[p.id]}
                        onClick={(e) => { e.stopPropagation(); act(p.id, "suspend",
                          `Suspend ${p.name}? Their session will be killed.`); }}>
                        <PAIcon name="ban" size={14}/>Suspend
                      </button>
                    )}
                    {tab === "suspended" && (
                      <button className="btn sm primary" disabled={!!busy[p.id]}
                        onClick={(e) => { e.stopPropagation(); act(p.id, "unsuspend",
                          `Restore ${p.name}?`); }}>
                        <PAIcon name="refresh" size={14}/>Restore
                      </button>
                    )}
                  </div>
                  <PAIcon name={openId === p.id ? "chevronDown" : "chevronRight"} size={16} style={{ color: "var(--ink-3)" }}/>
                </div>
              </div>

              {openId === p.id && (
                <div style={{ padding: "4px 20px 20px clamp(20px, 6vw, 70px)", background: "var(--surface-active)" }}>
                  {!openDetail && <div className="muted" style={{ fontSize: 13, padding: "8px 0" }}>Loading children…</div>}
                  {openDetail && (
                    <div style={{ display: "grid", gap: 18 }}>
                      <div>
                        <div className="ui-section-label" style={{ marginBottom: 8 }}>Sign-in number</div>
                        <PhoneEditor
                          value={openDetail.phone || p.phone}
                          onSave={(v) => savePhone(p.id, v)}/>
                      </div>

                      <div>
                        <div className="ui-section-label" style={{ marginBottom: 8 }}>
                          Claimed children — verify against the school register
                        </div>
                        {openDetail.links.length === 0 && (
                          <div className="muted" style={{ fontSize: 13 }}>No claimed children.</div>
                        )}
                        {openDetail.links.length > 0 && (
                          <div className="cx-tablecard">
                            <div className="ui-list">
                              {openDetail.links.map(l => (
                                <div key={l.id} className="ui-row" style={{ padding: "10px 16px", flexWrap: "wrap", rowGap: 6 }}>
                                  <span className="ui-id" style={{ minWidth: 60 }}>{l.roll_no}</span>
                                  <div className="ui-row-main">
                                    <div className="ui-row-title">{l.student_name}</div>
                                    <div className="ui-row-sub">{l.class_label || "—"} · {l.relation}</div>
                                  </div>
                                  <div className="ui-row-end" style={{ gap: 8 }}>
                                    {l.verified_at
                                      ? <PABadge tone="green" variant="outline">Verified</PABadge>
                                      : <PABadge tone="grey" variant="outline">Pending</PABadge>}
                                    <window.AppUI.IconButton icon="trash" label="Remove this claimed child (e.g. parent over-claimed)"
                                      size={15} className="sm"
                                      onClick={() => removeLink(p.id, l.id, l.student_name)}/>
                                  </div>
                                </div>
                              ))}
                            </div>
                          </div>
                        )}
                      </div>

                      {/* One-tap sign-in link. For the parent who cannot get in
                          and is on the phone to the office: passwords here are
                          scrypt-hashed and unrecoverable, and OTP depends on an
                          SMS actually arriving. */}
                      <div>
                        <div className="ui-section-label" style={{ marginBottom: 8 }}>Sign-in link</div>
                        {p.status !== "active" ? (
                          <div className="muted" style={{ fontSize: 13 }}>
                            Approve this account before sending a sign-in link.
                          </div>
                        ) : (
                          <>
                            <button className="btn sm" disabled={!!busy[p.id]}
                              onClick={() => issueSigninLink(p.id)}>
                              <PAIcon name="key" size={14}/>{busy[p.id] ? "Creating…" : "Create sign-in link"}
                            </button>

                            {signinLink && signinLink.parentId === p.id && (
                              <div className="ui-alert green" style={{ marginTop: 12 }}>
                                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8, marginBottom: 8 }}>
                                  <b>Sign-in link — copy now, shown once</b>
                                  <window.AppUI.IconButton icon="x" label="Dismiss" className="sm" onClick={() => setSigninLink(null)}/>
                                </div>
                                <div style={{ display: "flex", gap: 6 }}>
                                  {/* readOnly input rather than plain text: it stays
                                      selectable when the clipboard API is blocked. */}
                                  <input className="input mono" readOnly value={signinLink.url}
                                    onFocus={e => e.target.select()}
                                    style={{ flex: 1, minWidth: 0, fontSize: 12 }}/>
                                  <button className="btn" onClick={copySigninLink}>
                                    <PAIcon name={linkCopied ? "check" : "copy"} size={14}/>{linkCopied ? "Copied" : "Copy"}
                                  </button>
                                </div>
                                <div style={{ fontSize: 12, marginTop: 8, color: "var(--ink-2)" }}>
                                  Works once, expires in {signinLink.expires_in_minutes} minutes, and signs
                                  {" "}{p.name} straight into the parent app. Creating another cancels this one.
                                  Send it to {signinLink.parent?.phone || p.phone} — anyone who opens it is signed in.
                                </div>
                              </div>
                            )}
                          </>
                        )}
                      </div>
                    </div>
                  )}
                </div>
              )}
            </div>
          ))}
      </div>
    </div>
    </div>
  );
};

// Inline editor for the parent's sign-in number. Kept as its own component so
// each expanded row owns its draft state — the parent list renders these inside
// a .map, and shared state would leak one row's edit into another.
//
// The server does the real validation (E.164 normalisation + duplicate check);
// this only blocks the obviously-empty case and surfaces whatever the server
// says. It deliberately does NOT pre-normalise the draft, so the admin sees
// exactly what they typed if the server rejects it.
const PhoneEditor = ({ value, onSave }) => {
  const [editing, setEditing] = React.useState(false);
  const [draft, setDraft]     = React.useState(value || "");
  const [busy, setBusy]       = React.useState(false);
  const [err, setErr]         = React.useState(null);

  React.useEffect(() => { setDraft(value || ""); setErr(null); }, [value]);

  const commit = async () => {
    setBusy(true); setErr(null);
    try {
      await onSave(draft.trim());
      setEditing(false);
    } catch (e) {
      setErr(e.message || String(e));
    } finally {
      setBusy(false);
    }
  };

  if (!editing) {
    return (
      <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
        <span className="mono" style={{ fontSize: 14, color: "var(--ink-0)" }}>{value || "—"}</span>
        <button className="btn sm" onClick={() => setEditing(true)}>
          <PAIcon name="edit" size={14}/>Change
        </button>
      </div>
    );
  }

  return (
    <div>
      <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
        <input className="input" value={draft} placeholder="10-digit mobile"
          disabled={busy} onChange={(e) => setDraft(e.target.value)}
          style={{ flex: "0 0 200px" }}/>
        <button className="btn sm primary"
          disabled={busy || !draft.trim() || draft.trim() === (value || "")}
          onClick={commit}>Save</button>
        <button className="btn sm ghost" disabled={busy}
          onClick={() => { setDraft(value || ""); setErr(null); setEditing(false); }}>Cancel</button>
      </div>
      <div className="hint" style={{ marginTop: 6 }}>
        Receives the sign-in code. Saved as +91…; any code already sent to the old number stops working.
      </div>
      {err && <div className="ui-alert red" style={{ marginTop: 8 }}>{err}</div>}
    </div>
  );
};

window.AdminParentsScreen = AdminParentsScreen;
