/* Machine modals + detail drawer */
function MachineModals({ modal, close, clearSel }) {
  const s = useStore();
  const toast = useToast();
  const { type, machine } = modal;
  const [name, setName] = useState(machine?.name || "");
  const [tagInput, setTagInput] = useState("");
  const [tags, setTags] = useState(machine?.tags ? machine.tags.slice() : []);
  const [targetUser, setTargetUser] = useState(machine?.user || (s.users[0] && s.users[0].id));
  const [expMode, setExpMode] = useState("now"); // now | never
  const [confirmTxt, setConfirmTxt] = useState("");

  if (type === "rename") {
    return (
      <Modal title="Rename machine" subtitle={`Sets the given name (currently “${machine.name}”)`} icon="edit" onClose={close}
        footer={<><div className="spacer" /><button className="btn" onClick={close}>Cancel</button>
          <button className="btn primary" onClick={() => { HS.act.renameMachine(machine.id, name.trim() || machine.name); toast.push({ kind: "success", title: "Machine renamed", msg: name }); close(); }}>Save</button></>}>
        <div className="field">
          <label>Given name</label>
          <input className="input mono" value={name} autoFocus onChange={(e) => setName(e.target.value.replace(/[^a-z0-9-]/gi, "-").toLowerCase())} />
          <span className="hint">Sets <span className="code-inline">givenName</span> (reported hostname stays <span className="code-inline">{machine.hostname || machine.name}</span>). MagicDNS name becomes <span className="code-inline">{(name || machine.name)}.{s.dns.base_domain}</span></span>
        </div>
      </Modal>
    );
  }

  if (type === "tag" || type === "bulkTag") {
    const ids = type === "bulkTag" ? modal.ids : [machine.id];
    const addTag = (t) => { t = t.trim().replace(/^tag:/, ""); if (!t) return; const full = "tag:" + t; if (!tags.includes(full)) setTags([...tags, full]); setTagInput(""); };
    const willDetach = type === "tag" && machine.tags.length === 0 && tags.length > 0;
    return (
      <Modal title={type === "bulkTag" ? `Set forced tags on ${ids.length} nodes` : "Set forced tags"} subtitle="Forced tags are admin-set and replace the set" icon="tag" onClose={close}
        footer={<><div className="spacer" /><button className="btn" onClick={close}>Cancel</button>
          <button className="btn primary" onClick={() => { ids.forEach((id) => HS.act.setMachineTags(id, tags)); toast.push({ kind: "success", title: "Forced tags applied", msg: ids.length + " node(s)" }); clearSel && clearSel(); close(); }}>Apply tags</button></>}>
        <div className="field">
          <label>Forced tags</label>
          <div style={{ display: "flex", flexWrap: "wrap", gap: 6, padding: 8, border: "1px solid var(--border-strong)", borderRadius: 8, minHeight: 44, background: "var(--surface)" }}>
            {tags.map((t) => <span key={t} className="tag">{t}<span className="x" onClick={() => setTags(tags.filter((x) => x !== t))}><Ic name="x" size={11} /></span></span>)}
            <input className="mono" value={tagInput} placeholder="add tag…" onChange={(e) => setTagInput(e.target.value)}
              onKeyDown={(e) => { if (e.key === "Enter") addTag(tagInput); if (e.key === "Backspace" && !tagInput) setTags(tags.slice(0, -1)); }}
              style={{ border: "none", background: "none", outline: "none", flex: 1, minWidth: 80, fontSize: 12.5 }} />
          </div>
          <span className="hint">Writes via <span className="code-inline">POST /node/&#123;id&#125;/tags</span> (replaces the set). Suggested tags from <span className="code-inline">tagOwners</span>:</span>
          <div style={{ display: "flex", gap: 5, flexWrap: "wrap" }}>
            {s.tagOwners.filter((t) => !tags.includes(t)).map((t) => <span key={t} className="tag muted" style={{ cursor: "pointer" }} onClick={() => setTags([...tags, t])}>+ {t}</span>)}
          </div>
        </div>
        {willDetach && (
          <div style={{ display: "flex", gap: 9, padding: "10px 12px", background: "var(--warn-soft)", color: "var(--warn)", borderRadius: 8, fontSize: 12.5, lineHeight: 1.5 }}>
            <Ic name="warn" size={16} style={{ flexShrink: 0, marginTop: 1 }} />
            <div>A node is owned by tags <b>or</b> a user, never both. Tagging detaches it from <b>{s.userById(machine.user)?.name}</b> — ownership moves to <span className="code-inline">tag:tagged-devices</span>.</div>
          </div>
        )}
        {tags.length === 0 && type === "tag" && machine.tags.length > 0 && (
          <div style={{ display: "flex", gap: 9, padding: "10px 12px", background: "var(--surface-2)", color: "var(--text-dim)", borderRadius: 8, fontSize: 12.5 }}>
            <Ic name="info" size={16} style={{ flexShrink: 0 }} />Clearing all tags leaves the node owned by its last user.
          </div>
        )}
      </Modal>
    );
  }

  if (type === "move" || type === "bulkMove") {
    const ids = type === "bulkMove" ? modal.ids : [machine.id];
    const wasTagged = type === "move" && machine.tags.length > 0;
    return (
      <Modal title={type === "bulkMove" ? `Assign ${ids.length} nodes to a user` : "Assign to user"} subtitle="Sets user ownership (clears forced tags)" icon="move" onClose={close}
        footer={<><div className="spacer" /><button className="btn" onClick={close}>Cancel</button>
          <button className="btn primary" onClick={() => { ids.forEach((id) => HS.act.setMachineUser(id, targetUser)); toast.push({ kind: "success", title: "Ownership transferred", msg: "Now owned by " + s.userById(targetUser).name }); clearSel && clearSel(); close(); }}>Assign</button></>}>
        <div className="field">
          <label>Destination user</label>
          <div style={{ display: "flex", flexDirection: "column", gap: 6 }}>
            {s.users.map((u) => (
              <label key={u.id} style={{ display: "flex", alignItems: "center", gap: 10, padding: "9px 11px", border: "1px solid " + (String(targetUser) === u.id ? "var(--accent)" : "var(--border)"), borderRadius: 8, cursor: "pointer", background: String(targetUser) === u.id ? "var(--accent-soft)" : "transparent" }}>
                <input type="radio" name="u" checked={String(targetUser) === u.id} onChange={() => setTargetUser(u.id)} style={{ accentColor: "var(--accent)" }} />
                <Avatar user={u} size={26} />
                <div><div style={{ fontWeight: 550, fontSize: 13 }}>{u.displayName}</div><div style={{ fontSize: 12, color: "var(--text-faint)" }}>{u.email || u.name}</div></div>
                <div style={{ flex: 1 }} /><span className="badge">{s.machinesByUser(u.id).length}</span>
              </label>
            ))}
          </div>
        </div>
        {wasTagged && (
          <div style={{ display: "flex", gap: 9, padding: "10px 12px", background: "var(--warn-soft)", color: "var(--warn)", borderRadius: 8, fontSize: 12.5, lineHeight: 1.5 }}>
            <Ic name="warn" size={16} style={{ flexShrink: 0, marginTop: 1 }} />
            <div>This node is currently tag-owned ({machine.tags.join(", ")}). Assigning it to a user clears those tags and the node must re-authenticate.</div>
          </div>
        )}
      </Modal>
    );
  }

  if (type === "expire" || type === "bulkExpire") {
    const ids = type === "bulkExpire" ? modal.ids : [machine.id];
    return (
      <Modal title={type === "bulkExpire" ? `Expire ${ids.length} node keys?` : `Expire ${machine.name}?`} icon="clock" iconDanger
        subtitle="POST /node/{id}/expire" onClose={close}
        footer={<><div className="spacer" /><button className="btn" onClick={close}>Cancel</button>
          <button className="btn danger" onClick={() => {
            if (expMode === "never") { ids.forEach((id) => HS.act.setNeverExpire(id, true)); toast.push({ kind: "success", title: "Expiry disabled", msg: ids.length + " node(s) will never expire" }); }
            else { ids.forEach((id) => HS.act.expireMachine(id)); toast.push({ kind: "success", title: "Key expired", msg: ids.length + " node(s) forced offline" }); }
            clearSel && clearSel(); close();
          }}>{expMode === "never" ? "Disable expiry" : "Expire now"}</button></>}>
        <div className="field">
          <label>Action</label>
          <div className="seg" style={{ alignSelf: "flex-start" }}>
            <button className={expMode === "now" ? "on" : ""} onClick={() => setExpMode("now")}>Expire now</button>
            <button className={expMode === "never" ? "on" : ""} onClick={() => setExpMode("never")}>Never expire</button>
          </div>
        </div>
        <div style={{ fontSize: 13.5, color: "var(--text-dim)", lineHeight: 1.55 }}>
          {expMode === "now"
            ? <>Expiring forces the device offline immediately; the user must run <span className="code-inline">tailscale up</span> to re-authenticate. The node record is kept.</>
            : <>Sets <span className="code-inline">disableExpiry=true</span> — the key never expires and the node won't be forced to re-auth. Useful for servers and infra.</>}
        </div>
      </Modal>
    );
  }

  if (type === "delete" || type === "bulkDelete") {
    const ids = type === "bulkDelete" ? modal.ids : [machine.id];
    const bulk = type === "bulkDelete";
    const word = bulk ? String(ids.length) : machine.name;
    const ok = confirmTxt.trim() === word;
    return (
      <Modal title={bulk ? `Delete ${ids.length} nodes?` : `Delete ${machine.name}?`} icon="trash" iconDanger
        subtitle="DELETE /node/{id} — permanent" onClose={close}
        footer={<><div className="spacer" /><button className="btn" onClick={close}>Cancel</button>
          <button className="btn danger" disabled={!ok} onClick={() => { ids.forEach((id) => HS.act.deleteMachine(id)); toast.push({ kind: "success", title: bulk ? `${ids.length} nodes deleted` : "Node deleted", msg: bulk ? "" : machine.name }); clearSel && clearSel(); close(); }}>Delete {bulk ? `${ids.length} nodes` : "node"}</button></>}>
        <div style={{ fontSize: 13.5, color: "var(--text-dim)", lineHeight: 1.55 }}>
          {bulk ? <>Permanently removes <b>{ids.length}</b> node records, their IPs, and advertised routes. This cannot be undone.</>
            : <>The node record, its <span className="code-inline">{machine.ip4}</span> address, and any advertised routes will be removed. This cannot be undone.</>}
        </div>
        <div className="field">
          <label>Type <span className="code-inline">{word}</span> to confirm</label>
          <input className="input mono" autoFocus value={confirmTxt} onChange={(e) => setConfirmTxt(e.target.value)} placeholder={word} />
        </div>
      </Modal>
    );
  }

  if (type === "register") {
    return <RegisterModal close={close} />;
  }

  if (type === "approvePending") {
    return <ApprovePendingModal p={modal.p} close={close} />;
  }
  return null;
}

function ApprovePendingModal({ p, close }) {
  const s = useStore();
  const toast = useToast();
  const guess = s.users.find((u) => u.name === p.requestedUser) || s.users[0];
  const [user, setUser] = useState(guess.id);
  return (
    <Modal title="Approve registration" subtitle={`POST /api/v1/auth/approve · ${p.authId}`} icon="check" onClose={close}
      footer={<><div className="spacer" /><button className="btn" onClick={close}>Cancel</button>
        <button className="btn primary" onClick={() => { HS.act.approvePending(p.authId, user); toast.push({ kind: "success", title: "Node approved", msg: p.node + " joined" }); close(); }}>Approve &amp; admit</button></>}>
      <div style={{ display: "flex", flexDirection: "column", gap: 6, padding: "11px 13px", background: "var(--surface-2)", borderRadius: 8, fontSize: 13 }}>
        <div style={{ display: "flex", justifyContent: "space-between" }}><span style={{ color: "var(--text-dim)" }}>Node</span><b>{p.node}</b></div>
        <div style={{ display: "flex", justifyContent: "space-between" }}><span style={{ color: "var(--text-dim)" }}>Method</span><span className="badge">{p.method === "oidc" ? "OIDC" : "CLI"}</span></div>
        <div style={{ display: "flex", justifyContent: "space-between" }}><span style={{ color: "var(--text-dim)" }}>Key</span><span className="mono" style={{ fontSize: 12 }}>{p.key}</span></div>
      </div>
      <div className="field">
        <label>Admit as user</label>
        <Menu align="left" trigger={<button className="btn" style={{ justifyContent: "flex-start", width: "100%" }}><Avatar user={s.userById(user)} size={20} />{s.userById(user).name}<div style={{ flex: 1 }} /><Ic name="chevDown" size={14} /></button>}>
          {s.users.map((u) => <MenuItem key={u.id} icon={u.name === p.requestedUser ? "check" : undefined} onClick={() => setUser(u.id)}>{u.name}{u.name === p.requestedUser ? "  (requested)" : ""}</MenuItem>)}
        </Menu>
        <span className="hint">The device requested <span className="code-inline">{p.requestedUser}</span>; you choose the final owner.</span>
      </div>
    </Modal>
  );
}

function RegisterModal({ close }) {
  const s = useStore();
  const toast = useToast();
  const [tab, setTab] = useState("key");
  const [user, setUser] = useState(s.users[0] && s.users[0].id);
  const cu = s.userById(user);
  const cmd = `tailscale up --login-server ${s.serverInfo.url} --authkey <KEY>`;
  const regCmd = `headscale nodes register --user ${cu ? cu.name : "<user>"} --key mkey:abc123…`;
  return (
    <Modal title="Register a machine" subtitle="Add a new node to the tailnet" icon="machines" size="lg" onClose={close}
      footer={<><div className="spacer" /><button className="btn" onClick={close}>Close</button>
        <button className="btn primary" onClick={() => { toast.push({ kind: "success", title: "Waiting for node…", msg: "Run the command on your device" }); close(); }}>Done</button></>}>
      <div className="seg" style={{ alignSelf: "flex-start" }}>
        <button className={tab === "key" ? "on" : ""} onClick={() => setTab("key")}>Pre-auth key</button>
        <button className={tab === "manual" ? "on" : ""} onClick={() => setTab("manual")}>Manual register</button>
        <button className={tab === "oidc" ? "on" : ""} onClick={() => setTab("oidc")}>OIDC login</button>
      </div>
      <div className="field">
        <label>Assign to user</label>
        <Menu align="left" trigger={<button className="btn" style={{ justifyContent: "flex-start", width: 240 }}>{cu ? <><Avatar user={cu} size={20} />{cu.name}</> : <span style={{ color: "var(--text-faint)" }}>no users</span>}<div style={{ flex: 1 }} /><Ic name="chevDown" size={14} /></button>}>
          {s.users.map((u) => <MenuItem key={u.id} onClick={() => setUser(u.id)}>{u.name}</MenuItem>)}
        </Menu>
      </div>
      {tab === "key" && (
        <div className="field">
          <label>On the new device, run</label>
          <CmdBlock text={cmd} />
          <span className="hint">Generate a key under <b>Pre-auth keys</b> and paste it in place of <span className="code-inline">&lt;KEY&gt;</span>.</span>
        </div>
      )}
      {tab === "manual" && (
        <div className="field">
          <label>Register a node by its machine key</label>
          <CmdBlock text={regCmd} />
          <span className="hint">Run <span className="code-inline">tailscale up --login-server {s.serverInfo.url}</span> on the device first to obtain its machine key.</span>
        </div>
      )}
      {tab === "oidc" && (
        <div style={{ display: "flex", flexDirection: "column", gap: 12, alignItems: "flex-start" }}>
          <div className="field" style={{ width: "100%" }}>
            <label>Device login flow</label>
            <CmdBlock text={`tailscale up --login-server ${s.serverInfo.url}`} />
          </div>
          <div style={{ fontSize: 13, color: "var(--text-dim)", lineHeight: 1.5 }}>
            The device opens a browser to authenticate against your identity provider. After login the node appears here pending approval.
          </div>
        </div>
      )}
    </Modal>
  );
}

function CmdBlock({ text }) {
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 10, padding: "11px 13px", background: "var(--surface-2)", border: "1px solid var(--border)", borderRadius: 8 }}>
      <span style={{ color: "var(--accent)", fontFamily: "var(--font-mono)" }}>$</span>
      <code className="mono" style={{ fontSize: 12.5, flex: 1, overflowX: "auto", whiteSpace: "nowrap" }}>{text}</code>
      <CopyBtn text={text} label="Command copied" />
    </div>
  );
}

