// Network detail with per-network tabs

const { useState: useStateN, useEffect: useEffectN } = React;

function fmtBytes(b) {
  if (!b || b === 0) return "0 B";
  if (b >= 1e9) return (b / 1e9).toFixed(1) + " GB";
  if (b >= 1e6) return (b / 1e6).toFixed(1) + " MB";
  if (b >= 1e3) return (b / 1e3).toFixed(0) + " KB";
  return b + " B";
}

function NetworkDetail({ ctx }) {
  const { t, networkId } = ctx;
  const [net, setNet] = useStateN(null);
  const [tab, setTab] = useStateN("devices");

  useEffectN(() => {
    if (!networkId) return;
    ww.get("/networks/" + networkId).then(setNet).catch(() => {});
  }, [networkId]);

  const role = net ? net.my_role : "";
  if (!net) return <div style={{ padding: 32, color: "var(--ink-3)" }}>Loading...</div>;

  const tabs = [
    ["devices",  t.network.devicesTab],
    ["topology", t.network.topologyTab],
    ["performance", t.network.performanceTab],
    ["netflow",  t.network.netflowTab],
    ["routes",   t.network.routesTab],
    ["acl",      t.network.aclTab],
    ["qos",      t.network.qosTab],
    ["dns",      t.network.dnsTab],
    ["audit",    t.network.auditTab],
    ["billing",  t.network.billingTab],
    ["settings", t.network.settingsTab],
  ];

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
      <div style={{ display: "flex", alignItems: "center", gap: 14 }}>
        <h1 style={{ fontSize: 24, fontWeight: 500, margin: 0, letterSpacing: -0.01 }}>{net.name}</h1>
      </div>

      <div className="no-scrollbar" style={{ display: "flex", gap: 2, borderBottom: "1px solid var(--line)", overflowX: "auto" }}>
        {tabs.map(([k, l]) => (
          <button key={k} onClick={() => setTab(k)} style={{
            padding: "10px 14px", border: 0, background: "transparent",
            borderBottom: `2px solid ${tab === k ? "var(--ink)" : "transparent"}`,
            marginBottom: -1,
            fontSize: 13, fontWeight: tab === k ? 500 : 400,
            color: tab === k ? "var(--ink)" : "var(--ink-3)",
            cursor: "pointer", whiteSpace: "nowrap", fontFamily: "inherit",
          }}>{l}</button>
        ))}
      </div>

      <div>
        {tab === "devices"  && <DevicesTab ctx={ctx} role={role} net={net}/>}
        {tab === "topology"    && <TopologyTab ctx={ctx}/>}
        {tab === "performance" && <MatrixView ctx={ctx}/>}
        {tab === "routes"   && <RoutesTab ctx={ctx} role={role}/>}
        {tab === "dns"      && <DnsTab ctx={ctx}/>}
        {tab === "acl"      && <AclTab ctx={ctx} role={role}/>}
        {tab === "qos"      && <QosTab ctx={ctx} role={role}/>}
        {tab === "netflow"  && <NetFlowTab ctx={ctx}/>}
        {tab === "audit"    && <NetAuditTab ctx={ctx} net={net}/>}
        {tab === "billing"  && <NetBillingTab ctx={ctx} net={net} role={role}/>}
        {tab === "settings" && <NetSettingsTab ctx={ctx} role={role} net={net}/>}
      </div>
    </div>
  );
}

function DevicesTab({ ctx, role, net }) {
  const { t, networkId } = ctx;
  const [devices, setDevices] = useStateN([]);
  const [pending, setPending] = useStateN([]);
  const [search, setSearch] = useStateN("");

  useEffectN(() => {
    if (!networkId) return;
    ww.get("/networks/" + networkId + "/devices").then(setDevices).catch(() => {});
    ww.get("/networks/" + networkId + "/pending").then(setPending).catch(() => {});
  }, [networkId]);

  function isOnline(d) {
    if (!d.last_seen_at) return false;
    return (Date.now() / 1000 - d.last_seen_at) < 120;
  }

  function relTime(ts) {
    if (!ts) return "—";
    const s = Math.floor(Date.now() / 1000 - ts);
    if (s < 60) return s + "s";
    if (s < 3600) return Math.floor(s / 60) + "m";
    if (s < 86400) return Math.floor(s / 3600) + "h";
    return Math.floor(s / 86400) + "d";
  }

  async function approveDevice(pid) {
    try {
      await ww.post("/networks/" + networkId + "/pending/" + pid + "/approve");
      setPending(pending.filter(p => p.id !== pid));
      ww.get("/networks/" + networkId + "/devices").then(setDevices).catch(() => {});
    } catch (err) { alert(err.message); }
  }

  async function deleteDevice(did) {
    if (!confirm("Delete this device?")) return;
    try {
      await ww.del("/networks/" + networkId + "/devices/" + did);
      setDevices(devices.filter(d => d.id !== did));
    } catch (err) { alert(err.message); }
  }

  const filtered = devices.filter(d => !search || d.instance_name.toLowerCase().includes(search.toLowerCase()));
  const cols = t.network.deviceColumns;
  const grid = "1.4fr 1fr 0.8fr 1fr 1fr 1.2fr";

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      <InstallCard ctx={ctx} net={net}/>
      <Card title={t.network.devicesTitle} pad={0}>
        <div className="ww-table" style={{ "--ww-cols": grid }}>
          <div className="ww-table__toolbar">
            <input placeholder={t.common.search} value={search} onChange={e => setSearch(e.target.value)} style={{ flex: 1 }}/>
          </div>
          <div className="ww-table__head">
            {[...cols, ""].map((c, i) => <div key={i}>{c}</div>)}
          </div>
          {pending.map((p) => (
            <div key={"p-"+p.id} className="ww-table__row ww-table__row--mono" style={{
              background: "color-mix(in oklab, var(--warn) 8%, transparent)",
            }}>
              <span style={{ color: "var(--ink)", fontWeight: 500 }}>{p.instance_name}</span>
              <span style={{ color: "var(--ink-3)" }}>—</span>
              <span style={{ color: "var(--ink-3)" }}>{p.platform || "—"}</span>
              <span style={{ display: "flex", alignItems: "center", gap: 6 }}>
                <Dot c="var(--warn)"/>
                {t.network.pending}
              </span>
              <span style={{ color: "var(--ink-3)" }}>—</span>
              <span style={{ display: "flex", gap: 6, justifyContent: "flex-end" }}>
                {can(role, "edit") && <Btn small onClick={() => approveDevice(p.id)}>{t.network.approve}</Btn>}
              </span>
            </div>
          ))}
          {filtered.map((d) => {
            const on = isOnline(d);
            return (
              <div key={d.id} className="ww-table__row ww-table__row--mono" style={{ opacity: on ? 1 : 0.55 }}>
                <span style={{ color: "var(--ink)", fontWeight: 500 }}>{d.instance_name}</span>
                <span style={{ color: "var(--ink-2)" }}>{d.ipv4_address || "—"}</span>
                <span style={{ color: "var(--ink-3)" }}>{d.platform || "—"}</span>
                <span style={{ display: "flex", alignItems: "center", gap: 6 }}>
                  <Dot c={on ? "var(--accent)" : "var(--ink-3)"}/>
                  {on ? t.common.online : t.common.offline}
                </span>
                <span style={{ color: "var(--ink-3)" }}>{relTime(d.last_seen_at)}</span>
                <span style={{ display: "flex", gap: 6, justifyContent: "flex-end" }}>
                  {can(role, "delete") && <Btn variant="ghost" small onClick={() => deleteDevice(d.id)}>{t.network.revoke}</Btn>}
                </span>
              </div>
            );
          })}
          {devices.length === 0 && pending.length === 0 && (
            <div style={{ padding: 24, textAlign: "center", color: "var(--ink-3)", fontSize: 13 }}>No devices yet.</div>
          )}
        </div>
      </Card>
    </div>
  );
}

