// ActionMinutes - staff (superadmin) console: pick an existing account or set up a new one.
const { useState: useStateAdm, useEffect: useEffectAdm, useRef: useRefAdm } = React;

// Remember the staff session so "Exit preview" (AppBar) can restore it after we swap in an act-as token.
function rememberStaffSession() { try { const t = AM_API.getToken(); if (t) localStorage.setItem("am_staff_token", t); } catch (e) { /* private mode */ } }

// Parse a customer-id field: digits only, positive, within Int32 (the API binds it as an int).
function parseCustomerIdAdm(raw) {
  if (!/^\d+$/.test(raw)) return null;
  const n = Number(raw);
  return Number.isSafeInteger(n) && n > 0 && n <= 2147483647 ? n : null;
}

// Render an account's trial window + attribution code. All values are React-rendered (auto-escaped); no innerHTML.
function TrialCell({ trial }) {
  if (!trial || !trial.hasTrial) {
    return <span className="am-trial-badge none">No trial</span>;
  }
  const days = trial.daysRemaining;
  const label = trial.isExpired
    ? "Expired"
    : "Trial: " + days + " day" + (days === 1 ? "" : "s") + " left";
  return (
    <div>
      <span className={"am-trial-badge " + (trial.isExpired ? "expired" : "active")}>{label}</span>
      {trial.attributionCode ? (
        <div className="am-trial-code"><span className="lbl">Attribution: </span>{trial.attributionCode}</div>
      ) : null}
    </div>
  );
}

function StaffGate({ appState, navigate, children }) {
  if (!appState.isStaff) {
    return (
      <div className="am-appmain">
        <div className="am-domain-note warn">
          <span className="ico"><AMIcon name="triangle-alert" size={15} /></span>
          <span>PublicInput staff access required.</span>
        </div>
        <button className="pi-btn pi-btn-primary" style={{ marginTop: 16 }} onClick={() => navigate("/home")}>Go to dashboard</button>
      </div>
    );
  }
  return children;
}