/* ---- detail drawer ---- */
function MachineDrawer({ id, close, openModal }) {
  const s = useStore();
  const m = s.machines.find((x) => x.id === id);
  useEffect(() => {
    const h = (e) => e.key === "Escape" && close();
    document.addEventListener("keydown", h); return () => document.removeEventListener("keydown", h);
  }, []);
  if (!m) return null;
  const u = s.userById(m.user);
  const exp = expiryInfo(m.expiry);
  const tagged = m.tags.length > 0;
  const subnet = s.subnetPrefixes(m);
  const isExit = s.isExitNode(m);
  return (
    <div className="overlay" style={{ justifyItems: "end", padding: 0 }} onMouseDown={(e) => e.target === e.currentTarget && close()}>
      <div style={{ width: 440, maxWidth: "94vw", height: "100%", background: "var(--surface)", borderLeft: "1px solid var(--border-strong)", boxShadow: "var(--shadow-lg)", display: "flex", flexDirection: "column", animation: "drawerIn .26s var(--ease-out)" }}>
        <style>{`@keyframes drawerIn{from{transform:translateX(40px);opacity:.4}to{transform:none;opacity:1}}`}</style>
        <div style={{ padding: "18px 20px", borderBottom: "1px solid var(--border)", display: "flex", alignItems: "flex-start", gap: 12 }}>
          <span style={{ width: 42, height: 42, borderRadius: 11, background: "var(--surface-2)", display: "grid", placeItems: "center", color: "var(--text-dim)" }}><Ic name={osIcon(m.kind, m.os)} size={22} /></span>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
              <h3 style={{ fontSize: 16, fontWeight: 600 }}>{m.name}</h3>
              <span className={"dot-status " + (m.online ? "online" : "offline")} />
            </div>
            <div style={{ fontSize: 12.5, color: "var(--text-faint)" }}>{m.online ? "Connected" : "Last seen " + relTime(m.lastSeen)} · {osLabel(m.os)}</div>
          </div>
          <span className="x" style={{ cursor: "pointer", color: "var(--text-faint)", padding: 4 }} onClick={close}><Ic name="x" size={18} /></span>
        </div>

        <div style={{ flex: 1, overflowY: "auto", padding: 20, display: "flex", flexDirection: "column", gap: 18 }}>
          {exp.soon && <div className="card-pad" style={{ background: exp.cls === "danger" ? "var(--danger-soft)" : "var(--warn-soft)", borderRadius: 9, display: "flex", gap: 10, alignItems: "center", color: exp.cls === "danger" ? "var(--danger)" : "var(--warn)" }}>
            <Ic name="warn" size={18} /><div style={{ fontSize: 12.5 }}><b>{exp.label}.</b> Re-authenticate to keep it online.</div>
          </div>}

          <DrawerSection title="Addresses">
            <DRow label="IPv4"><span className="ip">{m.ip4}</span><CopyBtn text={m.ip4} /></DRow>
            <DRow label="IPv6"><span className="ip" style={{ fontSize: 11.5 }}>{m.ip6}</span><CopyBtn text={m.ip6} /></DRow>
            <DRow label="MagicDNS"><span className="mono" style={{ fontSize: 12 }}>{m.name}.{s.dns.base_domain}</span></DRow>
            <DRow label="Hostname"><span className="mono" style={{ fontSize: 12, color: "var(--text-faint)" }}>{m.hostname || m.name}</span></DRow>
          </DrawerSection>

          <DrawerSection title="Ownership">
            {tagged
              ? <DRow label="Owned by"><div style={{ display: "flex", gap: 4, flexWrap: "wrap", justifyContent: "flex-end" }}>{m.tags.map((t) => <span key={t} className="tag">{t}</span>)}</div></DRow>
              : <DRow label="User">{u && <span style={{ display: "inline-flex", alignItems: "center", gap: 7 }}><Avatar user={u} size={20} />{u.name}</span>}</DRow>}
            <DRow label="Ownership"><span className="badge">{tagged ? "tag-owned" : "user-owned"}</span></DRow>
            <DRow label="Registered"><span className="badge">{regMethodLabel(m.registerMethod)}</span></DRow>
            <DRow label="Type">{m.ephemeral ? <span className="badge">ephemeral</span> : <span className="badge">persistent</span>}</DRow>
          </DrawerSection>

          {m.availableRoutes.length > 0 && (
            <DrawerSection title="Routes">
              <div style={{ fontSize: 11.5, color: "var(--text-faint)", marginBottom: 6 }}>Advertised by the client; approve to enable. Edits the approved set.</div>
              {subnet.map((cidr) => {
                const approved = m.approvedRoutes.includes(cidr);
                return (
                  <div key={cidr} style={{ display: "flex", alignItems: "center", gap: 9, padding: "7px 0", borderBottom: "1px solid var(--border)" }}>
                    <Ic name="routes" size={15} style={{ color: "var(--text-dim)" }} />
                    <span className="ip" style={{ flex: 1 }}>{cidr}{s.isServed(m, cidr) && <span className="badge accent" style={{ marginLeft: 6, height: 17 }}>primary</span>}</span>
                    <span className={"toggle" + (approved ? " on" : "")} title="Approve / remove this prefix" onClick={() => approved ? HS.act.rejectRoute(m.id, cidr) : HS.act.approveRoute(m.id, cidr)} />
                  </div>
                );
              })}
              {isExit && (
                <div style={{ display: "flex", alignItems: "center", gap: 9, padding: "7px 0" }}>
                  <Ic name="exit" size={15} style={{ color: "var(--info)" }} />
                  <span className="ip" style={{ flex: 1 }}>Exit node <span className="badge" style={{ height: 17 }}>0.0.0.0/0 · ::/0</span></span>
                  <span className={"toggle" + (s.exitApproved(m) ? " on" : "")} title="Approve both egress prefixes" onClick={() => HS.act.setExitNode(m.id, !s.exitApproved(m))} />
                </div>
              )}
            </DrawerSection>
          )}

          <DrawerSection title="Client">
            <DRow label="Version"><span className="mono" style={{ fontSize: 12 }}>tailscale {m.client}</span></DRow>
            <DRow label="Key expiry"><span style={{ fontSize: 12.5 }}>{m.neverExpire ? "Never (disabled)" : m.expiry < Date.now() ? "Expired" : absDate(m.expiry)}</span></DRow>
            <DRow label="Node ID"><span className="mono" style={{ fontSize: 12, color: "var(--text-faint)" }}>{m.id}</span></DRow>
          </DrawerSection>
        </div>

        <div style={{ padding: 16, borderTop: "1px solid var(--border)", display: "flex", gap: 8, flexWrap: "wrap" }}>
          <button className="btn sm" onClick={() => openModal({ type: "rename", machine: m })}><Ic name="edit" size={14} />Rename</button>
          <button className="btn sm" onClick={() => openModal({ type: "tag", machine: m })}><Ic name="tag" size={14} />Tags</button>
          <button className="btn sm" onClick={() => openModal({ type: "move", machine: m })}><Ic name="move" size={14} />Assign</button>
          <div style={{ flex: 1 }} />
          <button className="btn sm danger" onClick={() => { openModal({ type: "expire", machine: m }); close(); }}><Ic name="clock" size={14} />Expire</button>
        </div>
      </div>
    </div>
  );
}
function DrawerSection({ title, children }) {
  return (
    <div>
      <div style={{ fontSize: 11, textTransform: "uppercase", letterSpacing: ".06em", color: "var(--text-faint)", fontWeight: 600, marginBottom: 8 }}>{title}</div>
      <div>{children}</div>
    </div>
  );
}
function DRow({ label, children }) {
  return (
    <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 10, padding: "6px 0", fontSize: 13 }}>
      <span style={{ color: "var(--text-dim)" }}>{label}</span>
      <span style={{ display: "inline-flex", alignItems: "center", gap: 6, textAlign: "right" }}>{children}</span>
    </div>
  );
}

Object.assign(window, { MachineModals, MachineDrawer, RegisterModal, ApprovePendingModal, CmdBlock, DRow, DrawerSection });