function MatrixView({ ctx }) {
  const { t, networkId } = ctx;
  const [matrixData, setMatrixData] = useStateN({ nodes: [], edges: [] });

  useEffectN(() => {
    if (!networkId) return;
    ww.get("/networks/" + networkId + "/matrix").then(d => setMatrixData(d || { nodes: [], edges: [] })).catch(() => {});
  }, [networkId]);

  const names = (matrixData.nodes || []).map(n => n.name || n.instance_name || "?");
  const n = names.length;
  const [sel, setSel] = useStateN(null);

  // Build edge lookup from real API data.
  const edgeMap = {};
  (matrixData.edges || []).forEach(e => {
    edgeMap[e.from + "," + e.to] = e;
    if (!edgeMap[e.to + "," + e.from]) edgeMap[e.to + "," + e.from] = e;
  });

  if (n === 0) return <Card title={t.matrix.title}><div style={{ padding: 32, textAlign: "center", color: "var(--ink-3)" }}>No devices to display.</div></Card>;
  const selPair = sel || [0, Math.min(1, n - 1)];
  const cellFor = (i, j) => {
    if (i === j) return null;
    const edge = edgeMap[i + "," + j];
    if (!edge) return null;
    return edge.path_type || "unreachable";
  };
  const edgeFor = (i, j) => edgeMap[i + "," + j] || null;
  const colorFor = (v) => v === "direct" ? "var(--accent)" : v === "user_relay" ? "color-mix(in oklab, var(--accent) 50%, var(--bg-2))" : v === "managed_relay" ? "var(--warn)" : v === "unreachable" ? "color-mix(in oklab, var(--bad) 30%, var(--bg))" : "transparent";

  return (
    <Card title={t.matrix.title}>
      <div style={{ display: "grid", gridTemplateColumns: "1.4fr 1fr", gap: 18 }}>
        <div>
          <div style={{ display: "grid", gridTemplateColumns: `120px repeat(${n}, 1fr)`, gap: 3 }}>
            <div/>
            {names.map((nm, i) => (
              <div key={i} style={{ fontFamily: "var(--mono)", fontSize: 9, color: "var(--ink-3)",
                writingMode: "vertical-rl", transform: "rotate(180deg)", height: 64, paddingTop: 4 }}>{nm}</div>
            ))}
            {names.map((nm, i) => (
              <React.Fragment key={i}>
                <div style={{ fontFamily: "var(--mono)", fontSize: 10, color: "var(--ink-3)", paddingRight: 8, textAlign: "right" }}>{nm}</div>
                {names.map((_, j) => {
                  const v = cellFor(i, j);
                  const isSel = selPair[0] === i && selPair[1] === j;
                  return (
                    <button key={j} onClick={() => v && setSel([i, j])} style={{
                      aspectRatio: 1, background: v ? colorFor(v) : "transparent",
                      border: v ? "1px solid color-mix(in oklab, var(--ink) 6%, transparent)" : "1px dashed var(--line)",
                      borderRadius: 3, cursor: v ? "pointer" : "default", padding: 0,
                      boxShadow: isSel ? "0 0 0 2px var(--ink)" : "none",
                    }}/>
                  );
                })}
              </React.Fragment>
            ))}
          </div>
          <div style={{ display: "flex", gap: 16, marginTop: 16, flexWrap: "wrap" }}>
            {[[t.matrix.legend[0], "var(--accent)"], [t.matrix.legend[1], "color-mix(in oklab, var(--accent) 50%, var(--bg-2))"], [t.matrix.legend[2], "var(--warn)"], [t.matrix.legend[3], "color-mix(in oklab, var(--bad) 30%, var(--bg))"]].map(([l, c]) => (
              <div key={l} style={{ display: "flex", alignItems: "center", gap: 8, fontFamily: "var(--mono)", fontSize: 11, color: "var(--ink-3)" }}>
                <span style={{ width: 12, height: 12, background: c, borderRadius: 3 }}/>{l}
              </div>
            ))}
          </div>
        </div>

        <div style={{ background: "var(--bg-2)", border: "1px solid var(--line-2)", borderRadius: 8, padding: 16, display: "flex", flexDirection: "column", gap: 14 }}>
          <div className="kicker" style={{ fontSize: 10 }}>{t.matrix.selected}</div>
          <div style={{ display: "flex", gap: 10, alignItems: "center" }}>
            <div style={{ flex: 1 }}>
              <div className="kicker" style={{ fontSize: 10, marginBottom: 4 }}>from</div>
              <div className="mono" style={{ fontSize: 14 }}>{names[selPair[0]]}</div>
            </div>
            <span style={{ fontSize: 14, color: "var(--ink-3)" }}>↔</span>
            <div style={{ flex: 1 }}>
              <div className="kicker" style={{ fontSize: 10, marginBottom: 4 }}>to</div>
              <div className="mono" style={{ fontSize: 14 }}>{names[selPair[1]]}</div>
            </div>
          </div>
          {(() => {
            const selEdge = edgeFor(selPair[0], selPair[1]);
            const rttVal = selEdge && selEdge.rtt_us ? (selEdge.rtt_us / 1000).toFixed(1) + " ms" : "—";
            const lossVal = selEdge ? (selEdge.loss_rate * 100).toFixed(2) + " %" : "—";
            return [[t.matrix.rtt, rttVal], [t.matrix.loss, lossVal], [t.matrix.path, cellFor(selPair[0], selPair[1]) || "—"]];
          })().map(([k, v], i) => (
            <div key={i} style={{ display: "flex", justifyContent: "space-between", padding: "8px 0", borderTop: "1px solid var(--line)", fontFamily: "var(--mono)" }}>
              <span style={{ color: "var(--ink-3)", fontSize: 11 }}>{k}</span>
              <span style={{ color: "var(--ink)", fontSize: 13 }}>{v}</span>
            </div>
          ))}
          <div style={{ fontSize: 11, color: "var(--ink-3)", lineHeight: 1.55 }}>{t.matrix.hint}</div>
        </div>
      </div>
    </Card>
  );
}

function TopologyTab({ ctx }) {
  return <TopologyGraph ctx={ctx}/>;
}

