const { useState, useEffect, useCallback, useRef } = React;

const TOKEN_KEY = "jimportal_token";
const getToken = () => window.sessionStorage.getItem(TOKEN_KEY);
const setToken = (t) => window.sessionStorage.setItem(TOKEN_KEY, t);
const clearToken = () => window.sessionStorage.removeItem(TOKEN_KEY);

async function api(path, { method = "GET", body } = {}) {
  const headers = { "Content-Type": "application/json" };
  const t = getToken();
  if (t) headers["Authorization"] = `Bearer ${t}`;
  const res = await fetch(`/api/v1${path}`, {
    method, headers, body: body ? JSON.stringify(body) : undefined,
  });
  if (res.status === 401) { clearToken(); window.dispatchEvent(new Event("bp-unauth")); throw new Error("Session expired."); }
  const data = await res.json().catch(() => ({}));
  if (!res.ok) throw new Error(data.detail || `Request failed (${res.status})`);
  return data;
}

function Cube({ size = 26 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 32 32" fill="none" className="cube">
      <path d="M16 2 L29 9.5 L16 17 L3 9.5 Z" fill="#7fdb4c" />
      <path d="M3 9.5 L16 17 L16 31 L3 23.5 Z" fill="#2fbf88" />
      <path d="M29 9.5 L16 17 L16 31 L29 23.5 Z" fill="#4f9e3a" />
    </svg>
  );
}

function Toast({ msg }) { return msg ? <div className="toast">{msg}</div> : null; }

const STATUS_LABEL = { creating: "creating", active: "active", suspended: "suspended", error: "error", deleted: "deleted" };
function StatusPill({ status }) {
  const cls = status === "active" ? "st-running" : status === "creating" ? "st-installing"
    : status === "error" ? "st-error" : "st-queued";
  return <span className={`status ${cls}`}><span className="dot" />{STATUS_LABEL[status] || status}</span>;
}

/* ── Auth ── */
function AuthScreen({ onAuthed }) {
  const [mode, setMode] = useState("login");
  const [email, setEmail] = useState(""); const [password, setPassword] = useState("");
  const [err, setErr] = useState(""); const [busy, setBusy] = useState(false);
  const submit = async (e) => {
    e.preventDefault(); setErr(""); setBusy(true);
    try {
      const path = mode === "login" ? "/auth/login" : "/auth/register";
      const { access_token } = await api(path, { method: "POST", body: { email, password } });
      setToken(access_token); onAuthed();
    } catch (e) { setErr(e.message); } finally { setBusy(false); }
  };
  return (
    <div className="auth-wrap">
      <div className="auth-card">
        <div className="brand"><Cube /><h1>jims<span>hosting</span></h1></div>
        <h2>{mode === "login" ? "Sign in" : "Create your free account"}</h2>
        {err && <div className="form-error">{err}</div>}
        <form onSubmit={submit}>
          <div className="field"><label>Email</label>
            <input type="email" value={email} autoComplete="email" required
              onChange={(e) => setEmail(e.target.value)} /></div>
          <div className="field"><label>Password{mode === "register" ? " (8+ characters)" : ""}</label>
            <input type="password" value={password} required minLength={8}
              autoComplete={mode === "login" ? "current-password" : "new-password"}
              onChange={(e) => setPassword(e.target.value)} /></div>
          <button className="btn" disabled={busy}>{busy ? "…" : mode === "login" ? "Sign in" : "Create account"}</button>
        </form>
        <div className="auth-switch">
          {mode === "login" ? "New here? " : "Already have an account? "}
          <button onClick={() => { setMode(mode === "login" ? "register" : "login"); setErr(""); }}>
            {mode === "login" ? "Create a free account" : "Sign in"}</button>
        </div>
      </div>
    </div>
  );
}

