/* ============================================================
   VKTR TCO — Admin Control Panel (V1.7)
   ------------------------------------------------------------
   v1.9.8 (2026-07-21): visible ONLY for role === "admin" (see
   auth.jsx) — "AI-enabled User" (stored role value unchanged,
   "observer") no longer gets any view of this panel, not even
   read-only. The previous transparent-handover design let that
   tier open the panel read-only, which turned out to be exactly
   the kind of role/permission ambiguity Rija flagged as a real
   bug: the three tiers are now a strict, simple ladder -- User <
   AI-enabled User < Admin, each a superset of the one below,
   Admin Panel access being the one thing that's Admin-exclusive.

   This panel only ever WRITES to Firestore users/{uid} docs.
   It never calls Anthropic/Google APIs directly and never holds
   any API key — the Cloudflare Worker is the sole enforcement
   point for apiAccess/tokenLimit at call time. This UI is
   convenience + visibility, not the security boundary.
   ============================================================ */

// ---- Admin action audit log (v1.7.5) — every admin write through this
// panel (role/apiAccess/tokenLimit changes, access grant/deny, sharedPresets
// edits) gets a timestamped record here, same shape/purpose as the existing
// per-user token usage log (cf-worker's logUsage). Matters most once
// multiple admins exist post-handover -- "who changed what," not just "what
// is it now." Client-written (not Worker-proxied, unlike token usage) since
// this is a plain Firestore write an already-authenticated admin session
// can make directly; firestore.rules gates who's allowed to write it.
function logAdminAction(actorUser, action, detail) {
  if (typeof firebase === "undefined" || !firebase.apps.length || !actorUser) return;
  firebase.firestore().collection("adminAuditLog").add({
    actorUid: actorUser.uid,
    actorEmail: actorUser.email,
    action,
    detail: detail || {},
    at: firebase.firestore.FieldValue.serverTimestamp(),
  }).catch(() => {});
}