/* ---------- Console: list + open + create ---------- */
function AdminConsolePage({ navigate, appState }) {
  const [q, setQ] = useStateAdm("");
  const [accounts, setAccounts] = useStateAdm(null);
  const [err, setErr] = useStateAdm(null);
  const [opening, setOpening] = useStateAdm(null);
  const [previewEmail, setPreviewEmail] = useStateAdm("");
  const [previewing, setPreviewing] = useStateAdm(false);
  const [attachId, setAttachId] = useStateAdm(null);
  const [attachCid, setAttachCid] = useStateAdm("");
  const [attachBusy, setAttachBusy] = useStateAdm(false);
  const [emailBusyId, setEmailBusyId] = useStateAdm(null);
  const [trialBusyId, setTrialBusyId] = useStateAdm(null);
  // Start/Extend-trial modal (replaces the old window.prompt() flow): the account being edited plus its
  // in-progress attribution code / day-count inputs and inline validation error.
  const [trialAcct, setTrialAcct] = useStateAdm(null);
  const [trialCode, setTrialCode] = useStateAdm("");
  const [trialDays, setTrialDays] = useStateAdm("90");
  const [trialError, setTrialError] = useStateAdm(null);

  // Monotonic request id: only the newest load() may apply its result. A toggle also bumps it (see toggleEmail),
  // so an in-flight search response can never clobber a row the user just toggled.
  const loadSeqRef = useRefAdm(0);
  const load = (query) => {
    setErr(null);
    const seq = ++loadSeqRef.current;
    AM_API.admin.accounts(query || "")
      .then((list) => { if (seq === loadSeqRef.current) setAccounts(list); })
      .catch((e) => { if (seq === loadSeqRef.current) setErr(e.message || "Could not load accounts."); });
  };
  useEffectAdm(() => { if (appState.isStaff) load(""); }, []);

  // The row whose attach panel is open right now; mirrors attachId so an in-flight request that resolves
  // after the user opened ANOTHER row's panel cannot clobber that row's state (cross-row race guard).
  const attachRowRef = useRefAdm(null);
  const startAttach = (a) => { attachRowRef.current = a.id; setAttachId(a.id); setAttachCid(a.customerId ? String(a.customerId) : ""); setErr(null); };
  const cancelAttach = () => { attachRowRef.current = null; setAttachId(null); setAttachCid(""); };

  // Attach a person to an existing legacy Customer so they share its boards. If the server reports a
  // conflict (they are already on another account), confirm explicitly before moving them (reassignCustomer).
  const doAttach = async (a) => {
    if (attachBusy) return; // double-submit guard (Enter key + button)
    const cid = parseCustomerIdAdm(attachCid.trim());
    if (cid === null) { setErr("Customer ID must be a positive whole number (up to 2147483647)."); return; }
    setAttachBusy(true);
    setErr(null);
    // Only close the panel and reload when this request is still the one the open panel belongs to.
    const finishIfCurrentRow = () => {
      if (attachRowRef.current !== a.id) return;
      attachRowRef.current = null;
      setAttachId(null); setAttachCid(""); load(q);
    };
    try {
      await AM_API.admin.createAccount({ email: a.email, existingCustomerId: cid });
      finishIfCurrentRow();
    } catch (e) {
      if (e.status === 409) {
        const who = a.jurisdiction || a.name || a.email;
        const ok = window.confirm(
          who + " (" + a.email + ") is already linked to another account (customer " + (a.customerId || "unknown") +
          "). Attaching to customer " + cid + " will MOVE them between accounts. Continue?");
        if (ok) {
          try {
            await AM_API.admin.createAccount({ email: a.email, existingCustomerId: cid, reassignCustomer: true });
            finishIfCurrentRow();
          } catch (e2) { setErr(e2.message || "Could not attach this account."); }
        }
      } else {
        setErr(e.message || "Could not attach this account.");
      }
    } finally {
      setAttachBusy(false);
    }
  };

  // Toggle an account's Do Not Email flag. One toggle in flight at a time (the emailBusyId guard also blocks the
  // double-submit); the result is applied to just this row by id, so a resolving request can never clobber another.
  const toggleEmail = async (a) => {
    if (emailBusyId) return;
    // Invalidate any in-flight search load so its (stale) response cannot overwrite this optimistic row update.
    loadSeqRef.current++;
    setEmailBusyId(a.id);
    setErr(null);
    try {
      const res = await AM_API.admin.setDoNotEmail(a.id, !a.doNotEmail);
      setAccounts((list) => list ? list.map((x) => (x.id === a.id ? { ...x, doNotEmail: res.doNotEmail } : x)) : list);
    } catch (e) {
      setErr(e.message || "Could not update the email setting.");
    } finally {
      setEmailBusyId(null);
    }
  };

  // Open the Start/Extend-trial modal for an account. The AE is the gate; the modal collects a channel
  // attribution code + length (see submitTrial). Requires a provisioned/attached customer first.
  const openTrial = (a) => {
    if (trialBusyId) return;
    if (!a.customerId) { setErr("Provision or attach this account to a customer before starting a trial."); return; }
    setTrialAcct(a);
    setTrialCode((a.trial && a.trial.attributionCode) || "");
    setTrialDays("90");
    setTrialError(null);
  };
  const closeTrial = () => { if (trialBusyId) return; setTrialAcct(null); setTrialError(null); };

  // Save (start / extend / restart) a trial and stamp the channel attribution code. Re-running overwrites the
  // window (server-side) -- that is the extend/restart. One in flight at a time; the result is applied to just
  // this row by id, and the stale-load guard (loadSeqRef bump) keeps an in-flight search from clobbering it.
  const submitTrial = async () => {
    const a = trialAcct;
    if (!a || trialBusyId) return;
    const daysStr = String(trialDays).trim();
    // Strict: reject trailing junk ("90abc") rather than silently coercing, consistent with parseCustomerIdAdm.
    if (!/^\d+$/.test(daysStr)) { setTrialError("Trial length must be a whole number of days."); return; }
    const days = parseInt(daysStr, 10);
    if (!Number.isInteger(days) || days < 1 || days > 3650) {
      setTrialError("Trial length must be a whole number between 1 and 3650 days.");
      return;
    }
    // Invalidate any in-flight search load so its (stale) response cannot overwrite this optimistic row update.
    loadSeqRef.current++;
    setTrialBusyId(a.id);
    setTrialError(null);
    try {
      // Send the code as-is; the server normalizes/validates it (trim, upper-case, safe chars, length cap).
      const res = await AM_API.admin.startTrial(a.id, { trialDays: days, attributionCode: trialCode.trim() || null });
      setAccounts((list) => list ? list.map((x) => (x.id === a.id ? { ...x, trial: res.trial } : x)) : list);
      setTrialAcct(null);
    } catch (e) {
      setTrialError(e.message || "Could not start the trial.");
    } finally {
      setTrialBusyId(null);
    }
  };

  const open = async (a) => {
    setOpening(a.id);
    try {
      const res = await AM_API.admin.impersonate(a.id);
      rememberStaffSession();
      AM_API.setToken(res.accessToken);
      window.location.href = "/home"; // reload operating as that account
    } catch (e) {
      setErr(e.message || "Could not open account.");
      setOpening(null);
    }
  };

  const startPreview = async () => {
    if (!previewEmail.trim() || previewing) return;
    setPreviewing(true);
    setErr(null);
    try {
      const res = await AM_API.admin.previewAs({ email: previewEmail.trim() });
      rememberStaffSession();
      AM_API.setToken(res.accessToken);
      window.location.href = "/home"; // preview as that government admin; the account's emails route to you
    } catch (e) {
      setErr(e.message || "Could not start the preview.");
      setPreviewing(false);
    }
  };

  return (
    <StaffGate appState={appState} navigate={navigate}>
      <div className="am-appmain" style={{ maxWidth: 1100 }}>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 14, flexWrap: "wrap", marginBottom: 18 }}>
          <div>
            <span className="pi-eyebrow">Staff console</span>
            <h2 style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 22, margin: "6px 0 4px" }}>Action Minutes accounts</h2>
            <p style={{ fontSize: 13.5, color: "var(--fg-2)", margin: 0 }}>Open an existing account or set up a new one for a client.</p>
          </div>
          <div style={{ display: "flex", gap: 8 }}>
            <button className="pi-btn pi-btn-outline" onClick={() => navigate("/admin/feedback")}><AMIcon name="star" size={14} /> Feedback</button>
            <button className="pi-btn pi-btn-primary" onClick={() => navigate("/admin/create")}><AMIcon name="plus" size={14} /> Set up a new account</button>
          </div>
        </div>

        <div className="pi-field" style={{ marginBottom: 14, maxWidth: 440 }}>
          <input type="text" placeholder="Search by name, email, or jurisdiction..." value={q}
            onChange={(e) => { setQ(e.target.value); load(e.target.value); }} />
        </div>

        <div className="pi-card" style={{ padding: "14px 16px", marginBottom: 16, background: "var(--bg-2, #F6F8FB)" }}>
          <div style={{ fontFamily: "var(--font-display)", fontWeight: 600, fontSize: 14, marginBottom: 4 }}>Preview as a government admin</div>
          <p style={{ fontSize: 12.5, color: "var(--fg-2)", margin: "0 0 10px" }}>
            Open a board exactly as a government admin will see it. We create their account and route its emails to you. The person is not contacted until you invite them.
          </p>
          <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
            <div className="pi-field" style={{ flex: 1, minWidth: 220 }}>
              <input type="email" placeholder="clerk@townofexample.gov" value={previewEmail}
                onChange={(e) => setPreviewEmail(e.target.value)}
                onKeyDown={(e) => { if (e.key === "Enter") startPreview(); }} />
            </div>
            <button className="pi-btn pi-btn-primary" onClick={startPreview} disabled={previewing || !previewEmail.trim()}>
              {previewing ? "Starting..." : "Preview as this admin"}
            </button>
          </div>
        </div>

        {err && <div className="am-domain-note warn" style={{ marginBottom: 12 }}><span className="ico"><AMIcon name="triangle-alert" size={15} /></span><span>{err}</span></div>}

        {!accounts ? (
          <div style={{ display: "flex", justifyContent: "center", minHeight: 200, alignItems: "center" }}>
            <span className="am-spin" style={{ width: 28, height: 28, borderColor: "rgba(3,149,255,0.2)", borderTopColor: "var(--pi-blue)", display: "inline-block" }}></span>
          </div>
        ) : accounts.length === 0 ? (
          <div className="am-empty">
            <span className="ic"><AMIcon name="file-text" size={28} strokeWidth={1.5} /></span>
            <div style={{ fontFamily: "var(--font-display)", fontWeight: 600, color: "var(--fg-2)", marginBottom: 4 }}>No accounts found</div>
            <p style={{ fontSize: 13, color: "var(--fg-3)" }}>Set up a new account to get started.</p>
          </div>
        ) : (
          <div className="pi-card" style={{ padding: 0, overflow: "hidden" }}>
            <table className="pi-table am-recent-table">
              <thead><tr><th>Account</th><th>Contact</th><th>Customer</th><th>Trial</th><th></th></tr></thead>
              <tbody>
                {accounts.map((a) => (
                  <React.Fragment key={a.id}>
                    <tr>
                      <td><span className="pi-row-title">{a.jurisdiction || a.name || "Account"}</span>{a.name ? <div className="pi-row-sub">{a.name}</div> : null}</td>
                      <td style={{ fontSize: 13 }}>{a.email}</td>
                      <td style={{ fontSize: 13 }}>{a.customerId ? ("#" + a.customerId) : <span style={{ color: "var(--fg-3)" }}>not provisioned</span>}</td>
                      <td><TrialCell trial={a.trial} /></td>
                      <td className="pi-row-actions">
                        <button className="pi-btn pi-btn-outline" style={{ fontSize: 12 }} onClick={() => open(a)} disabled={opening === a.id}>{opening === a.id ? "Opening..." : "Open"}</button>
                        <button
                          className={"pi-btn " + (a.doNotEmail ? "pi-btn-ghost" : "pi-btn-outline")}
                          style={{ fontSize: 12 }}
                          onClick={() => toggleEmail(a)}
                          disabled={!!emailBusyId}
                          aria-pressed={!a.doNotEmail}
                          aria-label={(a.doNotEmail ? "Turn emails on for " : "Turn emails off for ") + a.email}
                          title={a.doNotEmail ? "Emails are off for this account" : "Emails are on for this account"}
                        >
                          <AMIcon name={a.doNotEmail ? "mail-x" : "mail"} size={13} aria-hidden="true" /> {emailBusyId === a.id ? "Saving..." : (a.doNotEmail ? "Emails off" : "Emails on")}
                        </button>
                        <button
                          className="pi-btn pi-btn-ghost am-btn-xs"
                          onClick={() => openTrial(a)}
                          disabled={!a.customerId || !!trialBusyId}
                          title={a.customerId ? "" : "Provision or attach a customer first"}
                        >
                          {trialBusyId === a.id ? "Saving..." : ((a.trial && a.trial.hasTrial) ? "Extend trial" : "Start trial")}
                        </button>
                        {attachId !== a.id && (
                          <button className="pi-btn pi-btn-ghost" style={{ fontSize: 12 }} onClick={() => startAttach(a)}>Attach</button>
                        )}
                      </td>
                    </tr>
                    {attachId === a.id && (
                      <tr>
                        <td colSpan={5} style={{ background: "var(--bg-2, #F6F8FB)" }}>
                          <div style={{ display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" }}>
                            <span style={{ fontSize: 12.5, color: "var(--fg-2)" }}>Attach {a.email} to customer ID</span>
                            <div className="pi-field" style={{ width: 130 }}>
                              <input type="text" inputMode="numeric" pattern="[0-9]*" maxLength={10} value={attachCid}
                                aria-label={"Customer ID to attach " + a.email + " to"} disabled={attachBusy}
                                onChange={(e) => setAttachCid(e.target.value)}
                                onKeyDown={(e) => { if (e.key === "Enter") doAttach(a); }} placeholder="e.g. 1136" />
                            </div>
                            <button className="pi-btn pi-btn-primary" style={{ fontSize: 12 }} onClick={() => doAttach(a)} disabled={attachBusy || !attachCid.trim()}>{attachBusy ? "Attaching..." : "Attach"}</button>
                            <button className="pi-btn pi-btn-ghost" style={{ fontSize: 12 }} onClick={cancelAttach} disabled={attachBusy}>Cancel</button>
                          </div>
                        </td>
                      </tr>
                    )}
                  </React.Fragment>
                ))}
              </tbody>
            </table>
          </div>
        )}

        {trialAcct && (
          <AMModal
            title={(trialAcct.trial && trialAcct.trial.hasTrial) ? "Extend or restart trial" : "Start a trial"}
            onClose={closeTrial}
            footer={
              <React.Fragment>
                <button className="pi-btn pi-btn-ghost" onClick={closeTrial} disabled={!!trialBusyId}>Cancel</button>
                <button className="pi-btn pi-btn-primary" onClick={submitTrial} disabled={!!trialBusyId}>
                  {trialBusyId === trialAcct.id ? "Saving..." : "Save"}
                </button>
              </React.Fragment>
            }
          >
            <div className="am-trial-form">
              <p className="am-trial-intro">
                {(trialAcct.trial && trialAcct.trial.hasTrial) ? "Extend or restart the trial for " : "Start a trial for "}
                <strong>{trialAcct.email}</strong>. Re-running overwrites the current trial window.
              </p>
              <div className="pi-field">
                <label className="pi-field-label" htmlFor="am-trial-code">Channel attribution code (optional)</label>
                <input id="am-trial-code" type="text" value={trialCode} placeholder="e.g. CMCA"
                  disabled={!!trialBusyId} maxLength={64}
                  onChange={(e) => setTrialCode(e.target.value)} />
              </div>
              <div className="pi-field">
                <label className="pi-field-label" htmlFor="am-trial-days">Trial length in days</label>
                <input id="am-trial-days" type="number" min="1" max="3650" value={trialDays}
                  disabled={!!trialBusyId} inputMode="numeric"
                  onChange={(e) => setTrialDays(e.target.value)}
                  onKeyDown={(e) => { if (e.key === "Enter") submitTrial(); }} />
              </div>
              {trialError && (
                <div className="am-domain-note warn">
                  <span className="ico"><AMIcon name="triangle-alert" size={15} /></span><span>{trialError}</span>
                </div>
              )}
            </div>
          </AMModal>
        )}
      </div>
    </StaffGate>
  );
}

