// shotby admin — single-file React SPA. Same in-browser Babel pattern
// as the main shotby app. No build step.

const { useState, useEffect, useCallback } = React;

// ── auth helpers ─────────────────────────────────────────────────────

const TOKEN_KEY = "shotby_admin_token";

function readTokenFromHashOrStorage() {
  // If we arrived from a /#token=... redirect, capture it.
  const hash = window.location.hash || "";
  const m = hash.match(/[#&]token=([A-Za-z0-9_\-]+)/);
  if (m) {
    localStorage.setItem(TOKEN_KEY, m[1]);
    // Clean URL so the token isn't sitting in the address bar.
    history.replaceState(null, "", window.location.pathname);
    return m[1];
  }
  return localStorage.getItem(TOKEN_KEY) || null;
}

function clearToken() {
  localStorage.removeItem(TOKEN_KEY);
}

async function apiCall(path, opts = {}) {
  const token = localStorage.getItem(TOKEN_KEY);
  const res = await fetch(`${window.ADMIN_API}${path}`, {
    ...opts,
    headers: {
      "Content-Type": "application/json",
      ...(token ? { Authorization: `Bearer ${token}` } : {}),
      ...(opts.headers || {}),
    },
  });
  if (!res.ok) {
    let detail = `${res.status}`;
    try { detail = (await res.json()).error || detail; } catch {}
    const err = new Error(detail);
    err.status = res.status;
    throw err;
  }
  return await res.json();
}

// ── login gate ───────────────────────────────────────────────────────

function LoginGate({ onAuthed }) {
  return (
    <div className="login-card">
      <h2>shotby admin</h2>
      <p>Sign in at <a href={`${window.SHOTBY_SITE}/login?redirect=admin`}>shotby.tv/login</a> first.</p>
      <p style={{fontSize:"11px",color:"var(--text-faint)"}}>
        After signing in, return here. You need an admin email (configured via
        the ADMIN_EMAILS env var on the workers).
      </p>
      <p style={{marginTop:24}}>
        <button className="primary" onClick={() => {
          window.location.href = `${window.SHOTBY_SITE}/login?redirect=${encodeURIComponent(window.location.origin)}`;
        }}>Sign in at shotby.tv</button>
      </p>
      <p style={{marginTop:16}}>
        <button onClick={() => {
          const t = prompt("Or paste a session token manually:");
          if (t) { localStorage.setItem(TOKEN_KEY, t.trim()); onAuthed(); }
        }}>Paste token</button>
      </p>
    </div>
  );
}

// ── claims tab ───────────────────────────────────────────────────────

function ClaimsTab() {
  const [claims, setClaims] = useState(null);
  const [error, setError] = useState(null);
  const [rejecting, setRejecting] = useState(null);

  const load = useCallback(async () => {
    try {
      const data = await apiCall("/admin/claims/pending");
      setClaims(data.claims || []);
      setError(null);
    } catch (e) {
      setError(e.message);
    }
  }, []);

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

  const approve = async (id) => {
    if (!confirm(`Approve claim ${id}?`)) return;
    try { await apiCall(`/admin/claims/${id}/approve`, { method: "POST" }); load(); }
    catch (e) { setError(e.message); }
  };

  const reject = async (id, reason) => {
    try {
      await apiCall(`/admin/claims/${id}/reject`, { method: "POST", body: JSON.stringify({ reason }) });
      setRejecting(null);
      load();
    } catch (e) { setError(e.message); }
  };

  if (error) return <div className="error">{error}</div>;
  if (!claims) return <div className="loading">Loading claims…</div>;
  if (!claims.length) return <div className="empty">No pending claims.</div>;

  return (
    <div>
      <div className="toolbar"><span className="count">{claims.length} pending</span></div>
      <div className="row-list">
        {claims.map(c => (
          <div key={c.id} className="row row-claims">
            <div>
              <div className="row-title">{c.target_slug || c.slug || "(no slug)"}</div>
              <div className="row-meta">{c.slug_type || c.target_kind || "director"}</div>
            </div>
            <div>
              <div>{c.email || "(no email)"}</div>
              <div className="row-meta">IG: @{(c.instagram || c.target_ig_handle || "—").replace(/^@/, "")}</div>
            </div>
            <div className="row-meta">
              {c.ig_dm_attempted_from && <div>DM from @{c.ig_dm_attempted_from}</div>}
              {c.requires_manual_review && <span className="tag" style={{color:"var(--warn)",borderColor:"var(--warn)"}}>manual</span>}
            </div>
            <div className="row-actions">
              <button className="ok" onClick={() => approve(c.id)}>Approve</button>
              <button className="danger" onClick={() => setRejecting(c)}>Reject</button>
            </div>
          </div>
        ))}
      </div>

      {rejecting && (
        <RejectModal
          title={`Reject claim: ${rejecting.target_slug || rejecting.slug || "(no slug)"}`}
          onCancel={() => setRejecting(null)}
          onSubmit={(reason) => reject(rejecting.id, reason)}
        />
      )}
    </div>
  );
}

// ── contributions tab ────────────────────────────────────────────────

function ContributionsTab() {
  const [contribs, setContribs] = useState(null);
  const [error, setError] = useState(null);
  const [rejecting, setRejecting] = useState(null);

  const load = useCallback(async () => {
    try {
      const data = await apiCall("/admin/contributions/pending");
      setContribs(data.contributions || []);
      setError(null);
    } catch (e) { setError(e.message); }
  }, []);

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

  const approve = async (id) => {
    if (!confirm(`Approve contribution ${id}?`)) return;
    try { await apiCall(`/admin/contributions/${id}/approve`, { method: "POST" }); load(); }
    catch (e) { setError(e.message); }
  };

  const reject = async (id, reason) => {
    try {
      await apiCall(`/admin/contributions/${id}/reject`, { method: "POST", body: JSON.stringify({ reason }) });
      setRejecting(null);
      load();
    } catch (e) { setError(e.message); }
  };

  if (error) return <div className="error">{error}</div>;
  if (!contribs) return <div className="loading">Loading contributions…</div>;
  if (!contribs.length) return <div className="empty">No pending contributions.</div>;

  return (
    <div>
      <div className="toolbar"><span className="count">{contribs.length} pending</span></div>
      <div className="row-list">
        {contribs.map(c => (
          <div key={c.id} className="row row-contribs">
            <div>
              <span className="tag">{c.category || "?"}</span>
              <div className="row-meta" style={{marginTop:4}}>{c.target_slug || "(no target)"}</div>
            </div>
            <div>
              <div style={{fontSize:11,fontFamily:"Menlo,monospace",whiteSpace:"pre-wrap",maxHeight:80,overflow:"auto"}}>
                {JSON.stringify(c.data || c, null, 2).slice(0, 400)}
              </div>
            </div>
            <div className="row-meta">{c.submitted_at || c.created_at || "—"}</div>
            <div className="row-actions">
              <button className="ok" onClick={() => approve(c.id)}>Approve</button>
              <button className="danger" onClick={() => setRejecting(c)}>Reject</button>
            </div>
          </div>
        ))}
      </div>

      {rejecting && (
        <RejectModal
          title={`Reject contribution: ${rejecting.category}`}
          onCancel={() => setRejecting(null)}
          onSubmit={(reason) => reject(rejecting.id, reason)}
        />
      )}
    </div>
  );
}

// ── disputed credits tab ─────────────────────────────────────────────

function DisputedCreditsTab() {
  const [disputes, setDisputes] = useState(null);
  const [error, setError] = useState(null);
  const [rejecting, setRejecting] = useState(null);

  const load = useCallback(async () => {
    try {
      const data = await apiCall("/admin/credits/disputes");
      setDisputes(data.disputes || []);
      setError(null);
    } catch (e) { setError(e.message); }
  }, []);

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

  const remove = async (id) => {
    if (!confirm(`Remove the disputed credit ${id}? This updates canonical data.`)) return;
    try { await apiCall(`/admin/credits/disputes/${id}/remove`, { method: "POST" }); load(); }
    catch (e) { setError(e.message); }
  };

  const reject = async (id, reason) => {
    try {
      await apiCall(`/admin/credits/disputes/${id}/reject`, { method: "POST", body: JSON.stringify({ reason }) });
      setRejecting(null);
      load();
    } catch (e) { setError(e.message); }
  };

  if (error) return <div className="error">{error}</div>;
  if (!disputes) return <div className="loading">Loading disputes…</div>;
  if (!disputes.length) return <div className="empty">No disputed credits. (Phase 2's dispute endpoint must be live for this to populate.)</div>;

  return (
    <div>
      <div className="toolbar"><span className="count">{disputes.length} pending</span></div>
      <div className="row-list">
        {disputes.map(d => (
          <div key={d.id} className="row row-disputes">
            <div>
              <div className="row-title">{d.claimant_slug}</div>
              <div className="row-meta">credit {d.credit_id}</div>
            </div>
            <div>
              <div style={{fontSize:12,whiteSpace:"pre-wrap"}}>{d.reason || "(no reason)"}</div>
            </div>
            <div className="row-meta">{d.submitted_at || "—"}</div>
            <div className="row-actions">
              <button className="danger" onClick={() => remove(d.id)}>Remove credit</button>
              <button onClick={() => setRejecting(d)}>Reject dispute</button>
            </div>
          </div>
        ))}
      </div>

      {rejecting && (
        <RejectModal
          title={`Reject dispute on ${rejecting.claimant_slug}`}
          onCancel={() => setRejecting(null)}
          onSubmit={(reason) => reject(rejecting.id, reason)}
        />
      )}
    </div>
  );
}

// ── users tab ────────────────────────────────────────────────────────

function UsersTab() {
  const [filter, setFilter] = useState("all");
  const [q, setQ] = useState("");
  const [users, setUsers] = useState(null);
  const [error, setError] = useState(null);
  const [selected, setSelected] = useState(null);

  const load = useCallback(async () => {
    try {
      const data = await apiCall(`/admin/users?filter=${filter}&q=${encodeURIComponent(q)}`);
      setUsers(data.users || []);
      setError(null);
    } catch (e) { setError(e.message); }
  }, [filter, q]);

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

  if (error) return <div className="error">{error}</div>;

  return (
    <div>
      <div className="subtabs">
        {["all", "fans", "claimants", "pro"].map(f => (
          <button key={f} className={`subtab ${filter === f ? "active" : ""}`} onClick={() => setFilter(f)}>
            {f}
          </button>
        ))}
      </div>
      <div className="toolbar">
        <input placeholder="Search by email, name, username, slug…" value={q} onChange={e => setQ(e.target.value)} />
        <span className="count">{users ? users.length : "…"} users</span>
      </div>

      {!users ? <div className="loading">Loading…</div> : !users.length ? (
        <div className="empty">No users match.</div>
      ) : (
        <div className="row-list">
          {users.map(u => (
            <div key={u.id} className="row row-users">
              <div>
                <div className="row-title">{u.display_name || u.username || "(no name)"}</div>
                <div className="row-meta">{u.email}</div>
              </div>
              <div>
                {(u.claimed_slugs || []).slice(0, 3).map(s => (
                  <a key={s} className="tag" style={{marginRight:4}}
                     href={`${window.SHOTBY_SITE || "https://shotby.tv"}/director/${s}`}
                     target="_blank" rel="noopener noreferrer">{s}</a>
                ))}
                {(u.claimed_slugs || []).length > 3 && <span className="row-meta">+{u.claimed_slugs.length - 3}</span>}
              </div>
              <div>
                {u.is_admin && <span className="tag admin">admin</span>}
                {u.pro && <span className="tag pro">pro</span>}
                {u.suspended && <span className="tag suspended">suspended</span>}
                {!u.is_admin && !u.pro && !u.suspended && !u.claimed_slugs?.length && <span className="tag fan">fan</span>}
              </div>
              <div className="row-meta">{(u.created_at || "").slice(0, 10)}</div>
              <div className="row-actions">
                <button onClick={() => setSelected(u)}>Detail</button>
              </div>
            </div>
          ))}
        </div>
      )}

      {selected && <UserDetailModal user={selected} onClose={() => { setSelected(null); load(); }} />}
    </div>
  );
}

// ── user detail modal ────────────────────────────────────────────────

function UserDetailModal({ user, onClose }) {
  const [detail, setDetail] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    apiCall(`/admin/users/${user.id}`).then(setDetail).catch(e => setError(e.message));
  }, [user.id]);

  const toggleSuspend = async () => {
    const willSuspend = !detail.user.suspended;
    const reason = willSuspend ? prompt("Suspend reason:") : "";
    if (willSuspend && !reason) return;
    try {
      await apiCall(`/admin/users/${user.id}/suspend`, {
        method: "POST",
        body: JSON.stringify({ suspend: willSuspend, reason }),
      });
      const d = await apiCall(`/admin/users/${user.id}`);
      setDetail(d);
    } catch (e) { setError(e.message); }
  };

  const impersonate = async (writeMode) => {
    try {
      const body = { write: writeMode };
      const headers = {};
      if (writeMode) {
        const key = prompt("Enter IMPERSONATION_KEY (admin shared secret):");
        if (!key) return;
        headers["X-Impersonation-Key"] = key;
      }
      const res = await apiCall(`/admin/users/${user.id}/impersonate`, {
        method: "POST", body: JSON.stringify(body), headers,
      });
      if (res.redirect_url) {
        if (confirm(`Opening impersonation session (${res.expires_in_minutes} min, ${writeMode ? "WRITE" : "read-only"}).\n\nContinue?`)) {
          window.open(res.redirect_url, "_blank");
        }
      }
    } catch (e) { setError(e.message); }
  };

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <h2>{user.display_name || user.username || user.email}</h2>
        {error && <div className="error">{error}</div>}
        {!detail ? <div className="loading">Loading…</div> : (
          <>
            <div style={{marginBottom:16}}>
              {detail.user.is_admin && <span className="tag admin" style={{marginRight:4}}>admin</span>}
              {detail.user.pro && <span className="tag pro" style={{marginRight:4}}>pro</span>}
              {detail.user.suspended && <span className="tag suspended" style={{marginRight:4}}>suspended</span>}
            </div>
            <pre>{JSON.stringify(detail, null, 2)}</pre>
            <div className="actions">
              <button onClick={() => impersonate(false)}>Impersonate (read-only)</button>
              <button className="danger" onClick={() => impersonate(true)}>Impersonate (WRITE)</button>
              <button className={detail.user.suspended ? "ok" : "danger"} onClick={toggleSuspend}>
                {detail.user.suspended ? "Unsuspend" : "Suspend"}
              </button>
              <button onClick={onClose}>Close</button>
            </div>
          </>
        )}
      </div>
    </div>
  );
}