function TopologyGraph({ ctx }) {
  const { t, networkId } = ctx;
  const [devices, setDevices] = useStateN([]);

  useEffectN(() => {
    if (!networkId) return;
    ww.get("/networks/" + networkId + "/devices").then(setDevices).catch(() => {});
  }, [networkId]);

  if (devices.length === 0) {
    return <Card title={t.topology.graph}><div style={{ padding: 48, textAlign: "center", color: "var(--ink-3)", fontSize: 13 }}>No devices in this network.</div></Card>;
  }

  // Auto-layout: arrange nodes in a circle.
  const cx = 450, cy = 230, radius = 180;
  const nodes = devices.map((d, i) => {
    const angle = (2 * Math.PI * i) / devices.length - Math.PI / 2;
    const online = d.last_seen_at && (Date.now() / 1000 - d.last_seen_at) < 120;
    return { id: d.instance_name, x: cx + radius * Math.cos(angle), y: cy + radius * Math.sin(angle), offline: !online };
  });

  const n = (id) => nodes.find(x => x.id === id);
  return (
    <Card title={t.topology.graph} pad={0}>
      <svg viewBox="0 0 900 460" style={{ width: "100%", display: "block", background: "var(--bg-2)" }}>
        <defs>
          <pattern id="grid" width="30" height="30" patternUnits="userSpaceOnUse">
            <path d="M 30 0 L 0 0 0 30" fill="none" stroke="var(--line-2)" strokeWidth="0.5"/>
          </pattern>
        </defs>
        <rect width="900" height="460" fill="url(#grid)"/>
        {nodes.map(nd => (
          <g key={nd.id}>
            <circle cx={nd.x} cy={nd.y} r="11" fill={nd.offline ? "var(--bg-3)" : "var(--bg)"} stroke={nd.offline ? "var(--ink-3)" : "var(--ink)"} strokeWidth="1.5"/>
            {!nd.offline && <circle cx={nd.x} cy={nd.y} r="4" fill="var(--accent)"/>}
            <text x={nd.x} y={nd.y + 28} textAnchor="middle" fill="var(--ink-2)" fontSize="11" fontFamily="var(--mono)">{nd.id}</text>
          </g>
        ))}
      </svg>
      <div style={{ padding: "12px 18px", borderTop: "1px solid var(--line-2)", display: "flex", gap: 20 }}>
        {[["online", "var(--accent)"], ["offline", "var(--ink-3)"]].map(([l, c]) => (
          <div key={l} style={{ display: "flex", alignItems: "center", gap: 8, fontFamily: "var(--mono)", fontSize: 11, color: "var(--ink-3)" }}>
            <Dot c={c}/>{l}
          </div>
        ))}
      </div>
    </Card>
  );
}

function RoutesTab({ ctx, role }) {
  const { t, networkId } = ctx;
  const editable = can(role, "edit");
  const [devices, setDevices] = useStateN([]);
  const [masquerade, setMasquerade] = useStateN([]);

  useEffectN(() => {
    if (!networkId) return;
    ww.get("/networks/" + networkId + "/devices").then(setDevices).catch(() => {});
    ww.get("/networks/" + networkId + "/masquerade").then(setMasquerade).catch(() => {});
  }, [networkId]);

  // Aggregate advertised routes from all devices.
  const advertisements = [];
  devices.forEach(d => {
    (d.advertised_routes || []).forEach(cidr => {
      advertisements.push({ device: d.instance_name, cidr, type: "subnet" });
    });
  });

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      <Card title={t.network.routesAdvTitle} pad={0}>
        <div className="ww-table" style={{ "--ww-cols": "1.2fr 1.5fr 1fr" }}>
          <div className="ww-table__help">{t.network.routesAdvHelp}</div>
          <div className="ww-table__head">
            {[t.network.routesAdvBy, t.network.routesCidr, t.network.routesAdvType].map((c, i) => <div key={i}>{c}</div>)}
          </div>
          {advertisements.map((r, i) => (
            <div key={i} className="ww-table__row ww-table__row--mono">
              <span>{r.device}</span>
              <span style={{ color: "var(--ink)" }}>{r.cidr}</span>
              <span style={{ color: "var(--ink-2)" }}>{r.type}</span>
            </div>
          ))}
          {advertisements.length === 0 && <div style={{ padding: 20, textAlign: "center", color: "var(--ink-3)", fontSize: 13 }}>No advertised routes.</div>}
        </div>
      </Card>

      <Card title={t.network.routesMasqTitle} pad={0}>
        <div className="ww-table" style={{ "--ww-cols": "1.5fr 1.5fr 1fr 0.6fr 40px" }}>
          <div className="ww-table__help">{t.network.routesMasqHelp}</div>
          <div className="ww-table__head">
            {[t.network.ruleSrc, t.network.ruleDst, t.network.ruleVia, t.network.ruleNat, ""].map((c, i) => <div key={i}>{c}</div>)}
          </div>
          {masquerade.map((r) => (
            <div key={r.id} className="ww-table__row ww-table__row--mono">
              <span>{r.src_cidr}</span>
              <span style={{ color: "var(--ink)" }}>{r.dst_cidr}</span>
              <span style={{ color: "var(--ink-2)" }}>{r.via_device_id.substring(0, 8)}...</span>
              <span><Pill tone={r.nat_enabled ? "accent" : "ghost"} small>{r.nat_enabled ? "on" : "off"}</Pill></span>
              <span>{editable && <Btn variant="ghost" small onClick={async () => { await ww.del("/networks/"+networkId+"/masquerade/"+r.id); setMasquerade(masquerade.filter(x=>x.id!==r.id)); }}>×</Btn>}</span>
            </div>
          ))}
          {masquerade.length === 0 && <div style={{ padding: 20, textAlign: "center", color: "var(--ink-3)", fontSize: 13 }}>No masquerade rules.</div>}
        </div>
      </Card>
    </div>
  );
}

function DnsTab({ ctx }) {
  const { t, networkId } = ctx;
  const [records, setRecords] = useStateN([]);

  useEffectN(() => {
    if (!networkId) return;
    ww.get("/networks/" + networkId + "/dns").then(setRecords).catch(() => {});
  }, [networkId]);

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      <Card pad={0}>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(2, 1fr)", gap: 0 }}>
          {records.map((r, i) => (
            <div key={i} style={{
              padding: "14px 18px", display: "flex", justifyContent: "space-between", alignItems: "center",
              fontFamily: "var(--mono)", fontSize: 13,
              borderTop: i >= 2 ? "1px solid var(--line-2)" : 0,
              borderRight: i % 2 === 0 ? "1px solid var(--line-2)" : 0,
            }}>
              <span style={{ color: r.online ? "var(--ink)" : "var(--ink-3)" }}>{r.hostname}</span>
              <span style={{ color: "var(--ink-2)", fontSize: 12 }}>{r.ip}</span>
            </div>
          ))}
          {records.length === 0 && <div style={{ padding: 24, textAlign: "center", color: "var(--ink-3)", fontSize: 13 }}>No DNS records.</div>}
        </div>
      </Card>
    </div>
  );
}