/* ---------- Create a new account on behalf of a client ---------- */
function AdminCreateAccountPage({ navigate, appState }) {
  const [form, setForm] = useStateAdm({ name: "", email: "", jurisdiction: "", role: "Clerk", existingCustomerId: "" });
  const [loading, setLoading] = useStateAdm(false);
  const [err, setErr] = useStateAdm(null);
  const set = (k, v) => setForm((f) => ({ ...f, [k]: v }));

  const create = async () => {
    if (!form.email.trim() || loading) return;
    const cidRaw = form.existingCustomerId.trim();
    if (cidRaw && parseCustomerIdAdm(cidRaw) === null) { setErr("Customer ID must be a positive whole number (up to 2147483647)."); return; }
    setLoading(true);
    setErr(null);
    try {
      const res = await AM_API.admin.createAccount({
        name: form.name.trim(), email: form.email.trim(), jurisdiction: form.jurisdiction.trim(), role: form.role.trim() || "Clerk",
        // Optional: attach to an existing legacy Customer instead of provisioning a new one.
        existingCustomerId: cidRaw ? Number(cidRaw) : undefined,
      });
      if (res.accessToken) {
        rememberStaffSession();
        AM_API.setToken(res.accessToken);
        window.location.href = "/setup/board"; // walk onboarding operating as the new account
      } else {
        navigate("/admin/console");
      }
    } catch (e) {
      setErr(e.message || "Could not create the account.");
      setLoading(false);
    }
  };

  return (
    <StaffGate appState={appState} navigate={navigate}>
      <div className="am-appmain" style={{ maxWidth: 640 }}>
        <span className="pi-eyebrow">Staff console</span>
        <h2 style={{ fontFamily: "var(--font-display)", fontWeight: 700, fontSize: 22, margin: "6px 0 4px" }}>Set up a new account</h2>
        <p style={{ fontSize: 13.5, color: "var(--fg-2)", margin: "0 0 18px" }}>Create the client's account, then walk their board onboarding as them.</p>
        <div className="pi-card" style={{ padding: "22px 24px" }}>
          <div className="pi-field" style={{ marginBottom: 14 }}>
            <label className="pi-field-label">Agency name</label>
            <input type="text" value={form.name} onChange={(e) => set("name", e.target.value)} placeholder="City of Alpine, TX" />
          </div>
          <div className="pi-field" style={{ marginBottom: 14 }}>
            <label className="pi-field-label">Jurisdiction</label>
            <input type="text" value={form.jurisdiction} onChange={(e) => set("jurisdiction", e.target.value)} placeholder="City of Alpine, TX" />
          </div>
          <div className="pi-field" style={{ marginBottom: 14 }}>
            <label className="pi-field-label">Contact email</label>
            <input type="email" value={form.email} onChange={(e) => set("email", e.target.value)} placeholder="clerk@cityofalpine.gov" />
          </div>
          <div className="pi-field" style={{ marginBottom: 16 }}>
            <label className="pi-field-label">Role</label>
            <input type="text" value={form.role} onChange={(e) => set("role", e.target.value)} placeholder="City Clerk" />
          </div>
          <div className="pi-field" style={{ marginBottom: 16 }}>
            <label className="pi-field-label">Attach to existing customer ID (optional)</label>
            <input type="text" inputMode="numeric" pattern="[0-9]*" maxLength={10} value={form.existingCustomerId}
              onChange={(e) => set("existingCustomerId", e.target.value)} placeholder="e.g. 1136" />
            <div style={{ fontSize: 12, color: "var(--fg-3)", marginTop: 4 }}>Leave blank to provision a new account. Enter a customer ID to add this person to that existing account and share its boards.</div>
          </div>
          {err && (
            <div className="am-domain-note warn" style={{ marginBottom: 12 }}>
              <span className="ico"><AMIcon name="triangle-alert" size={15} /></span><span>{err}</span>
            </div>
          )}
          <div style={{ display: "flex", gap: 8 }}>
            <button className="pi-btn pi-btn-primary" onClick={create} disabled={loading || !form.email.trim()}>{loading ? "Creating..." : "Create and start onboarding"}</button>
            <button className="pi-btn pi-btn-ghost" onClick={() => navigate("/admin/console")}>Cancel</button>
          </div>
        </div>
      </div>
    </StaffGate>
  );
}