// ── reject reason modal ──────────────────────────────────────────────

function RejectModal({ title, onCancel, onSubmit }) {
  const [reason, setReason] = useState("");
  return (
    <div className="modal-overlay" onClick={onCancel}>
      <div className="modal" onClick={e => e.stopPropagation()}>
        <h2>{title}</h2>
        <p style={{fontSize:11,color:"var(--text-dim)"}}>The reason will be emailed to the user.</p>
        <textarea value={reason} onChange={e => setReason(e.target.value)} rows="4" style={{width:"100%"}}
          placeholder="Reason (optional)" />
        <div className="actions">
          <button onClick={onCancel}>Cancel</button>
          <button className="danger" onClick={() => onSubmit(reason)}>Reject</button>
        </div>
      </div>
    </div>
  );
}

// ── audit tab ────────────────────────────────────────────────────────

function AuditTab() {
  const [entries, setEntries] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    apiCall("/admin/audit?limit=200").then(d => setEntries(d.entries || [])).catch(e => setError(e.message));
  }, []);

  if (error) return <div className="error">{error}</div>;
  if (!entries) return <div className="loading">Loading audit log…</div>;
  if (!entries.length) return <div className="empty">No audit entries yet.</div>;

  return (
    <div className="row-list">
      {entries.map((e, i) => (
        <div key={i} className="row" style={{gridTemplateColumns:"180px 160px 180px 1fr"}}>
          <div className="row-meta">{e.timestamp}</div>
          <div><span className="tag">{e.action}</span></div>
          <div className="row-meta">{e.admin_email}</div>
          <div style={{fontSize:11,fontFamily:"Menlo,monospace",overflow:"hidden",textOverflow:"ellipsis"}}>
            {JSON.stringify(e.target)} {JSON.stringify(e.details || {})}
          </div>
        </div>
      ))}
    </div>
  );
}