/* ── Create free server ── */
function CreateFree({ onClose, onCreated, notify }) {
  const [name, setName] = useState("");
  const [types, setTypes] = useState(null);
  const [eggId, setEggId] = useState(null);
  const [version, setVersion] = useState("");
  const [err, setErr] = useState(""); const [busy, setBusy] = useState(false);

  useEffect(() => {
    (async () => {
      try {
        const t = await api("/server-types");
        setTypes(t);
        if (t.length) setEggId(t[0].egg_id);
      } catch (e) { setErr(e.message); setTypes([]); }
    })();
  }, []);

  const submit = async (e) => {
    e.preventDefault(); setErr(""); setBusy(true);
    try {
      await api("/servers", { method: "POST",
        body: { name, egg_id: eggId, mc_version: version || null } });
      notify("Free server is being created!"); onCreated(); onClose();
    } catch (e) { setErr(e.message); } finally { setBusy(false); }
  };

  const selected = types && types.find((t) => t.egg_id === eggId);

  return (
    <div className="modal-back" onMouseDown={onClose}>
      <div className="modal" onMouseDown={(e) => e.stopPropagation()}>
        <div className="modal-head"><h3>Create your free server</h3><button className="x" onClick={onClose}>×</button></div>
        <div className="modal-body">
          {err && <div className="form-error">{err}</div>}
          <form onSubmit={submit}>
            <div className="field"><label>Server type</label>
              {types === null ? <div className="loading-block">Loading types…</div>
                : types.length === 0 ? <div className="hint">No server types configured.</div>
                : <div className="type-grid">
                    {types.map((t) => (
                      <button type="button" key={t.egg_id}
                        className={`type-card ${eggId === t.egg_id ? "sel" : ""}`}
                        onClick={() => setEggId(t.egg_id)}>
                        <div className="type-label">{t.label}</div>
                        <div className="type-specs mono">{(t.memory_mb / 1024).toFixed(t.memory_mb % 1024 ? 1 : 0)}GB · {(t.disk_mb / 1024).toFixed(t.disk_mb % 1024 ? 1 : 0)}GB · {t.cpu_pct}%</div>
                      </button>
                    ))}
                  </div>}
            </div>
            {selected && (
              <p className="free-note">
                {selected.label}: {(selected.memory_mb / 1024).toFixed(selected.memory_mb % 1024 ? 1 : 0)} GB RAM, {(selected.disk_mb / 1024).toFixed(selected.disk_mb % 1024 ? 1 : 0)} GB disk, {selected.cpu_pct}% CPU. One free server at a time.
              </p>
            )}
            <div className="field"><label>Server name</label>
              <input value={name} onChange={(e) => setName(e.target.value)}
                placeholder="my-survival-world" required pattern="[A-Za-z0-9 ._\-]+"
                title="Letters, numbers, spaces, . _ -" />
              <div className="hint">this becomes your address, e.g. my-survival-world.mc.jimshosting.com</div></div>
            <div className="field"><label>Minecraft version <span className="opt">(optional)</span></label>
              <input value={version} onChange={(e) => setVersion(e.target.value)}
                placeholder="latest" className="mono" />
              <div className="hint">leave blank for the newest. e.g. 1.21.4</div></div>
            <button className="btn" disabled={busy || !eggId} style={{ marginTop: 6 }}>{busy ? "Creating…" : "Create free server"}</button>
          </form>
        </div>
      </div>
    </div>
  );
}

/* ── Server card ── */
function ServerCard({ server, onDelete, onManage, notify }) {
  const connect = server.connect_address;
  const copy = () => { navigator.clipboard.writeText(connect).then(() => notify("Address copied.")); };
  return (
    <div className="card">
      <div className="card-top">
        <div className="card-name">{server.name}</div>
        <StatusPill status={server.status} />
      </div>
      <div className="specs">
        <div className="spec"><div className="v">{(server.memory_mb / 1024)}G</div><div className="k">RAM</div></div>
        <div className="spec"><div className="v">{(server.disk_mb / 1024)}G</div><div className="k">Disk</div></div>
        <div className="spec"><div className="v">{server.cpu_pct}%</div><div className="k">CPU</div></div>
        <div className="spec"><div className="v">{server.type_label || server.tier}</div><div className="k">Type</div></div>
      </div>
      {connect && server.status === "active" ? (
        <div className="connect"><code>{connect}</code><button className="copy-btn" onClick={copy}>copy</button></div>
      ) : server.status === "creating" ? (
        <div className="card-pending-msg">Building your server… this takes a minute or two.</div>
      ) : server.status === "error" ? (
        <div className="card-pending-msg" style={{ color: "var(--red)" }}>{server.error_msg || "Something went wrong."}</div>
      ) : null}
      <div className="card-foot">
        <button className="btn ghost" onClick={() => onManage(server)}
          disabled={server.status !== "active"}
          title={server.status !== "active" ? "Available once the server is running" : ""}>Console &amp; controls</button>
        <button className="btn danger" onClick={() => onDelete(server)}>Delete</button>
      </div>
    </div>
  );
}