function AclTab({ ctx, role }) {
  const { t, networkId } = ctx;
  const editable = can(role, "edit");
  const [rules, setRules] = useStateN([]);
  const [adding, setAdding] = useStateN(false);
  const [aclForm, setAclForm] = useStateN({ action: "allow", chain: "inbound", source_cidr: "", dest_cidr: "", protocol: "", ports: "", l7_protocol: "", domain_pattern: "", priority: 100 });

  useEffectN(() => {
    if (!networkId) return;
    ww.get("/networks/" + networkId + "/acl").then(setRules).catch(() => {});
  }, [networkId]);

  async function deleteRule(id) {
    try { await ww.del("/networks/" + networkId + "/acl/" + id); setRules(rules.filter(r => r.id !== id)); }
    catch (e) { alert(e.message); }
  }

  async function createRule() {
    try {
      const r = await ww.post("/networks/" + networkId + "/acl", aclForm);
      setRules([...rules, r]);
      setAdding(false);
      setAclForm({ action: "allow", chain: "inbound", source_cidr: "", dest_cidr: "", protocol: "", ports: "", l7_protocol: "", domain_pattern: "", priority: 100 });
    } catch (e) { alert(e.message); }
  }

  const grid = "0.5fr 1.2fr 1.5fr 0.7fr 40px";
  return (
    <Card title={t.network.aclTitle} action={editable && <Btn small onClick={() => setAdding(!adding)}>+ {t.network.aclRule}</Btn>} pad={0}>
      <div className="ww-table" style={{ "--ww-cols": grid }}>
        <div className="ww-table__help">{t.network.aclHelp}</div>
        {adding && (
          <div style={{ padding: "14px 18px", borderBottom: "1px solid var(--line-2)", display: "flex", flexDirection: "column", gap: 10 }}>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: 10 }}>
              <div><div className="kicker" style={{ marginBottom: 4, fontSize: 10 }}>Source CIDR</div><input value={aclForm.source_cidr} onChange={e => setAclForm({...aclForm, source_cidr: e.target.value})} placeholder="0.0.0.0/0" style={{ width: "100%", fontFamily: "var(--mono)", fontSize: 12 }}/></div>
              <div><div className="kicker" style={{ marginBottom: 4, fontSize: 10 }}>Dest CIDR</div><input value={aclForm.dest_cidr} onChange={e => setAclForm({...aclForm, dest_cidr: e.target.value})} placeholder="0.0.0.0/0" style={{ width: "100%", fontFamily: "var(--mono)", fontSize: 12 }}/></div>
              <div><div className="kicker" style={{ marginBottom: 4, fontSize: 10 }}>Protocol</div><select value={aclForm.protocol} onChange={e => setAclForm({...aclForm, protocol: e.target.value})} style={{ width: "100%" }}><option value="">any</option><option value="tcp">tcp</option><option value="udp">udp</option><option value="icmp">icmp</option></select></div>
              <div><div className="kicker" style={{ marginBottom: 4, fontSize: 10 }}>Ports</div><input value={aclForm.ports} onChange={e => setAclForm({...aclForm, ports: e.target.value})} placeholder="80,443" style={{ width: "100%", fontFamily: "var(--mono)", fontSize: 12 }}/></div>
            </div>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: 10 }}>
              <div><div className="kicker" style={{ marginBottom: 4, fontSize: 10 }}>L7 Protocol</div><select value={aclForm.l7_protocol} onChange={e => setAclForm({...aclForm, l7_protocol: e.target.value})} style={{ width: "100%" }}><option value="">none</option>{["tls","http","quic","dns","ssh","rdp","bittorrent","stun","dtls","ntp"].map(p => <option key={p} value={p}>{p}</option>)}</select></div>
              <div><div className="kicker" style={{ marginBottom: 4, fontSize: 10 }}>Domain Pattern</div><input value={aclForm.domain_pattern} onChange={e => setAclForm({...aclForm, domain_pattern: e.target.value})} placeholder="*.example.com" style={{ width: "100%", fontFamily: "var(--mono)", fontSize: 12 }}/></div>
              <div><div className="kicker" style={{ marginBottom: 4, fontSize: 10 }}>Action</div><select value={aclForm.action} onChange={e => setAclForm({...aclForm, action: e.target.value})} style={{ width: "100%" }}><option value="allow">allow</option><option value="deny">deny</option></select></div>
              <div><div className="kicker" style={{ marginBottom: 4, fontSize: 10 }}>Chain</div><select value={aclForm.chain} onChange={e => setAclForm({...aclForm, chain: e.target.value})} style={{ width: "100%" }}><option value="inbound">inbound</option><option value="outbound">outbound</option></select></div>
            </div>
            <div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
              <Btn variant="ghost" small onClick={() => setAdding(false)}>{t.common.cancel}</Btn>
              <Btn small onClick={createRule}>{t.common.save}</Btn>
            </div>
          </div>
        )}
        <div className="ww-table__head">
          {[t.network.ruleKind, t.network.ruleSrc, t.network.ruleDst, t.network.ruleAction, ""].map((c, i) => <div key={i}>{c}</div>)}
        </div>
        {rules.map((r) => {
          const kind = r.l7_protocol ? "L7" : "L3";
          const src = r.source_cidr || "*";
          const dst = r.l7_protocol ? (r.domain_pattern || r.l7_protocol) : (r.dest_cidr || "*");
          return (
            <div key={r.id} className="ww-table__row ww-table__row--mono">
              <span><Pill tone={kind === "L7" ? "accent" : "ghost"} small>{kind}</Pill></span>
              <span>{src}</span>
              <span style={{ color: "var(--ink)" }}>{dst}</span>
              <span><Pill tone={r.action === "allow" ? "accent" : "bad"} small>{r.action}</Pill></span>
              <span>{editable && <Btn variant="ghost" small onClick={() => deleteRule(r.id)}>×</Btn>}</span>
            </div>
          );
        })}
        {rules.length === 0 && <div style={{ padding: 20, textAlign: "center", color: "var(--ink-3)", fontSize: 13 }}>No ACL rules.</div>}
      </div>
    </Card>
  );
}