function AdminPanel({ s, onClose }) {
  const { lang } = useLang();
  const { user: myUser, profile: myProfile } = useAuth();
  // Only role === "admin" can even open this panel (auth.jsx) -- this is a
  // defensive fail-safe against editing, not the real gate.
  const readOnly = !isAdmin(myProfile);
  const [tab, setTab] = React.useState("users");
  const [users, setUsers] = React.useState(null);
  const [error, setError] = React.useState(null);
  const [savingId, setSavingId] = React.useState(null);

  React.useEffect(() => {
    if (typeof firebase === "undefined" || !firebase.apps.length) {
      setError("Firebase belum terhubung.");
      return;
    }
    const unsub = firebase.firestore().collection("users").onSnapshot(
      (snap) => setUsers(snap.docs.map((d) => ({ id: d.id, ...d.data() }))),
      (err) => setError(err.message)
    );
    return unsub;
  }, []);

  const saveUser = async (uid, patch) => {
    if (readOnly) return;
    setSavingId(uid);
    try {
      await firebase.firestore().collection("users").doc(uid).update(patch);
      logAdminAction(myUser, "user_update", { targetUid: uid, patch });
    } catch (e) {
      alert("Gagal menyimpan: " + e.message);
    } finally {
      setSavingId(null);
    }
  };

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal-panel admin-panel" onClick={(e) => e.stopPropagation()}>
        <div className="modal-head">
          <h2>🛡 <Tr en="Admin Control Panel" id="Panel Kontrol Admin" />{readOnly && <Badge kind="info" style={{ marginLeft: 8 }}>read-only</Badge>}</h2>
          <button type="button" className="modal-close" onClick={onClose}>✕</button>
        </div>

        <div className="library-tabs">
          {[
            { key: "users", en: "Users", id_: "Pengguna" },
            { key: "access", en: "Access Requests", id_: "Permintaan Akses" },
            { key: "presets", en: "Shared Presets", id_: "Preset Bersama" },
          ].map((t) => (
            <div key={t.key} className={"library-tab" + (tab === t.key ? " active" : "")} onClick={() => setTab(t.key)}>
              {tr(lang, t.en, t.id_)}
            </div>
          ))}
        </div>

        {tab === "users" && (
          <>
            <p className="admin-panel-note">
              <Tr
                en="Enforcement of API access and token limits happens server-side in the Cloudflare Worker on every call — this panel edits the Firestore record it reads, it does not itself gate anything."
                id="Penegakan akses API dan batas token terjadi di sisi server (Cloudflare Worker) pada setiap panggilan — panel ini hanya mengubah data Firestore yang dibaca, bukan penegak akses itu sendiri."
              />
            </p>

            {error && <Alert kind="error">{error}</Alert>}
            {!error && users === null && <div className="admin-panel-loading">Loading…</div>}

            {users && (
              <table className="admin-table">
                <thead>
                  <tr>
                    <th><Tr en="User" id="Pengguna" /></th>
                    <th><Tr en="Access" id="Akses" /></th>
                    <th><Tr en="Role" id="Peran" /></th>
                    <th><Tr en="AI Features" id="Fitur AI" /></th>
                    <th><Tr en="Token Limit" id="Batas Token" /></th>
                    <th><Tr en="Reset" id="Reset" /></th>
                    <th><Tr en="Used (period)" id="Terpakai (periode)" /></th>
                    <th><Tr en="Notes" id="Catatan" /></th>
                  </tr>
                </thead>
                <tbody>
                  {users.map((u) => (
                    <AdminUserRow key={u.id} u={u} readOnly={readOnly} saving={savingId === u.id} onSave={(patch) => saveUser(u.id, patch)} />
                  ))}
                </tbody>
              </table>
            )}

            <div className="admin-panel-hint">
              <Tr
                en="Token usage graph: each API call is logged as its own timestamped record in users/{uid}/usage (see cf-worker/). Wire a chart here once real usage data exists — schema is already in place."
                id="Grafik penggunaan token: setiap panggilan API dicatat sebagai rekaman berstempel waktu di users/{uid}/usage (lihat cf-worker/). Sambungkan grafik di sini setelah data penggunaan nyata tersedia — skema sudah disiapkan."
              />
            </div>
          </>
        )}

        {tab === "access" && <AccessRequestsTab users={users} error={error} readOnly={readOnly} onSave={saveUser} />}
        {tab === "presets" && <SharedPresetsAdminTab s={s} readOnly={readOnly} />}
      </div>
    </div>
  );
}

// ---- Landing-page guest password + Access Requests (v1.7.5) ----
async function sha256HexBrowser(text) {
  const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
  return Array.from(new Uint8Array(buf)).map((b) => b.toString(16).padStart(2, "0")).join("");
}

function GuestPasswordCard({ readOnly }) {
  const { lang } = useLang();
  const { user: myUser } = useAuth();
  const [value, setValue] = React.useState("");
  const [status, setStatus] = React.useState("idle"); // idle | saving | saved | error

  const save = async () => {
    if (!value.trim() || readOnly) return;
    setStatus("saving");
    try {
      const passwordHash = await sha256HexBrowser(value.trim());
      await firebase.firestore().collection("config").doc("guestAccess").set({ passwordHash }, { merge: true });
      logAdminAction(myUser, "guest_password_changed", {});
      setStatus("saved");
      setValue("");
      setTimeout(() => setStatus("idle"), 2000);
    } catch (e) {
      setStatus("error");
    }
  };

  return (
    <div className="admin-guest-password">
      <div className="admin-guest-password-label">
        <Tr en="Guest access password" id="Kata sandi akses tamu" />
      </div>
      <p className="admin-panel-note" style={{ marginBottom: 8 }}>
        <Tr en="Shown on the landing page for anyone without a VKTR sign-in. Write-only here — the current value can't be read back, only overwritten. Hashed before it ever leaves this browser."
            id="Ditampilkan di halaman muka untuk siapa pun tanpa akun masuk VKTR. Hanya bisa ditulis di sini — nilai saat ini tidak bisa dibaca ulang, hanya ditimpa. Di-hash sebelum meninggalkan browser ini." />
      </p>
      <div className="admin-guest-password-row">
        <input type="text" className="input" style={{ flex: 1 }} value={value} disabled={readOnly || status === "saving"}
          placeholder={tr(lang, "Set new password…", "Atur kata sandi baru…")}
          onChange={(e) => setValue(e.target.value)} />
        <button type="button" className="btn btn-primary" disabled={readOnly || !value.trim() || status === "saving"} onClick={save}>
          {status === "saving" ? "…" : status === "saved" ? "✓" : tr(lang, "Save", "Simpan")}
        </button>
      </div>
      {status === "error" && <div className="landing-error"><Tr en="Couldn't save — try again." id="Gagal menyimpan — coba lagi." /></div>}
    </div>
  );
}