function EmptyState({ onCreate }) {
  return (
    <div className="empty">
      <svg width="72" height="72" viewBox="0 0 32 32" fill="none">
        <path d="M16 2 L29 9.5 L16 17 L3 9.5 Z" fill="#7fdb4c" opacity="0.9" />
        <path d="M3 9.5 L16 17 L16 31 L3 23.5 Z" fill="#2fbf88" opacity="0.7" />
        <path d="M29 9.5 L16 17 L16 31 L29 23.5 Z" fill="#4f9e3a" opacity="0.7" />
      </svg>
      <h3>You don't have a server yet</h3>
      <p>Spin up your free Minecraft server — ready in a couple minutes.</p>
      <button className="btn btn-inline" onClick={onCreate} style={{ margin: "0 auto" }}>Create your free server</button>
    </div>
  );
}

/* ── Admin panel ── */
function AdminServers({ notify }) {
  const [rows, setRows] = useState(null);
  const load = useCallback(async () => {
    try { setRows(await api("/admin/servers")); } catch (e) { notify(e.message); }
  }, [notify]);
  useEffect(() => { load(); const t = setInterval(load, 5000); return () => clearInterval(t); }, [load]);

  if (rows === null) return <div className="loading-block">Loading…</div>;
  if (rows.length === 0) return <div className="loading-block">No servers yet.</div>;
  return (
    <div className="admin-table-wrap">
      <table className="admin-table">
        <thead><tr>
          <th>Server</th><th>Owner</th><th>Status</th><th>Node</th>
          <th>Allocation</th><th>Address</th>
        </tr></thead>
        <tbody>
          {rows.map((s) => (
            <tr key={s.id}>
              <td className="mono">{s.name}</td>
              <td>{s.owner_email}</td>
              <td><StatusPill status={s.status} /></td>
              <td className="mono">{s.node_id ?? "—"}</td>
              <td className="mono">{s.allocation ?? "—"}</td>
              <td className="mono">{s.connect_address ?? "—"}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

function AdminUsers({ me, notify }) {
  const [rows, setRows] = useState(null);
  const load = useCallback(async () => {
    try { setRows(await api("/admin/users")); } catch (e) { notify(e.message); }
  }, [notify]);
  useEffect(() => { load(); }, [load]);

  const patch = async (id, body) => {
    try { await api(`/admin/users/${id}`, { method: "PATCH", body }); notify("Updated."); load(); }
    catch (e) { notify(e.message); }
  };
  const delUser = async (u) => {
    if (!window.confirm(`Delete ${u.email}? This removes their account AND destroys their servers.`)) return;
    try { await api(`/admin/users/${u.id}`, { method: "DELETE" }); notify("User deleted."); load(); }
    catch (e) { notify(e.message); }
  };
  const setQuota = (u) => {
    const v = window.prompt(`Free-server limit for ${u.email}:`, u.max_free_servers);
    if (v === null) return;
    const n = parseInt(v, 10);
    if (Number.isNaN(n) || n < 0) { notify("Enter a non-negative number."); return; }
    patch(u.id, { max_free_servers: n });
  };

  if (rows === null) return <div className="loading-block">Loading…</div>;
  return (
    <div className="admin-table-wrap">
      <table className="admin-table">
        <thead><tr>
          <th>Email</th><th>Servers</th><th>Limit</th><th>Admin</th><th>Actions</th>
        </tr></thead>
        <tbody>
          {rows.map((u) => (
            <tr key={u.id}>
              <td>{u.email}{u.id === me.id && <span className="you-tag">you</span>}</td>
              <td className="mono">{u.active_servers}</td>
              <td className="mono">{u.max_free_servers}</td>
              <td>{u.is_admin ? <span className="tag-admin">admin</span> : <span className="mono" style={{ color: "var(--text-mute)" }}>user</span>}</td>
              <td>
                <div className="admin-actions">
                  <button className="mini" onClick={() => setQuota(u)}>Quota</button>
                  <button className="mini" onClick={() => patch(u.id, { is_admin: !u.is_admin })}
                    disabled={u.id === me.id}>{u.is_admin ? "Revoke admin" : "Make admin"}</button>
                  <button className="mini danger" onClick={() => delUser(u)}
                    disabled={u.id === me.id}>Delete</button>
                </div>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

function AdminPanel({ me, notify }) {
  const [sub, setSub] = useState("servers");
  return (
    <div>
      <div className="page-head">
        <div><h2>Admin</h2><p>Manage all servers and users.</p></div>
        <div className="view-toggle">
          <button className={sub === "servers" ? "active" : ""} onClick={() => setSub("servers")}>Servers</button>
          <button className={sub === "users" ? "active" : ""} onClick={() => setSub("users")}>Users</button>
        </div>
      </div>
      {sub === "servers" ? <AdminServers notify={notify} /> : <AdminUsers me={me} notify={notify} />}
    </div>
  );
}

function Dashboard({ me, onLogout, reloadMe }) {
  const [tab, setTab] = useState("mine");
  const [servers, setServers] = useState(null);
  const [showCreate, setShowCreate] = useState(false);
  const [manageServer, setManageServer] = useState(null);
  const [toast, setToast] = useState("");
  const notify = useCallback((m) => { setToast(m); setTimeout(() => setToast(""), 2600); }, []);
  const load = useCallback(async () => {
    try { setServers(await api("/servers")); reloadMe(); } catch (e) { notify(e.message); }
  }, [notify, reloadMe]);
  useEffect(() => { if (tab === "mine") { load(); const t = setInterval(load, 5000); return () => clearInterval(t); } }, [load, tab]);

  const onDelete = async (server) => {
    if (!window.confirm(`Delete "${server.name}"? This permanently removes the server and its world.`)) return;
    try { await api(`/servers/${server.id}`, { method: "DELETE" }); notify("Server deleted."); load(); }
    catch (e) { notify(e.message); }
  };

  const atLimit = me.active_servers >= me.max_free_servers;

  return (
    <div>
      <div className="topbar">
        <div className="brand" style={{ margin: 0 }}><Cube size={22} /><h1 style={{ fontSize: 17 }}>jims<span>hosting</span></h1></div>
        <div className="topnav">
          <button className={tab === "mine" ? "active" : ""} onClick={() => setTab("mine")}>My Server</button>
          {me.is_admin && <button className={tab === "admin" ? "active" : ""} onClick={() => setTab("admin")}>Admin</button>}
        </div>
        <div className="who">
          <span>{me.email}</span>
          {me.is_admin && <span className="tag-admin">ADMIN</span>}
          <button className="logout" onClick={onLogout}>Sign out</button>
        </div>
      </div>
      <div className="container">
        {tab === "admin" && me.is_admin ? (
          <AdminPanel me={me} notify={notify} />
        ) : (
          <div>
            <div className="page-head">
              <div><h2>Your server</h2><p>Free tier · {me.active_servers}/{me.max_free_servers} used</p></div>
              <div className="head-actions">
                <button className="btn btn-inline" onClick={() => setShowCreate(true)} disabled={atLimit}
                  title={atLimit ? "You've reached your free server limit" : ""}>+ New free server</button>
              </div>
            </div>
            {servers === null ? <div className="loading-block">Loading…</div>
              : servers.length === 0 ? <EmptyState onCreate={() => setShowCreate(true)} />
              : <div className="grid">{servers.map((s) => <ServerCard key={s.id} server={s} onDelete={onDelete} onManage={setManageServer} notify={notify} />)}</div>}
          </div>
        )}
      </div>
      {showCreate && <CreateFree onClose={() => setShowCreate(false)} onCreated={load} notify={notify} />}
      {manageServer && <ManageModalFull server={manageServer} onClose={() => setManageServer(null)} notify={notify} />}
      <Toast msg={toast} />
    </div>
  );
}

function App() {
  const [me, setMe] = useState(undefined);
  const loadMe = useCallback(async () => {
    if (!getToken()) { setMe(null); return; }
    try { setMe(await api("/auth/me")); } catch { clearToken(); setMe(null); }
  }, []);
  useEffect(() => {
    loadMe();
    const onUnauth = () => setMe(null);
    window.addEventListener("bp-unauth", onUnauth);
    return () => window.removeEventListener("bp-unauth", onUnauth);
  }, [loadMe]);
  const logout = () => { clearToken(); setMe(null); };
  if (me === undefined) return <div className="boot">Loading…</div>;
  if (me === null) return <AuthScreen onAuthed={loadMe} />;
  return <Dashboard me={me} onLogout={logout} reloadMe={loadMe} />;
}

/* Full server management view — tabbed, Pterodactyl-parity.
   Loaded after app.jsx; uses the same global `api`, `getToken`, helpers. */

function fmtBytes(mb) {
  if (mb == null) return "—";
  if (mb >= 1024) return (mb / 1024).toFixed(1) + " GB";
  return mb + " MB";
}
function fmtSize(bytes) {
  if (bytes == null) return "—";
  const u = ["B", "KB", "MB", "GB"]; let i = 0; let n = bytes;
  while (n >= 1024 && i < u.length - 1) { n /= 1024; i++; }
  return n.toFixed(i ? 1 : 0) + " " + u[i];
}

/* ── Console tab (live) ── */
function ConsoleTab({ server, notify }) {
  const [lines, setLines] = useState([]);
  const [stats, setStats] = useState(null);
  const [state, setState] = useState("connecting");
  const [cmd, setCmd] = useState("");
  const [history, setHistory] = useState([]);
  const wsRef = useRef(null);
  const logRef = useRef(null);

  const append = useCallback((t) => setLines((p) => {
    const n = [...p, t]; return n.length > 800 ? n.slice(-800) : n;
  }), []);

  useEffect(() => {
    const proto = window.location.protocol === "https:" ? "wss:" : "ws:";
    const url = `${proto}//${window.location.host}/api/v1/servers/${server.id}/console?token=${encodeURIComponent(getToken())}`;
    const ws = new WebSocket(url); wsRef.current = ws;
    ws.onmessage = (ev) => {
      let m; try { m = JSON.parse(ev.data); } catch { return; }
      const { event, args } = m;
      if (event === "auth success") {
        setState((s) => s === "connecting" ? "running" : s);
        ws.send(JSON.stringify({ event: "send logs", args: [null] }));
        ws.send(JSON.stringify({ event: "send stats", args: [null] }));
      } else if (event === "console output" && args) append(args[0]);
      else if (event === "status" && args) setState(args[0]);
      else if (event === "stats" && args) {
        try { const s = JSON.parse(args[0]); setStats({
          cpu: s.cpu_absolute, mem: Math.round(s.memory_bytes / 1048576),
          disk: Math.round(s.disk_bytes / 1048576), state: s.state,
          netrx: s.network?.rx_bytes, nettx: s.network?.tx_bytes, uptime: s.uptime,
        }); setState(s.state); } catch {}
      } else if (event === "portal error" && args) { append(`[portal] ${args[0]}`); setState("error"); }
    };
    ws.onclose = () => setState((s) => s === "error" ? s : "disconnected");
    ws.onerror = () => setState("error");
    return () => { try { ws.close(); } catch {} };
  }, [server.id, append]);

  useEffect(() => { if (logRef.current) logRef.current.scrollTop = logRef.current.scrollHeight; }, [lines]);

  const power = async (signal) => {
    try { await api(`/servers/${server.id}/power`, { method: "POST", body: { signal } }); notify(`Sent ${signal}.`); }
    catch (e) { notify(e.message); }
  };
  const send = (e) => {
    e.preventDefault(); if (!cmd.trim()) return;
    const ws = wsRef.current;
    if (ws && ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({ event: "send command", args: [cmd] }));
    setHistory((h) => [...h, cmd]); setCmd("");
  };
  const sc = state === "running" ? "on" : state === "error" ? "err" : "off";

  return (
    <div>
      <div className="ctrl-row">
        <div className="ctrl-btns">
          <button className="mini" onClick={() => power("start")}>Start</button>
          <button className="mini" onClick={() => power("restart")}>Restart</button>
          <button className="mini danger" onClick={() => power("stop")}>Stop</button>
          <button className="mini danger" onClick={() => power("kill")}>Kill</button>
        </div>
        <div className="state-chip"><span className={`state-dot ${sc}`} />{state}</div>
      </div>
      {stats && (
        <div className="stat-grid">
          <div className="stat-box"><div className="sv">{stats.cpu?.toFixed(0) ?? 0}%</div><div className="sk">CPU</div></div>
          <div className="stat-box"><div className="sv">{fmtBytes(stats.mem)}</div><div className="sk">Memory</div></div>
          <div className="stat-box"><div className="sv">{fmtBytes(stats.disk)}</div><div className="sk">Disk</div></div>
          <div className="stat-box"><div className="sv">{stats.uptime ? Math.floor(stats.uptime / 60000) + "m" : "—"}</div><div className="sk">Uptime</div></div>
        </div>
      )}
      <div className="console" ref={logRef}>
        {lines.length === 0 ? <div className="console-empty">Connecting to console…</div>
          : lines.map((l, i) => <div key={i} className="console-line">{l}</div>)}
      </div>
      <form className="cmd-row" onSubmit={send}>
        <span className="cmd-prompt">/</span>
        <input value={cmd} onChange={(e) => setCmd(e.target.value)} placeholder="type a command and press enter" autoComplete="off" spellCheck="false" />
        <button className="mini" disabled={!cmd.trim()}>Send</button>
      </form>
    </div>
  );
}

/* ── Files tab ── */
function FilesTab({ server, notify }) {
  const [dir, setDir] = useState("/");
  const [items, setItems] = useState(null);
  const [editing, setEditing] = useState(null);
  const [content, setContent] = useState("");

  const load = useCallback(async () => {
    try { setItems(await api(`/servers/${server.id}/files?directory=${encodeURIComponent(dir)}`)); }
    catch (e) { notify(e.message); }
  }, [server.id, dir, notify]);
  useEffect(() => { load(); }, [load]);

  const open = async (f) => {
    const path = (dir === "/" ? "" : dir) + "/" + f.name;
    if (!f.is_file) { setDir(path); return; }
    if (f.size > 2 * 1024 * 1024) { notify("File too large to edit inline."); return; }
    try { const r = await api(`/servers/${server.id}/files/contents?file=${encodeURIComponent(path)}`);
      setContent(r.content); setEditing(path); } catch (e) { notify(e.message); }
  };
  const save = async () => {
    try { await api(`/servers/${server.id}/files/write`, { method: "POST", body: { file: editing, content } });
      notify("Saved."); setEditing(null); } catch (e) { notify(e.message); }
  };
  const del = async (f) => {
    if (!window.confirm(`Delete ${f.name}?`)) return;
    try { await api(`/servers/${server.id}/files/delete`, { method: "POST", body: { root: dir, files: [f.name] } });
      notify("Deleted."); load(); } catch (e) { notify(e.message); }
  };
  const up = () => { if (dir === "/") return; setDir(dir.split("/").slice(0, -1).join("/") || "/"); };

  if (editing !== null) {
    return (
      <div>
        <div className="file-head"><button className="mini" onClick={() => setEditing(null)}>← Back</button>
          <span className="mono file-path">{editing}</span><button className="mini" onClick={save}>Save</button></div>
        <textarea className="file-editor" value={content} onChange={(e) => setContent(e.target.value)} spellCheck="false" />
      </div>
    );
  }
  return (
    <div>
      <div className="file-head">
        <button className="mini" onClick={up} disabled={dir === "/"}>← Up</button>
        <span className="mono file-path">{dir}</span>
      </div>
      {items === null ? <div className="loading-block">Loading…</div> : (
        <div className="file-list">
          {items.length === 0 ? <div className="console-empty" style={{ padding: 14 }}>Empty directory.</div>
            : items.map((f) => (
            <div key={f.name} className="file-row">
              <button className="file-name" onClick={() => open(f)}>
                <span className="file-ico">{f.is_file ? "📄" : "📁"}</span>{f.name}
              </button>
              <span className="file-size mono">{f.is_file ? fmtSize(f.size) : ""}</span>
              <button className="mini danger" onClick={() => del(f)}>Del</button>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

/* ── Backups tab ── */
function BackupsTab({ server, notify }) {
  const [items, setItems] = useState(null);
  const [busy, setBusy] = useState(false);
  const load = useCallback(async () => {
    try { setItems(await api(`/servers/${server.id}/backups`)); } catch (e) { notify(e.message); }
  }, [server.id, notify]);
  useEffect(() => { load(); const t = setInterval(load, 5000); return () => clearInterval(t); }, [load]);

  const create = async () => {
    setBusy(true);
    try { await api(`/servers/${server.id}/backups`, { method: "POST", body: { name: "" } });
      notify("Backup started."); load(); } catch (e) { notify(e.message); } finally { setBusy(false); }
  };
  const restore = async (b) => {
    if (!window.confirm(`Restore "${b.name}"? This overwrites current files.`)) return;
    try { await api(`/servers/${server.id}/backups/${b.uuid}/restore`, { method: "POST" }); notify("Restore started."); }
    catch (e) { notify(e.message); }
  };
  const del = async (b) => {
    if (!window.confirm(`Delete backup "${b.name}"?`)) return;
    try { await api(`/servers/${server.id}/backups/${b.uuid}`, { method: "DELETE" }); notify("Deleted."); load(); }
    catch (e) { notify(e.message); }
  };
  const download = async (b) => {
    try { const r = await api(`/servers/${server.id}/backups/${b.uuid}/download`); window.open(r.url, "_blank"); }
    catch (e) { notify(e.message); }
  };
  return (
    <div>
      <div className="tab-head"><button className="mini" onClick={create} disabled={busy}>{busy ? "…" : "+ Create backup"}</button></div>
      {items === null ? <div className="loading-block">Loading…</div>
        : items.length === 0 ? <div className="console-empty" style={{ padding: 14 }}>No backups yet.</div> : (
        <div className="admin-table-wrap"><table className="admin-table">
          <thead><tr><th>Name</th><th>Size</th><th>Status</th><th>Actions</th></tr></thead>
          <tbody>{items.map((b) => (
            <tr key={b.uuid}>
              <td className="mono">{b.name}</td>
              <td className="mono">{fmtSize(b.bytes)}</td>
              <td>{b.is_successful ? <span className="tag-ok">done</span> : <span className="tag-wait">…</span>}</td>
              <td><div className="admin-actions">
                <button className="mini" onClick={() => download(b)} disabled={!b.is_successful}>Download</button>
                <button className="mini" onClick={() => restore(b)} disabled={!b.is_successful}>Restore</button>
                <button className="mini danger" onClick={() => del(b)}>Del</button>
              </div></td>
            </tr>
          ))}</tbody>
        </table></div>
      )}
    </div>
  );
}

/* ── Startup tab ── */
function StartupTab({ server, notify }) {
  const [data, setData] = useState(null);
  const load = useCallback(async () => {
    try { setData(await api(`/servers/${server.id}/startup`)); } catch (e) { notify(e.message); }
  }, [server.id, notify]);
  useEffect(() => { load(); }, [load]);

  const save = async (key, value) => {
    try { await api(`/servers/${server.id}/startup`, { method: "PUT", body: { key, value } }); notify("Saved."); load(); }
    catch (e) { notify(e.message); }
  };
  if (data === null) return <div className="loading-block">Loading…</div>;
  return (
    <div>
      <div className="startup-cmd mono">{data.startup_command}</div>
      <div className="var-list">
        {data.variables.map((v) => (
          <VarRow key={v.env} v={v} onSave={save} />
        ))}
      </div>
    </div>
  );
}
function VarRow({ v, onSave }) {
  const [val, setVal] = useState(v.value ?? "");
  return (
    <div className="var-row">
      <div className="var-meta"><div className="var-name">{v.name}</div><div className="var-env mono">{v.env}</div></div>
      <div className="var-edit">
        <input className="var-input mono" value={val} onChange={(e) => setVal(e.target.value)} disabled={!v.editable} />
        <button className="mini" onClick={() => onSave(v.env, val)} disabled={!v.editable || val === v.value}>Save</button>
      </div>
    </div>
  );
}

/* ── Schedules tab ── */
function SchedulesTab({ server, notify }) {
  const [items, setItems] = useState(null);
  const [show, setShow] = useState(false);
  const load = useCallback(async () => {
    try { setItems(await api(`/servers/${server.id}/schedules`)); } catch (e) { notify(e.message); }
  }, [server.id, notify]);
  useEffect(() => { load(); }, [load]);
  const del = async (s) => {
    if (!window.confirm(`Delete schedule "${s.name}"?`)) return;
    try { await api(`/servers/${server.id}/schedules/${s.id}`, { method: "DELETE" }); notify("Deleted."); load(); }
    catch (e) { notify(e.message); }
  };
  return (
    <div>
      <div className="tab-head"><button className="mini" onClick={() => setShow(true)}>+ New schedule</button></div>
      {items === null ? <div className="loading-block">Loading…</div>
        : items.length === 0 ? <div className="console-empty" style={{ padding: 14 }}>No schedules.</div> : (
        <div className="admin-table-wrap"><table className="admin-table">
          <thead><tr><th>Name</th><th>Cron</th><th>Active</th><th>Next run</th><th></th></tr></thead>
          <tbody>{items.map((s) => (
            <tr key={s.id}>
              <td>{s.name}</td>
              <td className="mono">{s.cron ? `${s.cron.minute} ${s.cron.hour} ${s.cron.day_of_month} ${s.cron.month} ${s.cron.day_of_week}` : "—"}</td>
              <td>{s.active ? <span className="tag-ok">on</span> : <span className="mono" style={{ color: "var(--text-mute)" }}>off</span>}</td>
              <td className="mono">{s.next_run_at ? new Date(s.next_run_at).toLocaleString() : "—"}</td>
              <td><button className="mini danger" onClick={() => del(s)}>Del</button></td>
            </tr>
          ))}</tbody>
        </table></div>
      )}
      {show && <ScheduleCreate server={server} onClose={() => setShow(false)} onCreated={load} notify={notify} />}
    </div>
  );
}
function ScheduleCreate({ server, onClose, onCreated, notify }) {
  const [f, setF] = useState({ name: "", minute: "0", hour: "*/6", day_of_month: "*", month: "*", day_of_week: "*" });
  const set = (k) => (e) => setF({ ...f, [k]: e.target.value });
  const submit = async (e) => {
    e.preventDefault();
    try { await api(`/servers/${server.id}/schedules`, { method: "POST", body: { ...f, is_active: true } });
      notify("Schedule created."); onCreated(); onClose(); } catch (e) { notify(e.message); }
  };
  return (
    <div className="modal-back" onMouseDown={onClose}>
      <div className="modal" onMouseDown={(e) => e.stopPropagation()}>
        <div className="modal-head"><h3>New schedule</h3><button className="x" onClick={onClose}>×</button></div>
        <div className="modal-body">
          <form onSubmit={submit}>
            <div className="field"><label>Name</label><input value={f.name} onChange={set("name")} required placeholder="Nightly restart" /></div>
            <div className="cron-grid">
              {["minute", "hour", "day_of_month", "month", "day_of_week"].map((k) => (
                <div key={k} className="field"><label>{k.replace(/_/g, " ")}</label>
                  <input className="mono" value={f[k]} onChange={set(k)} /></div>
              ))}
            </div>
            <div className="hint">Cron format. Example: minute <code>0</code>, hour <code>*/6</code> = every 6 hours.</div>
            <button className="btn" style={{ marginTop: 10 }}>Create schedule</button>
          </form>
        </div>
      </div>
    </div>
  );
}

/* ── Subusers tab ── */
function SubusersTab({ server, notify }) {
  const [items, setItems] = useState(null);
  const [email, setEmail] = useState("");
  const load = useCallback(async () => {
    try { setItems(await api(`/servers/${server.id}/subusers`)); } catch (e) { notify(e.message); }
  }, [server.id, notify]);
  useEffect(() => { load(); }, [load]);
  const add = async (e) => {
    e.preventDefault(); if (!email.trim()) return;
    try { await api(`/servers/${server.id}/subusers`, { method: "POST", body: { email } });
      notify("Subuser added."); setEmail(""); load(); } catch (e) { notify(e.message); }
  };
  const del = async (u) => {
    if (!window.confirm(`Remove ${u.email}?`)) return;
    try { await api(`/servers/${server.id}/subusers/${u.uuid}`, { method: "DELETE" }); notify("Removed."); load(); }
    catch (e) { notify(e.message); }
  };
  return (
    <div>
      <form className="cmd-row" onSubmit={add} style={{ marginBottom: 14 }}>
        <input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="email to grant access" type="email" />
        <button className="mini" disabled={!email.trim()}>Add subuser</button>
      </form>
      {items === null ? <div className="loading-block">Loading…</div>
        : items.length === 0 ? <div className="console-empty" style={{ padding: 14 }}>No subusers.</div> : (
        <div className="admin-table-wrap"><table className="admin-table">
          <thead><tr><th>Email</th><th>Permissions</th><th></th></tr></thead>
          <tbody>{items.map((u) => (
            <tr key={u.uuid}>
              <td>{u.email}</td>
              <td className="mono" style={{ fontSize: 11 }}>{u.permissions.length} perms</td>
              <td><button className="mini danger" onClick={() => del(u)}>Remove</button></td>
            </tr>
          ))}</tbody>
        </table></div>
      )}
    </div>
  );
}

/* ── The full manage modal with tabs ── */
const MANAGE_TABS = [
  ["console", "Console"], ["files", "Files"], ["backups", "Backups"],
  ["startup", "Startup"], ["schedules", "Schedules"], ["subusers", "Subusers"],
];
function ManageModalFull({ server, onClose, notify }) {
  const [tab, setTab] = useState("console");
  return (
    <div className="modal-back" onMouseDown={onClose}>
      <div className="modal manage" onMouseDown={(e) => e.stopPropagation()}>
        <div className="modal-head">
          <div><h3>{server.name}</h3>
            {server.connect_address && <div className="modal-sub mono">{server.connect_address}</div>}</div>
          <button className="x" onClick={onClose}>×</button>
        </div>
        <div className="manage-tabs">
          {MANAGE_TABS.map(([k, label]) => (
            <button key={k} className={tab === k ? "active" : ""} onClick={() => setTab(k)}>{label}</button>
          ))}
        </div>
        <div className="modal-body manage-body">
          {tab === "console" && <ConsoleTab server={server} notify={notify} />}
          {tab === "files" && <FilesTab server={server} notify={notify} />}
          {tab === "backups" && <BackupsTab server={server} notify={notify} />}
          {tab === "startup" && <StartupTab server={server} notify={notify} />}
          {tab === "schedules" && <SchedulesTab server={server} notify={notify} />}
          {tab === "subusers" && <SubusersTab server={server} notify={notify} />}
        </div>
      </div>
    </div>
  );
}


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