function QosTab({ ctx, role }) {
  const { t, networkId } = ctx;
  const [rules, setRules] = useStateN([]);
  const [adding, setAdding] = useStateN(false);
  const [qosForm, setQosForm] = useStateN({ match_protocol: "", match_src_cidr: "", match_dst_cidr: "", match_ports: "", match_l7: "", match_domain: "", dscp: 0, rate_limit_bps: 0, description: "", priority: 100 });

  useEffectN(() => {
    if (!networkId) return;
    ww.get("/networks/" + networkId + "/qos").then(setRules).catch(() => {});
  }, [networkId]);

  async function deleteRule(id) {
    try { await ww.del("/networks/" + networkId + "/qos/" + id); setRules(rules.filter(r => r.id !== id)); }
    catch (e) { alert(e.message); }
  }

  async function createRule() {
    try {
      const r = await ww.post("/networks/" + networkId + "/qos", { ...qosForm, dscp: parseInt(qosForm.dscp) || 0, rate_limit_bps: parseInt(qosForm.rate_limit_bps) || 0, priority: parseInt(qosForm.priority) || 100 });
      setRules([...rules, r]);
      setAdding(false);
      setQosForm({ match_protocol: "", match_src_cidr: "", match_dst_cidr: "", match_ports: "", match_l7: "", match_domain: "", dscp: 0, rate_limit_bps: 0, description: "", priority: 100 });
    } catch (e) { alert(e.message); }
  }

  function fmtRate(bps) {
    if (!bps || bps === 0) return "unlimited";
    if (bps >= 1e9) return (bps / 1e9).toFixed(1) + " Gbps";
    if (bps >= 1e6) return (bps / 1e6).toFixed(0) + " Mbps";
    if (bps >= 1e3) return (bps / 1e3).toFixed(0) + " Kbps";
    return bps + " bps";
  }

  function dscpName(v) {
    const names = { 0: "BE", 46: "EF", 26: "AF31", 34: "AF41", 8: "CS1", 16: "CS2", 24: "CS3", 32: "CS4", 40: "CS5", 48: "CS6" };
    return names[v] || ("DSCP " + v);
  }

  const prioForDscp = (v) => v >= 40 ? "high" : v >= 16 ? "mid" : "low";
  const prioTone = { high: "accent", mid: "ghost", low: "warn" };

  return (
    <Card title={t.network.qosRules} action={can(role, "edit") && <Btn small onClick={() => setAdding(!adding)}>+ {t.network.qosAdd}</Btn>} pad={0}>
      <div className="ww-table" style={{ "--ww-cols": "2fr 0.8fr 1fr 0.8fr 40px" }}>
        <div className="ww-table__help">{t.network.qosHelp}</div>
        {adding && (
          <div style={{ padding: "14px 18px", borderBottom: "1px solid var(--line-2)", display: "flex", flexDirection: "column", gap: 10 }}>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: 10 }}>
              <div><div className="kicker" style={{ marginBottom: 4, fontSize: 10 }}>Description</div><input value={qosForm.description} onChange={e => setQosForm({...qosForm, description: e.target.value})} placeholder="Rule name" style={{ width: "100%", fontSize: 12 }}/></div>
              <div><div className="kicker" style={{ marginBottom: 4, fontSize: 10 }}>Match Protocol</div><select value={qosForm.match_protocol} onChange={e => setQosForm({...qosForm, match_protocol: e.target.value})} style={{ width: "100%" }}><option value="">any</option><option value="tcp">tcp</option><option value="udp">udp</option><option value="icmp">icmp</option></select></div>
              <div><div className="kicker" style={{ marginBottom: 4, fontSize: 10 }}>DSCP (0-63)</div><input type="number" min="0" max="63" value={qosForm.dscp} onChange={e => setQosForm({...qosForm, dscp: e.target.value})} style={{ width: "100%", fontFamily: "var(--mono)", fontSize: 12 }}/></div>
              <div><div className="kicker" style={{ marginBottom: 4, fontSize: 10 }}>Rate Limit (bps)</div><input type="number" min="0" value={qosForm.rate_limit_bps} onChange={e => setQosForm({...qosForm, rate_limit_bps: e.target.value})} placeholder="0 = unlimited" style={{ width: "100%", fontFamily: "var(--mono)", fontSize: 12 }}/></div>
            </div>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr 1fr", gap: 10 }}>
              <div><div className="kicker" style={{ marginBottom: 4, fontSize: 10 }}>Src CIDR</div><input value={qosForm.match_src_cidr} onChange={e => setQosForm({...qosForm, match_src_cidr: e.target.value})} placeholder="0.0.0.0/0" style={{ width: "100%", fontFamily: "var(--mono)", fontSize: 12 }}/></div>
              <div><div className="kicker" style={{ marginBottom: 4, fontSize: 10 }}>Dst CIDR</div><input value={qosForm.match_dst_cidr} onChange={e => setQosForm({...qosForm, match_dst_cidr: e.target.value})} placeholder="0.0.0.0/0" style={{ width: "100%", fontFamily: "var(--mono)", fontSize: 12 }}/></div>
              <div><div className="kicker" style={{ marginBottom: 4, fontSize: 10 }}>L7 Protocol</div><select value={qosForm.match_l7} onChange={e => setQosForm({...qosForm, match_l7: e.target.value})} style={{ width: "100%" }}><option value="">none</option>{["tls","http","quic","dns","ssh","rdp"].map(p => <option key={p} value={p}>{p}</option>)}</select></div>
              <div><div className="kicker" style={{ marginBottom: 4, fontSize: 10 }}>Domain</div><input value={qosForm.match_domain} onChange={e => setQosForm({...qosForm, match_domain: e.target.value})} placeholder="*.example.com" style={{ width: "100%", fontFamily: "var(--mono)", fontSize: 12 }}/></div>
            </div>
            <div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
              <Btn variant="ghost" small onClick={() => setAdding(false)}>{t.common.cancel}</Btn>
              <Btn small onClick={createRule}>{t.common.save}</Btn>
            </div>
          </div>
        )}
        <div className="ww-table__head">
          {[t.network.qosMatch, t.network.qosDscp, t.network.qosRate, t.network.qosPriority, ""].map((c, i) => <div key={i}>{c}</div>)}
        </div>
        {rules.map((r) => {
          const match = [r.match_src_cidr, r.match_dst_cidr, r.match_l7, r.match_domain].filter(Boolean).join(" → ") || r.description || "*";
          const prio = prioForDscp(r.dscp);
          return (
            <div key={r.id} className="ww-table__row ww-table__row--mono">
              <span style={{ color: "var(--ink)" }}>{match}</span>
              <span style={{ color: "var(--accent)" }}>{dscpName(r.dscp)}</span>
              <span style={{ color: "var(--ink-2)" }}>{fmtRate(r.rate_limit_bps)}</span>
              <span><Pill tone={prioTone[prio]} small>{prio}</Pill></span>
              <span>{can(role, "edit") && <Btn variant="ghost" small onClick={() => deleteRule(r.id)}>×</Btn>}</span>
            </div>
          );
        })}
        {rules.length === 0 && <div style={{ padding: 20, textAlign: "center", color: "var(--ink-3)", fontSize: 13 }}>No QoS rules.</div>}
      </div>
    </Card>
  );
}