function AccessRequestRow({ icon, name, subtitle, onApprove, onDeny, readOnly }) {
  const { lang } = useLang();
  return (
    <div className="library-row">
      <span style={{ fontSize: 16, flex: "none" }} aria-hidden="true">{icon}</span>
      <div className="library-row-info">
        <div className="library-row-name">{name}</div>
        {subtitle && <div className="library-row-sub">{subtitle}</div>}
      </div>
      {!readOnly && onApprove && (
        <button type="button" className="btn btn-ghost library-row-btn" title={tr(lang, "Approve", "Setujui")} onClick={onApprove}>✓</button>
      )}
      {!readOnly && onDeny && (
        <button type="button" className="btn btn-ghost library-row-btn library-row-danger" title={tr(lang, "Deny", "Tolak")} onClick={onDeny}>✕</button>
      )}
    </div>
  );
}

function AccessRequestsTab({ users, error, readOnly, onSave }) {
  const { lang } = useLang();
  // v1.9.8 (2026-07-21): previously only showed accounts that had
  // EXPLICITLY clicked "Request access" (accessRequestedAt set) -- any
  // account that just signed in and landed on the "access required"
  // screen, without submitting a request, was invisible here while
  // simultaneously already showing up in the Users tab looking like a
  // normal active account (first-sign-in creates the Firestore doc
  // immediately, regardless of accessGranted). Reported by Rija
  // 2026-07-21: a colleague's sign-in was never surfaced for approval and
  // silently looked like an already-granted "User" in the main list. Now
  // shows EVERY not-yet-approved, not-denied account, splitting out
  // whether they actually submitted a request note or just haven't gotten
  // that far yet -- nobody can fall through the gap between the two tabs.
  const requested = (users || []).filter((u) => !window.hasAccess(u) && !u.accessDenied && u.accessRequestedAt);
  const awaiting = (users || []).filter((u) => !window.hasAccess(u) && !u.accessDenied && !u.accessRequestedAt);
  const denied = (users || []).filter((u) => !window.hasAccess(u) && u.accessDenied);

  const approve = (uid) => onSave(uid, { accessGranted: true, accessDenied: false });
  const deny = (uid) => onSave(uid, { accessDenied: true });

  return (
    <>
      <GuestPasswordCard readOnly={readOnly} />
      <p className="admin-panel-note" style={{ marginTop: 16 }}>
        <Tr en="Non-@vktr.id sign-ins that requested access, approved or denied directly here — no email involved."
            id="Akun masuk non-@vktr.id yang mengajukan akses, disetujui atau ditolak langsung di sini — tanpa email." />
      </p>
      {error && <Alert kind="error">{error}</Alert>}
      {!error && users === null && <div className="admin-panel-loading">Loading…</div>}
      {users && requested.length === 0 && awaiting.length === 0 && denied.length === 0 && (
        <div className="library-empty"><Tr en="No pending access requests." id="Tidak ada permintaan akses tertunda." /></div>
      )}
      {requested.map((u) => (
        <AccessRequestRow key={u.id} icon="⏳" readOnly={readOnly}
          name={u.displayName || u.email}
          subtitle={[u.email, u.accessRequestNote].filter(Boolean).join(" · ")}
          onApprove={() => approve(u.id)}
          onDeny={() => deny(u.id)}
        />
      ))}
      {awaiting.map((u) => (
        <AccessRequestRow key={u.id} icon="👋" readOnly={readOnly}
          name={u.displayName || u.email}
          subtitle={[u.email, tr(lang, "signed in, hasn't submitted a request yet", "sudah masuk, belum mengajukan permintaan")].filter(Boolean).join(" · ")}
          onApprove={() => approve(u.id)}
          onDeny={() => deny(u.id)}
        />
      ))}
      {denied.map((u) => (
        <AccessRequestRow key={u.id} icon="🚫" readOnly={readOnly}
          name={u.displayName || u.email}
          subtitle={tr(lang, "Denied", "Ditolak") + " · " + u.email}
          onApprove={() => approve(u.id)}
          onDeny={null}
        />
      ))}
    </>
  );
}