// ── products placeholder ─────────────────────────────────────────────

function ProductsTab() {
  const [data, setData] = useState(null);
  useEffect(() => { apiCall("/admin/products/pending").then(setData).catch(() => {}); }, []);
  return <div className="empty">{data?.note || "Phase 4 — wired up when product showcase ships."}</div>;
}

// ── Get Indexed channel submissions tab ──────────────────────────────
// Queue-only public submissions (never auto-processed). Copy the channel
// URL and run intake manually until the auto-run path is turned on.
function ChannelSubmissionsTab() {
  const [subs, setSubs] = useState(null);
  const [error, setError] = useState(null);
  const [copied, setCopied] = useState(null);

  const load = useCallback(async () => {
    try {
      const data = await apiCall("/admin/channel-submissions");
      setSubs(data.submissions || []);
      setError(data.error || null);
    } catch (e) { setError(e.message); setSubs([]); }
  }, []);

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

  const copy = async (text, id) => {
    try {
      await navigator.clipboard.writeText(text);
      setCopied(id);
      setTimeout(() => setCopied(c => (c === id ? null : c)), 1400);
    } catch {}
  };

  const remove = async (s) => {
    const url = s.channel_url || s.url || "";
    if (!window.confirm(`Delete this channel submission?\n\n${url}\n\nThis can't be undone.`)) return;
    try {
      await apiCall("/admin/channel-submissions/delete", {
        method: "POST",
        body: JSON.stringify({ key: s._key }),
      });
      load();
    } catch (e) { setError(e.message); }
  };

  if (subs === null) return <div className="loading">Loading channel submissions…</div>;

  return (
    <div>
      <div className="toolbar">
        <span className="count">{subs.length} queued</span>
        <button onClick={load} style={{marginLeft:10}}>Refresh</button>
        {error ? <span className="error" style={{marginLeft:10}}>{error}</span> : null}
      </div>
      {!subs.length ? (
        <div className="empty">No channel submissions yet.</div>
      ) : (
        <div className="row-list">
          {subs.map(s => {
            // New KV entries use channel_url/description/created_at; keep a
            // fallback to the legacy url/note/submitted_at for any old rows.
            const url = s.channel_url || s.url || "";
            const note = s.description || s.note || "";
            const when = s.created_at || s.submitted_at || "—";
            const rid = s.id || s._key;
            return (
            <div key={rid} className="row row-contribs">
              <div>
                <a href={url} target="_blank" rel="noopener noreferrer"
                   style={{fontFamily:"Menlo,monospace",fontSize:12,wordBreak:"break-all"}}>{url || "(no url)"}</a>
                <div className="row-meta" style={{marginTop:4}}>
                  {s.email || "no email"}
                  {s.owner_claimed ? <span className="tag" style={{marginLeft:6, color:"var(--accent)", borderColor:"var(--accent)"}}>✓ owner</span> : null}
                </div>
              </div>
              <div>
                <div style={{fontSize:11,whiteSpace:"pre-wrap",maxHeight:90,overflow:"auto"}}>{note || "—"}</div>
              </div>
              <div className="row-meta">{when}</div>
              <div className="row-actions">
                <button className="ok" onClick={() => copy(url, rid)}>
                  {copied === rid ? "Copied ✓" : "Copy URL"}
                </button>
                <button className="danger" onClick={() => remove(s)}>Delete</button>
              </div>
            </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

// ── main app ─────────────────────────────────────────────────────────

const TABS = [
  { id: "claims", label: "Claims", Comp: ClaimsTab },
  { id: "channels", label: "Channel submissions", Comp: ChannelSubmissionsTab },
  { id: "contributions", label: "Contributions", Comp: ContributionsTab },
  { id: "disputes", label: "Credit disputes", Comp: DisputedCreditsTab },
  { id: "products", label: "Products", Comp: ProductsTab },
  { id: "users", label: "Users", Comp: UsersTab },
  { id: "audit", label: "Audit log", Comp: AuditTab },
];

function App() {
  const [admin, setAdmin] = useState(undefined); // undefined = loading, null = unauthed, object = authed
  const [tab, setTab] = useState("claims");

  const checkAuth = useCallback(async () => {
    const token = readTokenFromHashOrStorage();
    if (!token) { setAdmin(null); return; }
    try {
      const data = await apiCall("/admin/me");
      setAdmin(data.admin);
    } catch (e) {
      console.error("admin auth failed:", e);
      clearToken();
      setAdmin(null);
    }
  }, []);

  useEffect(() => { checkAuth(); }, [checkAuth]);

  if (admin === undefined) return <div className="loading">Authenticating…</div>;
  if (admin === null) return <LoginGate onAuthed={checkAuth} />;

  const ActiveTab = TABS.find(t => t.id === tab)?.Comp || ClaimsTab;

  return (
    <div className="app-shell">
      <div className="topbar">
        <h1>shotby <span className="accent">admin</span></h1>
        <div className="meta">
          {admin.email} · <a href="#" onClick={(e) => { e.preventDefault(); clearToken(); setAdmin(null); }}>sign out</a>
        </div>
      </div>
      <div className="tabs">
        {TABS.map(t => (
          <button key={t.id} className={`tab ${tab === t.id ? "active" : ""}`} onClick={() => setTab(t.id)}>
            {t.label}
          </button>
        ))}
      </div>
      <div className="main">
        <ActiveTab />
      </div>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById("root")).render(<App />);