function NetFlowTab({ ctx }) {
  const { t, networkId } = ctx;
  const [flows, setFlows] = useStateN([]);
  const [hourly, setHourly] = useStateN([]);
  const [flowSearch, setFlowSearch] = useStateN("");
  const [flowProto, setFlowProto] = useStateN("");
  const [flowPeer, setFlowPeer] = useStateN("");

  useEffectN(() => {
    if (!networkId) return;
    ww.get("/networks/" + networkId + "/flows").then(setFlows).catch(() => {});
    ww.get("/networks/" + networkId + "/stats/hourly").then(setHourly).catch(() => {});
  }, [networkId]);

  // Compute throughput chart data from hourly stats.
  const data = hourly.length > 0
    ? hourly.slice(-60).map(r => (r.total_tx_bytes + r.total_rx_bytes) / 1e6)
    : Array.from({ length: 60 }, () => 0);
  const max = Math.max(1, ...data);
  const filteredFlows = flows.filter(f => {
    if (flowSearch && !(`${f.src_ip} ${f.dst_ip}`).toLowerCase().includes(flowSearch.toLowerCase())) return false;
    if (flowProto && f.protocol !== flowProto) return false;
    if (flowPeer && f.src_ip !== flowPeer && f.dst_ip !== flowPeer) return false;
    return true;
  });
  const peers = [...new Set(flows.flatMap(f => [f.src_ip, f.dst_ip]))].sort();
  const top = filteredFlows.map(f => [
    `${f.src_ip} → ${f.dst_ip}`,
    `${f.protocol}/${f.dst_port}`,
    fmtBytes(f.tx_bytes + f.rx_bytes),
    "",
  ]);
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      <div style={{ display: "grid", gridTemplateColumns: "2fr 1fr", gap: 14 }}>
        <Card title={t.network.netflowChart}>
          <svg viewBox="0 0 600 180" style={{ width: "100%", height: 180 }}>
            {[0, 25, 50, 75].map(y => <line key={y} x1="0" y1={180 - y * 1.6} x2="600" y2={180 - y * 1.6} stroke="var(--line-2)" strokeWidth="0.5"/>)}
            <polyline points={data.map((v, i) => `${i * 10},${180 - (v / max) * 160}`).join(" ")}
              fill="none" stroke="var(--accent)" strokeWidth="1.5"/>
            <polygon points={`0,180 ${data.map((v, i) => `${i * 10},${180 - (v / max) * 160}`).join(" ")} 600,180`}
              fill="var(--accent-soft)" opacity="0.5"/>
          </svg>
        </Card>
        <Card title={t.network.netflowProto}>
          <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
            {(() => {
              if (flows.length === 0) return <div style={{ padding: 16, textAlign: "center", color: "var(--ink-3)", fontSize: 12 }}>No flow data.</div>;
              const totalBytes = flows.reduce((s, f) => s + (f.tx_bytes || 0) + (f.rx_bytes || 0), 0) || 1;
              const buckets = {};
              flows.forEach(f => {
                const key = f.protocol + "/" + f.dst_port;
                buckets[key] = (buckets[key] || 0) + (f.tx_bytes || 0) + (f.rx_bytes || 0);
              });
              const sorted = Object.entries(buckets).sort((a, b) => b[1] - a[1]).slice(0, 5);
              const colors = ["var(--accent)", "var(--ink-2)", "var(--warn)", "var(--ink-3)", "var(--bad)"];
              return sorted.map(([label, bytes], i) => {
                const pct = Math.round(bytes / totalBytes * 100);
                return (
                  <div key={label}>
                    <div style={{ display: "flex", justifyContent: "space-between", fontSize: 12, fontFamily: "var(--mono)", marginBottom: 5 }}>
                      <span>{label}</span><span style={{ color: "var(--ink-3)" }}>{pct}%</span>
                    </div>
                    <div style={{ height: 6, background: "var(--bg-3)", borderRadius: 999, overflow: "hidden" }}>
                      <div style={{ width: `${pct}%`, height: "100%", background: colors[i] || "var(--ink-3)" }}/>
                    </div>
                  </div>
                );
              });
            })()}
          </div>
        </Card>
      </div>
      <Card title={t.network.netflowTop} pad={0}>
        <div className="ww-table" style={{ "--ww-cols": "2fr 1fr 1fr" }}>
          <div className="ww-table__toolbar">
            <input placeholder={t.network.netflowSearch} value={flowSearch} onChange={e => setFlowSearch(e.target.value)} style={{ flex: 1 }}/>
            <select value={flowProto} onChange={e => setFlowProto(e.target.value)} style={{ minWidth: 120 }}>
              <option value="">{t.network.netflowAllProto}</option>
              <option value="tcp">tcp</option><option value="udp">udp</option><option value="icmp">icmp</option>
            </select>
            <select value={flowPeer} onChange={e => setFlowPeer(e.target.value)} style={{ minWidth: 120 }}>
              <option value="">{t.network.netflowAllPeer}</option>
              {peers.map(p => <option key={p} value={p}>{p}</option>)}
            </select>
          </div>
          <div className="ww-table__head">
            {[t.network.netflowPair, t.network.netflowProtoCol, t.network.netflowBytes].map(c => <div key={c}>{c}</div>)}
          </div>
          {top.map((r, i) => (
            <div key={i} className="ww-table__row ww-table__row--mono">
              <span style={{ color: "var(--ink)" }}>{r[0]}</span>
              <span style={{ color: "var(--accent)" }}>{r[1]}</span>
              <span style={{ color: "var(--ink-2)" }}>{r[2]}</span>
            </div>
          ))}
          {top.length === 0 && <div style={{ padding: 20, textAlign: "center", color: "var(--ink-3)", fontSize: 13 }}>No flows recorded.</div>}
        </div>
      </Card>
    </div>
  );
}

function InstallCard({ ctx, net }) {
  const { t } = ctx;
  const code = net.config_code || "";
  const platforms = [
    ["linux",   "Linux",   `ww connect ${code}`],
    ["macos",   "macOS",   `ww connect ${code}`],
    ["windows", "Windows", `ww.exe connect ${code}`],
  ];
  const [plat, setPlat] = useStateN("linux");
  const cmd = platforms.find(p => p[0] === plat)?.[2] || "";

  if (!code) return null;

  return (
    <Card title={t.network.installCode} pad={0}>
      <div style={{ display: "flex", gap: 2, padding: "0 18px", borderBottom: "1px solid var(--line-2)" }}>
        {platforms.map(([k, l]) => (
          <button key={k} onClick={() => setPlat(k)} style={{
            padding: "10px 14px", border: 0, background: "transparent",
            borderBottom: `2px solid ${plat === k ? "var(--ink)" : "transparent"}`,
            marginBottom: -1,
            fontSize: 12.5, fontWeight: plat === k ? 500 : 400,
            color: plat === k ? "var(--ink)" : "var(--ink-3)",
            cursor: "pointer", fontFamily: "inherit",
          }}>{l}</button>
        ))}
      </div>
      <div style={{ padding: 18, display: "flex", alignItems: "center", gap: 10 }}>
        <div style={{ flex: 1, background: "var(--ink)", color: "var(--bg)", padding: "12px 16px", borderRadius: 8, fontFamily: "var(--mono)", fontSize: 12.5, overflow: "auto" }}>
          <span style={{ color: "var(--accent)" }}>$</span> {cmd}
        </div>
        <Btn variant="ghost" small onClick={() => { navigator.clipboard.writeText(cmd); }}>Copy</Btn>
      </div>
    </Card>
  );
}

function NetAuditTab({ ctx, net }) {
  const { t, networkId } = ctx;
  const [events, setEvents] = useStateN([]);
  const [search, setSearch] = useStateN("");
  const [filter, setFilter] = useStateN("");

  useEffectN(() => {
    if (!networkId) return;
    ww.get("/networks/" + networkId + "/timeline").then(setEvents).catch(() => {});
  }, [networkId]);

  function fmtTs(ts) {
    return new Date(ts * 1000).toISOString().replace("T", " ").slice(0, 19);
  }

  const filtered = events.filter(e => {
    if (search && !JSON.stringify(e).toLowerCase().includes(search.toLowerCase())) return false;
    if (filter && !e.event_type.startsWith(filter)) return false;
    return true;
  });

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      <Card title={t.network.auditTitle} pad={0}>
        <div className="ww-table" style={{ "--ww-cols": "1.6fr 1.2fr 2fr 1.2fr 1fr" }}>
          <div className="ww-table__toolbar">
            <input placeholder={t.common.search} value={search} onChange={e => setSearch(e.target.value)} style={{ flex: 1 }}/>
            <select value={filter} onChange={e => setFilter(e.target.value)} style={{ minWidth: 140 }}>
              <option value="">{t.network.auditAllActions}</option>
              <option value="device.">device.*</option>
              <option value="acl.">acl.*</option>
              <option value="member.">member.*</option>
            </select>
          </div>
          <div className="ww-table__head">
            {[t.network.auditTimestamp, t.network.auditAction, t.network.auditSubject, t.network.auditActor, ""].map((c, i) => <div key={i}>{c}</div>)}
          </div>
          {filtered.map((e) => (
            <div key={e.id} className="ww-table__row ww-table__row--mono ww-table__row--sm">
              <span style={{ color: "var(--ink-3)" }}>{fmtTs(e.ts)}</span>
              <span style={{ color: "var(--accent)" }}>{e.event_type}</span>
              <span style={{ color: "var(--ink)" }}>{e.device_name || e.detail || "—"}</span>
              <span style={{ color: "var(--ink-2)" }}>{e.actor || "—"}</span>
              <span style={{ color: "var(--ink-3)" }}>{e.detail || ""}</span>
            </div>
          ))}
          {filtered.length === 0 && <div style={{ padding: 20, textAlign: "center", color: "var(--ink-3)", fontSize: 13 }}>No events.</div>}
        </div>
      </Card>
    </div>
  );
}