/* ---------- Preview-as landing: staff arrive here (typically via PublicInput SSO from the sign-in
   screen) to preview a government admin's account. Reads ?email=, opens a preview session, lands home. ---------- */
function PreviewAsPage({ navigate, appState }) {
  const [err, setErr] = useStateAdm(null);
  useEffectAdm(() => {
    const email = new URLSearchParams(window.location.search).get("email");
    if (!email) { setErr("No government email was provided to preview."); return; }
    if (!appState.isStaff) { setErr("PublicInput staff access is required to preview an account."); return; }
    AM_API.admin.previewAs({ email }).then((res) => {
      rememberStaffSession();
      AM_API.setToken(res.accessToken);
      window.location.href = "/home"; // now previewing as that account; its emails route to you
    }).catch((e) => setErr(e.message || "Could not start the preview."));
  }, []);

  return (
    <StaffGate appState={appState} navigate={navigate}>
      <div className="am-appmain" style={{ maxWidth: 520, textAlign: "center", paddingTop: 48 }}>
        {err ? (
          <React.Fragment>
            <div className="am-domain-note warn"><span className="ico"><AMIcon name="triangle-alert" size={15} /></span><span>{err}</span></div>
            <button className="pi-btn pi-btn-primary" style={{ marginTop: 16 }} onClick={() => navigate("/admin/console")}>Back to console</button>
          </React.Fragment>
        ) : (
          <div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 16 }}>
            <span className="am-spin" style={{ width: 32, height: 32, borderColor: "rgba(3,149,255,0.2)", borderTopColor: "var(--pi-blue)", display: "inline-block" }}></span>
            <div style={{ fontFamily: "var(--font-display)", fontWeight: 600, color: "var(--fg-2)" }}>Setting up your preview...</div>
          </div>
        )}
      </div>
    </StaffGate>
  );
}

Object.assign(window, { AdminConsolePage, AdminCreateAccountPage, PreviewAsPage });