// ---- Shared Presets (v1.7) — admin-curated, cloud-hosted preset library,
// deliberately kept separate from the static, Excel-validated presets.js/
// build_presets.py pipeline: those are proven to 0% delta against real TCOO
// files and must never be hand-edited; sharedPresets is a live "save
// whatever's on screen right now" library any admin can curate, public-read
// so any signed-in or guest user can browse and load it (see firestore.rules).
function useSharedPresets() {
  const [presets, setPresets] = React.useState(null);
  const [error, setError] = React.useState(null);
  React.useEffect(() => {
    if (typeof firebase === "undefined" || !firebase.apps.length) { setPresets({}); return; }
    const unsub = firebase.firestore().collection("sharedPresets").onSnapshot(
      (snap) => {
        const next = {};
        snap.forEach((doc) => { next[doc.id] = doc.data(); });
        setPresets(next);
      },
      (err) => setError(err.message)
    );
    return unsub;
  }, []);
  return { presets, error };
}

function newSharedPresetId() {
  return `sp_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
}

function SharedPresetsAdminTab({ s, readOnly }) {
  const { lang } = useLang();
  const { user } = useAuth();
  const { presets, error } = useSharedPresets();
  const ids = presets ? Object.keys(presets).sort((a, b) => (presets[b].savedAt || 0) - (presets[a].savedAt || 0)) : [];

  // v1.9.9 (2026-07-21) audit fixes, both real: (1) saveCurrent saved `s`
  // RAW, without the same PRESET_SAVE_EXCLUDE_KEYS filtering
  // screens.jsx's SaveAsPresetCard already applies (depotBom/depotMetrics
  // can be large enough to push a shared preset's Firestore document over
  // the 1MB limit -- and unlike a private profile, a shared preset is
  // meant to be portable across accounts, so instance-specific depot sync
  // data doesn't even belong in it). (2) None of these three writes had
  // error handling, AND logAdminAction fired unconditionally regardless of
  // whether the write actually succeeded -- meaning the audit log itself
  // ("who changed what, not just what is it now") could contain entries
  // for actions that never actually happened. Both fixed: filter before
  // saving, alert on failure, and only log the action after the write is
  // confirmed to have landed.
  const saveFailedAlert = (e) => alert(tr(lang, "Couldn't save — check your connection and try again.", "Gagal menyimpan — periksa koneksi Anda dan coba lagi.") + (e && e.message ? `\n(${e.message})` : ""));

  const saveCurrent = async () => {
    const name = window.prompt(tr(lang, "Name this shared preset", "Nama preset bersama ini"), s.company || "Untitled");
    if (!name) return;
    const description = window.prompt(tr(lang, "Short description (optional)", "Deskripsi singkat (opsional)"), "") || "";
    const id = newSharedPresetId();
    const clean = { ...s };
    (window.PRESET_SAVE_EXCLUDE_KEYS || []).forEach((k) => delete clean[k]);
    try {
      await firebase.firestore().collection("sharedPresets").doc(id).set({
        name, description, state: clean, savedAt: Date.now(), savedBy: (user && user.email) || "unknown",
      });
      logAdminAction(user, "shared_preset_created", { id, name });
    } catch (e) { saveFailedAlert(e); }
  };

  const rename = async (id, data) => {
    const name = window.prompt(tr(lang, "Rename preset", "Ganti nama preset"), data.name);
    if (!name) return;
    try {
      await firebase.firestore().collection("sharedPresets").doc(id).update({ name });
      logAdminAction(user, "shared_preset_renamed", { id, from: data.name, to: name });
    } catch (e) { saveFailedAlert(e); }
  };

  const remove = async (id) => {
    if (!window.confirm(tr(lang, "Delete this shared preset for everyone? This can't be undone.", "Hapus preset bersama ini untuk semua orang? Tidak dapat dibatalkan."))) return;
    try {
      await firebase.firestore().collection("sharedPresets").doc(id).delete();
      logAdminAction(user, "shared_preset_deleted", { id });
    } catch (e) { saveFailedAlert(e); }
  };

  return (
    <>
      <p className="admin-panel-note">
        <Tr
          en="Separate from the built-in, Excel-validated presets (presets/ folder) — this is a live, cloud-hosted library any admin can curate. Public read, admin-only write (see firestore.rules)."
          id="Terpisah dari preset bawaan yang tervalidasi Excel (folder presets/) — ini adalah perpustakaan langsung berbasis cloud yang bisa dikurasi admin mana pun. Baca publik, tulis khusus admin (lihat firestore.rules)."
        />
      </p>
      {error && <Alert kind="error">{error}</Alert>}
      {!readOnly && (
        <div className="library-toolbar">
          <button type="button" className="btn btn-primary" style={{ fontSize: 13 }} onClick={saveCurrent}>
            + <Tr en="Save current profile as shared preset" id="Simpan profil saat ini sebagai preset bersama" />
          </button>
        </div>
      )}
      {presets === null && <div className="admin-panel-loading">Loading…</div>}
      {presets && ids.length === 0 && (
        <div className="library-empty"><Tr en="No shared presets yet." id="Belum ada preset bersama." /></div>
      )}
      {presets && ids.map((id) => {
        const data = presets[id];
        return (
          <LibraryRow key={id} icon="☁"
            name={data.name}
            subtitle={[data.description, data.savedAt ? new Date(data.savedAt).toLocaleDateString(lang === "en" ? "en-US" : "id-ID") : ""].filter(Boolean).join(" · ")}
            onRename={!readOnly ? (() => rename(id, data)) : null}
            onDelete={!readOnly ? (() => remove(id)) : null}
          />
        );
      })}
    </>
  );
}

function AdminUserRow({ u, readOnly, saving, onSave }) {
  const { lang } = useLang();
  const [tokenLimit, setTokenLimit] = React.useState(u.tokenLimit === -1 ? "" : String(u.tokenLimit ?? 0));
  const [unlimited, setUnlimited] = React.useState(u.tokenLimit === -1);
  const [notes, setNotes] = React.useState(u.notes || "");

  const commitTokenLimit = () => {
    const n = unlimited ? -1 : Math.max(0, Number(tokenLimit) || 0);
    onSave({ tokenLimit: n });
  };

  // v1.9.8 (2026-07-21): role now drives apiAccess atomically -- User =
  // no AI features, AI-enabled User/Admin = AI features on. Removes the
  // separate, independently-togglable "API Access" checkbox that used to
  // let the two fields drift out of sync (the actual root cause of
  // canUseGatedApi's old inverted-observer bug: a stale apiAccess value
  // left set, or unset, from before a role change). The AI Features cell
  // below is now a read-only reflection of the role, not its own control.
  const changeRole = (newRole) => onSave({ role: newRole, apiAccess: newRole === "observer" || newRole === "admin" });

  // Reported bug (Rija, 2026-07-21): a colleague's account showed up here
  // looking like a normal active "User" while actually still stuck behind
  // the "access required" landing screen on his end -- this list includes
  // EVERY Firestore users/{uid} doc that exists (created at first sign-in,
  // before any approval), with nothing distinguishing "approved" from
  // "just signed in, not yet approved." This badge closes that gap.
  const pendingApproval = !window.hasAccess(u) && !u.accessDenied;

  return (
    <tr>
      <td>
        <div className="admin-user-name">{u.displayName || u.email}</div>
        <div className="admin-user-email">{u.email}</div>
      </td>
      <td>
        {pendingApproval ? (
          <Badge kind="warn">{tr(lang, "Pending approval", "Menunggu persetujuan")}</Badge>
        ) : u.accessDenied ? (
          <Badge kind="danger">{tr(lang, "Denied", "Ditolak")}</Badge>
        ) : (
          <Badge kind="ok">{tr(lang, "Active", "Aktif")}</Badge>
        )}
      </td>
      <td>
        <select className="select" value={u.role || "user"} disabled={readOnly}
          onChange={(e) => changeRole(e.target.value)}>
          <option value="user">{tr(lang, "User", "Pengguna")}</option>
          <option value="observer">{tr(lang, "AI-enabled User", "Pengguna AI")}</option>
          <option value="admin">{tr(lang, "Admin", "Admin")}</option>
        </select>
      </td>
      <td>
        <span className="admin-toggle" title={tr(lang, "Derived from role — change the role dropdown to change this.", "Diturunkan dari peran — ubah dropdown peran untuk mengubah ini.")}>
          {u.apiAccess ? tr(lang, "Enabled", "Aktif") : tr(lang, "Disabled", "Nonaktif")}
        </span>
      </td>
      <td>
        <div className="admin-token-limit">
          <input className="input" type="number" min="0" style={{ width: 90 }}
            disabled={readOnly || unlimited} value={tokenLimit}
            onChange={(e) => setTokenLimit(e.target.value)}
            onBlur={commitTokenLimit} />
          <label className="admin-unlimited">
            <input type="checkbox" checked={unlimited} disabled={readOnly}
              onChange={(e) => { setUnlimited(e.target.checked); onSave({ tokenLimit: e.target.checked ? -1 : Math.max(0, Number(tokenLimit) || 0) }); }} />
            {tr(lang, "Unlimited", "Tanpa Batas")}
          </label>
        </div>
      </td>
      <td>
        <select className="select" value={u.resetCadence || "monthly"} disabled={readOnly}
          onChange={(e) => onSave({ resetCadence: e.target.value })}>
          <option value="daily">{tr(lang, "Daily", "Harian")}</option>
          <option value="weekly">{tr(lang, "Weekly", "Mingguan")}</option>
          <option value="monthly">{tr(lang, "Monthly", "Bulanan")}</option>
          <option value="yearly">{tr(lang, "Yearly", "Tahunan")}</option>
        </select>
      </td>
      <td>{fmt.num(u.tokensUsed || 0)}{saving && " …"}</td>
      <td>
        <input className="input" style={{ width: 160 }} value={notes} disabled={readOnly}
          placeholder="e.g. API not supported"
          onChange={(e) => setNotes(e.target.value)}
          onBlur={() => onSave({ notes })} />
      </td>
    </tr>
  );
}

window.AdminPanel = AdminPanel;
Object.assign(window, { useSharedPresets, newSharedPresetId });