function NetBillingTab({ ctx, net, role }) {
  const { t, networkId } = ctx;
  const editable = can(role, "billing");
  const [orders, setOrders] = useStateN([]);

  useEffectN(() => {
    if (!networkId) return;
    ww.get("/networks/" + networkId + "/billing/orders").then(setOrders).catch(() => {});
  }, [networkId]);

  const plans = [
    { id: "free",       price: "$0",   priceCny: "¥0",    blurb: t.billing.freeBlurb },
    { id: "pro",        price: "$36",  priceCny: "¥99",   blurb: t.billing.proBlurb },
    { id: "enterprise", price: "$108", priceCny: "¥299",  blurb: t.billing.entBlurb },
  ];

  async function handleUpgrade(plan) {
    try {
      const res = await ww.post("/networks/" + networkId + "/billing/stripe", { plan, period: "monthly" });
      if (res.checkout_url) window.location.href = res.checkout_url;
    } catch (e) { alert(e.message); }
  }

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 12 }}>
        {plans.map(p => {
          const active = p.id === (net.plan || "free");
          return (
            <Card key={p.id}>
              <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 8 }}>
                <div style={{ fontSize: 15, fontWeight: 500 }}>{t.plans[p.id]}</div>
                {active && <Pill tone="accent" small>{t.billing.current}</Pill>}
              </div>
              <div style={{ fontSize: 26, fontWeight: 500, fontFamily: "var(--mono)", marginBottom: 6 }}>{p.price}<span style={{ fontSize: 12, color: "var(--ink-3)", fontWeight: 400 }}> / mo</span></div>
              <div style={{ fontSize: 12, color: "var(--ink-3)", lineHeight: 1.55, marginBottom: 12 }}>{p.blurb}</div>
              {!active && editable && p.id !== "free" && <Btn small onClick={() => handleUpgrade(p.id)}>{t.common.upgrade} →</Btn>}
            </Card>
          );
        })}
      </div>

      <Card title={t.billing.invoices} pad={0}>
        <div className="ww-table" style={{ "--ww-cols": "1fr 1fr 1fr 1fr 1fr" }}>
          <div className="ww-table__head">
            {[t.billing.invoiceDate, "Plan", "Period", t.billing.invoiceAmount, t.billing.invoiceStatus].map((c, i) => <div key={i}>{c}</div>)}
          </div>
          {orders.map((o) => (
            <div key={o.id} className="ww-table__row ww-table__row--mono">
              <span style={{ color: "var(--ink-3)" }}>{new Date((o.paid_at || o.created_at) * 1000).toISOString().slice(0, 10)}</span>
              <span>{o.plan}</span>
              <span style={{ color: "var(--ink-2)" }}>{o.period}</span>
              <span>{o.currency === "cny" ? "¥" : "$"}{(o.amount / 100).toFixed(2)}</span>
              <span><Pill tone={o.status === "paid" ? "accent" : "warn"} small>{o.status}</Pill></span>
            </div>
          ))}
          {orders.length === 0 && <div style={{ padding: 20, textAlign: "center", color: "var(--ink-3)", fontSize: 13 }}>No orders yet.</div>}
        </div>
      </Card>
    </div>
  );
}

function NetSettingsTab({ ctx, role, net }) {
  const { t, networkId } = ctx;
  const editable = can(role, "edit");
  const canInvite = can(role, "invite");
  const cidr = net.ipv4_cidr || "10.0.0.0/24";
  const [inviteOpen, setInviteOpen] = useStateN(false);
  const [invEmail, setInvEmail] = useStateN("");
  const [invRole, setInvRole] = useStateN("editor");
  const [members, setMembers] = useStateN([]);
  const memberRoles = [["editor", t.roles.editor], ["reader", t.roles.reader]];

  async function sendInvite() {
    if (!invEmail.trim()) return;
    try {
      await ww.post("/networks/" + networkId + "/members", { email: invEmail.trim(), role: invRole });
      setInvEmail(""); setInviteOpen(false);
      ww.get("/networks/" + networkId + "/members").then(setMembers).catch(() => {});
    } catch (e) { alert(e.message); }
  }
  const [a, b] = cidr.split("/");
  const hostBits = 32 - parseInt(b || "24", 10);
  const totalHosts = Math.max(0, (1 << hostBits) - 2);
  const used = net.device_count || 0;

  useEffectN(() => {
    if (!networkId) return;
    ww.get("/networks/" + networkId + "/members").then(setMembers).catch(() => {});
    ww.get("/tokens").then(setTokens).catch(() => {});
  }, [networkId]);

  async function createToken() {
    if (!tokenForm.name.trim()) return;
    try {
      const body = { name: tokenForm.name.trim(), scope: tokenForm.scope };
      if (tokenForm.expires_days) body.expires_days = parseInt(tokenForm.expires_days);
      const r = await ww.post("/tokens", body);
      setNewTokenValue(r.token);
      setTokens([r, ...tokens]);
      setTokenForm({ name: "", scope: "readonly", expires_days: null });
      setAddingToken(false);
    } catch (e) { alert(e.message); }
  }

  async function revokeToken(id) {
    if (!confirm("Revoke this token? This cannot be undone.")) return;
    try {
      await ww.del("/tokens/" + id);
      setTokens(tokens.filter(tk => tk.id !== id));
    } catch (e) { alert(e.message); }
  }

  const [tokens, setTokens] = useStateN([]);
  const [addingToken, setAddingToken] = useStateN(false);
  const [tokenForm, setTokenForm] = useStateN({ name: "", scope: "readonly", expires_days: null });
  const [newTokenValue, setNewTokenValue] = useStateN("");
  const [netName, setNetName] = useStateN(net.name);
  const [saving, setSaving] = useStateN(false);

  async function saveName() {
    setSaving(true);
    try { await ww.put("/networks/" + networkId, { name: netName }); } catch (e) { alert(e.message); }
    setSaving(false);
  }

  async function deleteNetwork() {
    if (!confirm("Delete this network and all devices? This cannot be undone.")) return;
    try { await ww.del("/networks/" + networkId); window.location.href = "app.html"; } catch (e) { alert(e.message); }
  }

  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      <Card title={t.network.settingsGeneral}>
        <div style={{ display: "flex", gap: 10, alignItems: "flex-end" }}>
          <div style={{ flex: 1 }}><div className="kicker" style={{ marginBottom: 6 }}>{t.networks.name}</div>
            <input value={netName} onChange={e => setNetName(e.target.value)} disabled={!editable} style={{ width: "100%" }}/></div>
          {editable && netName !== net.name && <Btn small onClick={saveName} disabled={saving}>{t.common.save}</Btn>}
        </div>
      </Card>

      <Card title={t.network.addressTitle}>
        <div style={{ fontSize: 12.5, color: "var(--ink-3)", marginBottom: 12, lineHeight: 1.55 }}>
          {t.network.addressHelp}
        </div>
        <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
          <div>
            <div className="kicker" style={{ marginBottom: 6 }}>{t.networks.cidr}</div>
            <input value={cidr} readOnly style={{ width: "100%", fontFamily: "var(--mono)", opacity: 0.7 }}/>
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "repeat(3, 1fr)", gap: 0, fontFamily: "var(--mono)", fontSize: 12.5, border: "1px solid var(--line-2)", borderRadius: 6, overflow: "hidden" }}>
            {[
              [t.network.addressNetwork, `${a || "—"} / ${b || "24"}`],
              [t.network.addressUsable, `${totalHosts.toLocaleString()} ${t.network.addressHosts}`],
              [t.network.addressUsed, `${used} (${Math.round(used / Math.max(totalHosts, 1) * 100)}%)`],
            ].map(([l, v], i) => (
              <div key={i} style={{ padding: "10px 12px", borderRight: i < 2 ? "1px solid var(--line-2)" : 0, background: "var(--bg-2)" }}>
                <div className="kicker" style={{ fontSize: 10, marginBottom: 4 }}>{l}</div>
                <div style={{ color: "var(--ink)" }}>{v}</div>
              </div>
            ))}
          </div>
        </div>
      </Card>

      <Card title={t.network.membersTitle} action={canInvite && <Btn small onClick={() => setInviteOpen(true)}>{t.members.invite}</Btn>} pad={0}>
        <div className="ww-table" style={{ "--ww-cols": "2fr 1fr 1fr 1fr 40px" }}>
          <div className="ww-table__help">{t.network.membersHelp}</div>
          {inviteOpen && (
            <div style={{ padding: "14px 18px", borderBottom: "1px solid var(--line-2)", display: "grid", gridTemplateColumns: "2fr 1fr auto auto", gap: 10, alignItems: "end" }}>
              <div><div className="kicker" style={{ marginBottom: 6 }}>{t.members.inviteEmail}</div><input value={invEmail} onChange={e => setInvEmail(e.target.value)} placeholder="user@company.com" style={{ width: "100%" }}/></div>
              <div><div className="kicker" style={{ marginBottom: 6 }}>{t.members.inviteRole}</div>
                <select value={invRole} onChange={e => setInvRole(e.target.value)} style={{ width: "100%" }}>
                  {memberRoles.map(([k, l]) => <option key={k} value={k}>{l}</option>)}
                </select>
              </div>
              <Btn variant="ghost" small onClick={() => setInviteOpen(false)}>{t.common.cancel}</Btn>
              <Btn small onClick={sendInvite}>{t.members.sendInvite}</Btn>
            </div>
          )}
          <div className="ww-table__head">
            {[t.members.member, t.members.role, t.members.joined, t.members.lastSeen, ""].map((c, i) => <div key={i}>{c}</div>)}
          </div>
          {members.map((m, i) => (
            <div key={i} className="ww-table__row">
              <div style={{ display: "flex", gap: 10, alignItems: "center" }}>
                <Avatar name={m.email.split("@")[0]}/>
                <div>
                  <div style={{ fontSize: 13, fontWeight: 500 }}>{m.email.split("@")[0]}</div>
                  <div className="mono" style={{ fontSize: 11, color: "var(--ink-3)" }}>{m.email}</div>
                </div>
              </div>
              <div style={{ color: "var(--ink-2)" }}>{t.roles[m.role] || m.role}</div>
              <div className="mono" style={{ fontSize: 11, color: "var(--ink-3)" }}>{m.joined_at ? new Date(m.joined_at * 1000).toISOString().slice(0, 10) : "—"}</div>
              <div className="mono" style={{ fontSize: 11, color: "var(--ink-3)" }}>—</div>
              <div>{canInvite && m.role !== "owner" && <Btn variant="ghost" small>⋯</Btn>}</div>
            </div>
          ))}
        </div>
      </Card>

      <Card title={t.network.tokensTitle} action={editable && <Btn small onClick={() => setAddingToken(!addingToken)}>+ {t.tokens.add}</Btn>} pad={0}>
        <div className="ww-table" style={{ "--ww-cols": "1fr 1.4fr 1.3fr 1fr 1fr 72px" }}>
          <div className="ww-table__help">{t.tokens.networkHint}</div>
          {addingToken && (
            <div style={{ padding: "14px 18px", borderBottom: "1px solid var(--line-2)", display: "grid", gridTemplateColumns: "1.5fr 1fr 1fr auto auto", gap: 10, alignItems: "end" }}>
              <div><div className="kicker" style={{ marginBottom: 4, fontSize: 10 }}>Name</div><input value={tokenForm.name} onChange={e => setTokenForm({...tokenForm, name: e.target.value})} placeholder="My token" style={{ width: "100%" }}/></div>
              <div><div className="kicker" style={{ marginBottom: 4, fontSize: 10 }}>Scope</div><select value={tokenForm.scope} onChange={e => setTokenForm({...tokenForm, scope: e.target.value})} style={{ width: "100%" }}><option value="readonly">readonly</option><option value="readwrite">readwrite</option><option value="admin">admin</option></select></div>
              <div><div className="kicker" style={{ marginBottom: 4, fontSize: 10 }}>Expires (days)</div><input type="number" min="1" value={tokenForm.expires_days || ""} onChange={e => setTokenForm({...tokenForm, expires_days: e.target.value || null})} placeholder="never" style={{ width: "100%" }}/></div>
              <Btn variant="ghost" small onClick={() => setAddingToken(false)}>{t.common.cancel}</Btn>
              <Btn small onClick={createToken}>{t.common.save}</Btn>
            </div>
          )}
          {newTokenValue && (
            <div style={{ padding: "12px 18px", background: "color-mix(in oklab, var(--accent) 8%, transparent)", borderBottom: "1px solid var(--line-2)", display: "flex", alignItems: "center", gap: 10, fontFamily: "var(--mono)", fontSize: 12 }}>
              <span style={{ color: "var(--ink-3)" }}>New token:</span>
              <span style={{ flex: 1, wordBreak: "break-all" }}>{newTokenValue}</span>
              <Btn variant="ghost" small onClick={() => { navigator.clipboard.writeText(newTokenValue); }}>Copy</Btn>
              <Btn variant="ghost" small onClick={() => setNewTokenValue("")}>Dismiss</Btn>
            </div>
          )}
          <div className="ww-table__head">
            {[t.tokens.colName, t.tokens.colToken, t.tokens.colScopes, t.tokens.colCreated, t.tokens.colExpires, ""].map((c, i) => <div key={i}>{c}</div>)}
          </div>
          {tokens.map((tk) => (
            <div key={tk.id} className="ww-table__row ww-table__row--mono ww-table__row--sm">
              <span style={{ fontWeight: 500 }}>{tk.name}</span>
              <span style={{ color: "var(--ink-3)" }}>{tk.token_prefix || "ww_***"}</span>
              <span>{tk.scope}</span>
              <span style={{ color: "var(--ink-3)" }}>{tk.created_at ? new Date(tk.created_at * 1000).toISOString().slice(0, 10) : "—"}</span>
              <span style={{ color: "var(--ink-3)" }}>{tk.expires_at ? new Date(tk.expires_at * 1000).toISOString().slice(0, 10) : "never"}</span>
              <span>{can(role, "delete") && <Btn variant="ghost" small onClick={() => revokeToken(tk.id)}>{t.tokens.revoke}</Btn>}</span>
            </div>
          ))}
          {tokens.length === 0 && !addingToken && <div style={{ padding: 16, textAlign: "center", color: "var(--ink-3)", fontSize: 12 }}>No API tokens.</div>}
        </div>
      </Card>

      {can(role, "delete") && (
        <Card title={t.settings.dangerZone}>
          <p style={{ fontSize: 12, color: "var(--ink-3)", margin: "0 0 14px 0", lineHeight: 1.55 }}>{t.network.deleteBody}</p>
          <Btn variant="danger" small onClick={deleteNetwork}>{t.common.delete}</Btn>
        </Card>
      )}
    </div>
  );
}

Object.assign(window, { NetworkDetail });
