import { useEffect, useState } from "react";
import { API_BASE } from "./config";

const DEFAULTS = {
  model: "",
  auto_chat_model: "", // Auto-mode: model for chat intent ("" = built-in preference)
  auto_code_model: "", // Auto-mode: model for code intent
  think: false, // Qwen3-style reasoning; off = much faster chat/memory
  temperature: 0.7,
  num_ctx: 0, // context window in tokens; 0 = model default
  rag_top_k: 3, // document chunks injected into chat
  rag_min_score: 0.6, // min cosine similarity for a chunk to count
  action_tool_policy: "off", // off | ask (per-call approval) | allow
  system_prompt: "",
  timeout: 120,
  gpu_offload: -1, // -1 = Auto; 0–100 = percent of layers forced onto the GPU
  memory_model: "mistral:latest", // curator model for memory extraction
  memory_gpu_offload: 0, // curator offload; 0 = all CPU/RAM (safe on small GPUs)
  memory_merge_threshold: 0.80, // 0 = off; else update near-duplicate facts in place
};

export function Settings() {
  const [form, setForm] = useState(DEFAULTS);
  const [saved, setSaved] = useState(false);
  const [error, setError] = useState("");

  // Icon branding state
  const [iconApps, setIconApps] = useState([]);
  const [iconFilter, setIconFilter] = useState("");
  const [iconFrac, setIconFrac] = useState(0.60);
  const [iconRing, setIconRing] = useState(false);
  const [selectedApps, setSelectedApps] = useState([]);
  const [brandQueue, setBrandQueue] = useState([]);
  const [isBranding, setIsBranding] = useState(false);
  const [modelList, setModelList] = useState([]);
  const [platform, setPlatform] = useState("");

  // Vite dev server toggle
  const [viteRunning, setViteRunning] = useState(null);
  const [viteBusy, setViteBusy] = useState(false);
  const [viteError, setViteError] = useState("");

  const loadViteStatus = () => {
    fetch(`${API_BASE}/frontend/status`)
      .then(r => r.ok ? r.json() : null)
      .then(d => { if (d) setViteRunning(!!d.running); })
      .catch(() => {});
  };

  const toggleVite = async () => {
    setViteBusy(true);
    setViteError("");
    try {
      const action = viteRunning ? "stop" : "start";
      const r = await fetch(`${API_BASE}/frontend/${action}`, { method: "POST" });
      const d = await r.json().catch(() => null);
      if (d && d.status === "error") setViteError(d.detail || "Failed to start Vite");
    } catch (e) {
      setViteError(e.message || "Failed");
    }
    setViteBusy(false);
    loadViteStatus();
  };

  useEffect(() => {
    fetch(`${API_BASE}/models`).then(r => r.ok ? r.json() : null)
      .then(d => setModelList((d && d.models) || [])).catch(() => {});
    fetch(`${API_BASE}/settings`)
      .then(r => r.ok ? r.json() : null)
      .then(d => { if (d) setForm(f => ({ ...f, ...d })); })
      .catch(() => {});

    fetch(`${API_BASE}/icons/apps`)
      .then(r => r.ok ? r.json() : null)
      .then(d => { if (d) setIconApps(d.apps || []); })
      .catch(() => {});

    fetch(`${API_BASE}/status`)
      .then(r => r.ok ? r.json() : null)
      .then(d => { if (d) setPlatform(d.platform || ""); })
      .catch(() => {});

    loadViteStatus();
  }, []);

  const update = (key, value) => setForm(f => ({ ...f, [key]: value }));

  const save = async () => {
    setError("");
    setSaved(false);
    try {
      const r = await fetch(`${API_BASE}/settings`, {
        method: "PUT",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(form),
      });
      if (!r.ok) throw new Error(await r.text());
      setSaved(true);
      setTimeout(() => setSaved(false), 2500);
    } catch (e) {
      setError(e.message || "Failed to save");
    }
  };

  const reset = () => setForm(DEFAULTS);

  // Icon branding logic
  const toggleAppSelect = (app) => {
    setSelectedApps(prev => {
      const exists = prev.find(a => a.icon_name === app.icon_name);
      if (exists) return prev.filter(a => a.icon_name !== app.icon_name);
      return [...prev, app];
    });
  };

  const removeFromQueue = (icon_name) => {
    setSelectedApps(prev => prev.filter(a => a.icon_name !== icon_name));
  };

  const brandAll = async () => {
    if (!selectedApps.length || isBranding) return;
    setIsBranding(true);
    const queue = selectedApps.map(a => ({ ...a, status: "pending" }));
    setBrandQueue(queue);

    for (let i = 0; i < queue.length; i++) {
      const app = queue[i];
      setBrandQueue(prev => prev.map((a, idx) => idx === i ? { ...a, status: "running" } : a));
      try {
        const r = await fetch(`${API_BASE}/icons/brand`, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({
            src_path: app.icon_path,
            output_name: app.icon_name,
            frac: iconFrac,
            nexus_ring: iconRing,
            reload: false,
          }),
        });
        if (!r.ok) throw new Error(await r.text());
        setBrandQueue(prev => prev.map((a, idx) => idx === i ? { ...a, status: "done" } : a));
      } catch (e) {
        setBrandQueue(prev => prev.map((a, idx) => idx === i ? { ...a, status: "error", err: e.message } : a));
      }
    }

    // Apply cache once after all are done
    await fetch(`${API_BASE}/icons/apply`, { method: "POST" }).catch(() => {});

    // Mark already_branded in the app list
    setIconApps(prev => prev.map(a => {
      if (selectedApps.find(s => s.icon_name === a.icon_name)) {
        return { ...a, already_branded: true };
      }
      return a;
    }));

    setIsBranding(false);
  };

  const filteredApps = iconApps.filter(a =>
    a.name.toLowerCase().includes(iconFilter.toLowerCase())
  );

  const statusIcon = (status) => {
    if (status === "pending") return <span style={{ color: "#666" }}>○</span>;
    if (status === "running") return <span style={{ color: "#8cc63f" }}>⟳</span>;
    if (status === "done") return <span style={{ color: "#8cc63f" }}>✓</span>;
    if (status === "error") return <span style={{ color: "#f44336" }}>✗</span>;
    return null;
  };

  const inputStyle = {
    width: "100%",
    padding: "0.6rem 0.75rem",
    background: "#222",
    color: "#eee",
    border: "1px solid #333",
    borderRadius: "8px",
    fontSize: "0.9rem",
    boxSizing: "border-box",
  };

  const labelStyle = {
    display: "block",
    fontSize: "0.8rem",
    color: "#888",
    marginBottom: "0.4rem",
    textTransform: "uppercase",
    letterSpacing: "0.05em",
  };

  const sectionStyle = {
    background: "#161616",
    border: "1px solid #333",
    borderRadius: "12px",
    padding: "1.25rem",
    marginBottom: "1rem",
  };

  const displayQueue = isBranding ? brandQueue : selectedApps;

  return (
    <div style={{ width: "100%" }}>
      <h2 style={{ margin: "0 0 1.25rem", fontSize: "1.1rem", color: "#eee" }}>Settings</h2>

      {/* Auto model routing */}
      <div style={sectionStyle}>
        <h3 style={{ margin: "0 0 0.5rem", fontSize: "0.95rem", color: "#bbb" }}>Auto model routing</h3>
        <div style={{ fontSize: "0.72rem", color: "#555", marginBottom: "0.75rem" }}>
          When no model is pinned (chat picker on "auto"), which model fires for each
          detected intent. "Auto" = the built-in preference for your hardware.
          {form.model && <span style={{ color: "#c9a227" }}> A model is currently pinned in chat, so routing is bypassed until you set it back to auto.</span>}
        </div>
        <div style={{ display: "flex", gap: "1rem" }}>
          {[["auto_chat_model", "Chat / general"], ["auto_code_model", "Coding questions"]].map(([key, label]) => (
            <div key={key} style={{ flex: 1 }}>
              <label style={labelStyle}>{label}</label>
              <select value={form[key]} onChange={e => update(key, e.target.value)}
                style={{ width: "100%", padding: "0.6rem", background: "#222", color: "#eee", border: "1px solid #333", borderRadius: "8px", marginTop: "0.3rem" }}>
                <option value="">Auto (best installed)</option>
                {modelList.map(m => <option key={m} value={m}>{m}</option>)}
                {form[key] && !modelList.includes(form[key]) && <option value={form[key]}>{form[key]} (not installed)</option>}
              </select>
            </div>
          ))}
        </div>
      </div>

      {/* Generation */}
      <div style={sectionStyle}>
        <h3 style={{ margin: "0 0 1rem", fontSize: "0.95rem", color: "#bbb" }}>Generation</h3>

        <div style={{ marginBottom: "0.25rem" }}>
          <label style={labelStyle}>
            Temperature
            <span style={{ float: "right", color: "#7aa", fontWeight: 600, textTransform: "none", letterSpacing: 0 }}>
              {Number(form.temperature).toFixed(2)}
            </span>
          </label>
          <input
            type="range"
            min="0"
            max="2"
            step="0.05"
            value={form.temperature}
            onChange={e => update("temperature", parseFloat(e.target.value))}
            style={{ width: "100%", accentColor: "#007acc" }}
          />
          <div style={{ display: "flex", justifyContent: "space-between", fontSize: "0.72rem", color: "#555", marginTop: "0.2rem" }}>
            <span>0.0 — precise</span>
            <span>1.0 — balanced</span>
            <span>2.0 — creative</span>
          </div>
        </div>

        <div style={{ marginTop: "1.25rem" }}>
          <label style={labelStyle}>
            Context Window (num_ctx)
            <span style={{ float: "right", color: "#7aa", fontWeight: 600, textTransform: "none", letterSpacing: 0 }}>
              {form.num_ctx > 0 ? `${form.num_ctx} tok` : "Model default"}
            </span>
          </label>
          <input
            type="number"
            min="0"
            step="512"
            value={form.num_ctx}
            onChange={e => update("num_ctx", parseInt(e.target.value) || 0)}
            style={{ width: "100%", padding: "0.6rem", background: "#222", color: "#eee", border: "1px solid #333", borderRadius: "8px", boxSizing: "border-box" }}
          />
          <div style={{ fontSize: "0.72rem", color: "#555", marginTop: "0.2rem" }}>
            0 = use the model's default. Larger fits more context but uses more memory.
          </div>
        </div>

        <div style={{ marginTop: "1.25rem", display: "flex", gap: "1rem" }}>
          <div style={{ flex: 1 }}>
            <label style={labelStyle}>Document chunks (top-k)</label>
            <input
              type="number" min="0" step="1"
              value={form.rag_top_k}
              onChange={e => update("rag_top_k", Math.max(0, parseInt(e.target.value) || 0))}
              style={{ width: "100%", padding: "0.6rem", background: "#222", color: "#eee", border: "1px solid #333", borderRadius: "8px", boxSizing: "border-box" }}
            />
          </div>
          <div style={{ flex: 1 }}>
            <label style={labelStyle}>
              Min relevance
              <span style={{ float: "right", color: "#7aa", fontWeight: 600, textTransform: "none", letterSpacing: 0 }}>
                {Number(form.rag_min_score).toFixed(2)}
              </span>
            </label>
            <input
              type="range" min="0" max="1" step="0.05"
              value={form.rag_min_score}
              onChange={e => update("rag_min_score", parseFloat(e.target.value))}
              style={{ width: "100%", accentColor: "#007acc" }}
            />
          </div>
        </div>
        <div style={{ fontSize: "0.72rem", color: "#555", marginTop: "0.2rem" }}>
          How many uploaded-document chunks to pull into chat, and the minimum cosine
          similarity each must clear. Higher relevance = fewer, tighter matches.
        </div>

        <div style={{ marginTop: "1.25rem" }}>
          <label style={labelStyle}>Action tools (web search, fetch URL, write memory)</label>
          <select
            value={form.action_tool_policy}
            onChange={e => update("action_tool_policy", e.target.value)}
            style={{ width: "100%", padding: "0.6rem", background: "#222", color: "#eee", border: "1px solid #333", borderRadius: "8px", marginTop: "0.3rem" }}
          >
            <option value="off">Off — never run action tools</option>
            <option value="ask">Ask — approve each action before it runs</option>
            <option value="allow">Allow — run action tools freely</option>
          </select>
          <div style={{ fontSize: "0.72rem", color: form.action_tool_policy === "allow" ? "#c9a227" : "#555", marginTop: "0.2rem" }}>
            Controls tools that act (vs. read-only). A playbook must also list the tool.
            {form.action_tool_policy === "ask" && " You'll get an Approve/Deny prompt in chat."}
            {form.action_tool_policy === "allow" && " Actions run without confirmation."}
          </div>
        </div>

        <div style={{ marginTop: "1.25rem" }}>
          <label style={labelStyle}>
            GPU Offload
            <span style={{ float: "right", color: "#b040c0", fontWeight: 600, textTransform: "none", letterSpacing: 0 }}>
              {form.gpu_offload < 0 ? "Auto" : `${form.gpu_offload}%`}
            </span>
          </label>
          <input
            type="range"
            min="0"
            max="100"
            step="5"
            value={form.gpu_offload < 0 ? 100 : form.gpu_offload}
            disabled={form.gpu_offload < 0}
            onChange={e => update("gpu_offload", parseInt(e.target.value))}
            style={{ width: "100%", accentColor: "#b040c0", opacity: form.gpu_offload < 0 ? 0.4 : 1 }}
          />
          <div style={{ display: "flex", justifyContent: "space-between", fontSize: "0.72rem", color: "#555", marginTop: "0.2rem" }}>
            <span>0% — all CPU</span>
            <span>100% — all GPU</span>
          </div>
          <label style={{ display: "flex", alignItems: "center", gap: "0.5rem", cursor: "pointer", userSelect: "none", marginTop: "0.6rem" }}>
            <input
              type="checkbox"
              checked={form.gpu_offload < 0}
              onChange={e => update("gpu_offload", e.target.checked ? -1 : 50)}
              style={{ accentColor: "#8cc63f", width: "16px", height: "16px" }}
            />
            <span style={{ fontSize: "0.82rem", color: form.gpu_offload < 0 ? "#8cc63f" : "#888" }}>
              Auto — fit as many layers as VRAM allows (recommended)
            </span>
          </label>
          <p style={{ margin: "0.4rem 0 0", fontSize: "0.72rem", color: "#555" }}>
            Lower values push more of the model onto the CPU — slower, but frees VRAM and reduces GPU load. Layer count is resolved per model.
          </p>
        </div>

        <div style={{ marginTop: "1.25rem" }}>
          <label style={{ display: "flex", alignItems: "center", gap: "0.5rem", cursor: "pointer", userSelect: "none" }}>
            <input
              type="checkbox"
              checked={!!form.think}
              onChange={e => update("think", e.target.checked)}
              style={{ accentColor: "#b040c0", width: "16px", height: "16px" }}
            />
            <span style={{ fontSize: "0.85rem", color: "#ccc", fontWeight: 600 }}>Extended thinking</span>
          </label>
          <p style={{ margin: "0.4rem 0 0", fontSize: "0.72rem", color: "#555" }}>
            Let reasoning models (e.g. Qwen3) work through a hidden thinking step before answering. <strong>Off is much faster</strong> for chat and memory; turn on for hard multi-step problems. Ignored by models with no thinking mode.
          </p>
        </div>
      </div>

      {/* Memory Curator */}
      <div style={sectionStyle}>
        <h3 style={{ margin: "0 0 1rem", fontSize: "0.95rem", color: "#bbb" }}>Memory Curator</h3>
        <p style={{ margin: "0 0 1rem", fontSize: "0.75rem", color: "#555" }}>
          The model that reviews each exchange and saves new facts to memory, in the background after every reply.
        </p>

        <div style={{ marginBottom: "1rem" }}>
          <label style={labelStyle}>Curator Model</label>
          <input
            type="text"
            value={form.memory_model}
            onChange={e => update("memory_model", e.target.value)}
            placeholder="mistral:latest (blank = same model as chat)"
            style={inputStyle}
          />
          <p style={{ margin: "0.35rem 0 0", fontSize: "0.75rem", color: "#555" }}>
            A dedicated model like <code>mistral:latest</code> extracts facts more reliably than a small chat model.
          </p>
        </div>

        <div>
          <label style={labelStyle}>
            Curator GPU Offload
            <span style={{ float: "right", color: "#b040c0", fontWeight: 600, textTransform: "none", letterSpacing: 0 }}>
              {form.memory_gpu_offload < 0 ? "Auto" : `${form.memory_gpu_offload}%`}
            </span>
          </label>
          <input
            type="range"
            min="0"
            max="100"
            step="5"
            value={form.memory_gpu_offload < 0 ? 100 : form.memory_gpu_offload}
            disabled={form.memory_gpu_offload < 0}
            onChange={e => update("memory_gpu_offload", parseInt(e.target.value))}
            style={{ width: "100%", accentColor: "#b040c0", opacity: form.memory_gpu_offload < 0 ? 0.4 : 1 }}
          />
          <div style={{ display: "flex", justifyContent: "space-between", fontSize: "0.72rem", color: "#555", marginTop: "0.2rem" }}>
            <span>0% — all CPU/RAM</span>
            <span>100% — all GPU</span>
          </div>
          <label style={{ display: "flex", alignItems: "center", gap: "0.5rem", cursor: "pointer", userSelect: "none", marginTop: "0.6rem" }}>
            <input
              type="checkbox"
              checked={form.memory_gpu_offload < 0}
              onChange={e => update("memory_gpu_offload", e.target.checked ? -1 : 0)}
              style={{ accentColor: "#8cc63f", width: "16px", height: "16px" }}
            />
            <span style={{ fontSize: "0.82rem", color: form.memory_gpu_offload < 0 ? "#8cc63f" : "#888" }}>
              Auto — let Ollama fit layers to spare VRAM
            </span>
          </label>
          <p style={{ margin: "0.4rem 0 0", fontSize: "0.72rem", color: "#555" }}>
            Keep at <strong>0% (all CPU/RAM)</strong> on a small GPU so the curator doesn't evict the chat model — on a ~4&nbsp;GB GPU that thrash makes extraction time out and silently save nothing. Raise it only if you have spare VRAM.
          </p>
        </div>

        <div style={{ marginTop: "1.25rem" }}>
          <label style={{ display: "flex", alignItems: "center", gap: "0.5rem", cursor: "pointer", userSelect: "none" }}>
            <input
              type="checkbox"
              checked={form.memory_merge_threshold > 0}
              onChange={e => update("memory_merge_threshold", e.target.checked ? 0.80 : 0)}
              style={{ accentColor: "#b040c0", width: "16px", height: "16px" }}
            />
            <span style={{ fontSize: "0.85rem", color: "#ccc", fontWeight: 600 }}>Merge similar facts (edit in place)</span>
          </label>
          <p style={{ margin: "0.4rem 0 0.7rem", fontSize: "0.72rem", color: "#555" }}>
            When a newly extracted fact closely matches an existing one, update that entry instead of adding a duplicate. Uses embeddings.
          </p>
          {form.memory_merge_threshold > 0 && (
            <>
              <label style={labelStyle}>
                Merge threshold
                <span style={{ float: "right", color: "#b040c0", fontWeight: 600, textTransform: "none", letterSpacing: 0 }}>
                  {Number(form.memory_merge_threshold).toFixed(2)}
                </span>
              </label>
              <input
                type="range"
                min="0.60"
                max="0.98"
                step="0.01"
                value={form.memory_merge_threshold}
                onChange={e => update("memory_merge_threshold", parseFloat(e.target.value))}
                style={{ width: "100%", accentColor: "#b040c0" }}
              />
              <div style={{ display: "flex", justifyContent: "space-between", fontSize: "0.72rem", color: "#555", marginTop: "0.2rem" }}>
                <span>0.60 — merges more</span>
                <span>0.98 — only near-identical</span>
              </div>
            </>
          )}
        </div>
      </div>

      {/* Behaviour */}
      <div style={sectionStyle}>
        <h3 style={{ margin: "0 0 1rem", fontSize: "0.95rem", color: "#bbb" }}>Behaviour</h3>

        <div style={{ marginBottom: "1rem" }}>
          <label style={labelStyle}>Global System Prompt</label>
          <textarea
            value={form.system_prompt}
            onChange={e => update("system_prompt", e.target.value)}
            placeholder="Instructions applied to every conversation when no playbook is active..."
            rows={5}
            style={{ ...inputStyle, fontFamily: "system-ui", resize: "vertical" }}
          />
          <p style={{ margin: "0.35rem 0 0", fontSize: "0.75rem", color: "#555" }}>
            Playbook system prompts override this when active.
          </p>
        </div>

        <div>
          <label style={labelStyle}>
            Request Timeout (seconds)
            <span style={{ float: "right", color: "#7aa", fontWeight: 600, textTransform: "none", letterSpacing: 0 }}>
              {form.timeout}s
            </span>
          </label>
          <input
            type="range"
            min="10"
            max="600"
            step="10"
            value={form.timeout}
            onChange={e => update("timeout", parseInt(e.target.value))}
            style={{ width: "100%", accentColor: "#007acc" }}
          />
          <div style={{ display: "flex", justifyContent: "space-between", fontSize: "0.72rem", color: "#555", marginTop: "0.2rem" }}>
            <span>10s</span>
            <span>300s</span>
            <span>600s</span>
          </div>
        </div>
      </div>

      {/* Vite Dev Server */}
      <div style={sectionStyle}>
        <h3 style={{ margin: "0 0 0.5rem", fontSize: "0.95rem", color: "#bbb" }}>Vite Dev Server</h3>
        <p style={{ margin: "0 0 1rem", fontSize: "0.75rem", color: "#555" }}>
          Hot-reload server for editing the frontend directly (interface/web). Not
          needed for normal use — this backend already serves the built UI.
        </p>
        <div style={{ display: "flex", alignItems: "center", gap: "0.75rem", flexWrap: "wrap" }}>
          <button
            onClick={toggleVite}
            disabled={viteBusy || viteRunning === null}
            style={{
              padding: "0.6rem 1.25rem",
              background: viteRunning ? "#2a1a1a" : "#152a15",
              color: viteRunning ? "#ff8a80" : "#8aff8a",
              border: "1px solid " + (viteRunning ? "#5a2a2a" : "#2a5a2a"),
              borderRadius: "8px",
              cursor: (viteBusy || viteRunning === null) ? "not-allowed" : "pointer",
              fontSize: "0.88rem",
              fontWeight: 500,
              opacity: (viteBusy || viteRunning === null) ? 0.6 : 1,
            }}
          >
            {viteBusy ? (viteRunning ? "Stopping…" : "Starting…")
              : viteRunning ? "⏹ Stop Vite" : "▶ Start Vite"}
          </button>
          <span style={{ fontSize: "0.82rem", color: viteRunning ? "#8aff8a" : "#666" }}>
            {viteRunning === null ? "Checking…" : viteRunning ? "Running at :5173" : "Stopped"}
          </span>
          {viteError && <span style={{ color: "#f44336", fontSize: "0.85rem" }}>{viteError}</span>}
        </div>
      </div>

      {/* Icon Branding — Linux-only: composites icons onto XFCE/GTK app tiles,
          which don't exist on Windows/macOS. */}
      {platform === "linux" && (
      <div style={sectionStyle}>
        <h3 style={{ margin: "0 0 0.75rem", fontSize: "0.95rem", color: "#bbb" }}>App Icon Branding</h3>
        <p style={{ margin: "0 0 1rem", fontSize: "0.78rem", color: "#555" }}>
          Select apps on the left to composite their icon onto the NexusOS tile.
        </p>

        {/* Controls row */}
        <div style={{ display: "flex", gap: "1.5rem", marginBottom: "1rem", alignItems: "center", flexWrap: "wrap" }}>
          <div style={{ flex: "1 1 180px" }}>
            <label style={labelStyle}>
              Logo size
              <span style={{ float: "right", color: "#b040c0", fontWeight: 600, textTransform: "none", letterSpacing: 0 }}>
                {Math.round(iconFrac * 100)}%
              </span>
            </label>
            <input
              type="range"
              min="0.30"
              max="0.85"
              step="0.05"
              value={iconFrac}
              onChange={e => setIconFrac(parseFloat(e.target.value))}
              style={{ width: "100%", accentColor: "#b040c0" }}
            />
          </div>
          <label style={{ display: "flex", alignItems: "center", gap: "0.5rem", cursor: "pointer", userSelect: "none" }}>
            <input
              type="checkbox"
              checked={iconRing}
              onChange={e => setIconRing(e.target.checked)}
              style={{ accentColor: "#8cc63f", width: "16px", height: "16px" }}
            />
            <span style={{ fontSize: "0.85rem", color: iconRing ? "#8cc63f" : "#888" }}>Nexus ring</span>
          </label>
        </div>

        {/* Shuttle */}
        <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "0.75rem" }}>
          {/* Left: app list */}
          <div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
            <input
              type="text"
              placeholder="Filter apps…"
              value={iconFilter}
              onChange={e => setIconFilter(e.target.value)}
              style={{ ...inputStyle, padding: "0.4rem 0.6rem", fontSize: "0.82rem" }}
            />
            <div style={{ height: "280px", overflowY: "auto", background: "#0d0d0d", border: "1px solid #2a2a2a", borderRadius: "8px" }}>
              {filteredApps.length === 0 && (
                <div style={{ padding: "1rem", color: "#444", fontSize: "0.82rem", textAlign: "center" }}>
                  {iconApps.length === 0 ? "Loading…" : "No apps match"}
                </div>
              )}
              {filteredApps.map(app => {
                const isSelected = !!selectedApps.find(a => a.icon_name === app.icon_name);
                return (
                  <div
                    key={app.icon_name}
                    onClick={() => !isBranding && !isSelected && app.icon_path && toggleAppSelect(app)}
                    style={{
                      display: "flex",
                      alignItems: "center",
                      gap: "0.5rem",
                      padding: "0.35rem 0.6rem",
                      cursor: isBranding || isSelected || !app.icon_path ? "default" : "pointer",
                      background: isSelected ? "rgba(176,64,192,0.15)" : "transparent",
                      borderLeft: isSelected ? "2px solid #b040c0" : "2px solid transparent",
                      opacity: !app.icon_path ? 0.35 : 1,
                    }}
                    onMouseEnter={e => { if (!isSelected && app.icon_path && !isBranding) e.currentTarget.style.background = "rgba(255,255,255,0.04)"; }}
                    onMouseLeave={e => { if (!isSelected) e.currentTarget.style.background = "transparent"; }}
                  >
                    {app.icon_path ? (
                      <img
                        src={`${API_BASE}/icons/image?path=${encodeURIComponent(app.icon_path)}`}
                        alt=""
                        style={{ width: "22px", height: "22px", objectFit: "contain", flexShrink: 0 }}
                        onError={e => { e.target.style.display = "none"; }}
                      />
                    ) : (
                      <div style={{ width: "22px", height: "22px", background: "#1a1a1a", borderRadius: "4px", flexShrink: 0 }} />
                    )}
                    <span style={{ fontSize: "0.82rem", color: "#ccc", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                      {app.name}
                    </span>
                    {app.already_branded && (
                      <span style={{ marginLeft: "auto", color: "#8cc63f", fontSize: "0.72rem", flexShrink: 0 }}>✓</span>
                    )}
                  </div>
                );
              })}
            </div>
          </div>

          {/* Right: queue */}
          <div style={{ display: "flex", flexDirection: "column", gap: "0.5rem" }}>
            <div style={{ fontSize: "0.8rem", color: "#555", padding: "0.4rem 0", minHeight: "2rem" }}>
              {isBranding ? `Branding ${brandQueue.length} app${brandQueue.length !== 1 ? "s" : ""}…` : `${selectedApps.length} selected`}
            </div>
            <div style={{ height: "280px", overflowY: "auto", background: "#0d0d0d", border: "1px solid #2a2a2a", borderRadius: "8px" }}>
              {displayQueue.length === 0 && (
                <div style={{ padding: "1rem", color: "#333", fontSize: "0.82rem", textAlign: "center" }}>
                  Click apps to add →
                </div>
              )}
              {displayQueue.map(app => (
                <div
                  key={app.icon_name}
                  style={{ display: "flex", alignItems: "center", gap: "0.5rem", padding: "0.35rem 0.6rem" }}
                >
                  {isBranding ? (
                    <span style={{ fontSize: "1rem", width: "18px", textAlign: "center", flexShrink: 0 }}>
                      {statusIcon(app.status)}
                    </span>
                  ) : (
                    <button
                      onClick={() => removeFromQueue(app.icon_name)}
                      style={{ background: "transparent", border: "none", color: "#555", cursor: "pointer", fontSize: "0.9rem", padding: "0 2px", lineHeight: 1, flexShrink: 0 }}
                      onMouseEnter={e => e.target.style.color = "#f44336"}
                      onMouseLeave={e => e.target.style.color = "#555"}
                    >
                      ×
                    </button>
                  )}
                  <span style={{ fontSize: "0.82rem", color: isBranding && app.status === "done" ? "#8cc63f" : "#ccc", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
                    {app.name}
                  </span>
                </div>
              ))}
            </div>
          </div>
        </div>

        {/* Brand button */}
        <div style={{ marginTop: "0.75rem", display: "flex", alignItems: "center", gap: "0.75rem" }}>
          <button
            onClick={brandAll}
            disabled={!selectedApps.length || isBranding}
            style={{
              padding: "0.6rem 1.25rem",
              background: selectedApps.length && !isBranding ? "#5e0066" : "#1a1a1a",
              color: selectedApps.length && !isBranding ? "#eee" : "#444",
              border: `1px solid ${selectedApps.length && !isBranding ? "#b040c0" : "#2a2a2a"}`,
              borderRadius: "8px",
              cursor: selectedApps.length && !isBranding ? "pointer" : "default",
              fontSize: "0.88rem",
              fontWeight: 500,
              transition: "background 0.15s",
            }}
          >
            {isBranding ? "Branding…" : `Brand ${selectedApps.length || ""} App${selectedApps.length !== 1 ? "s" : ""}`}
          </button>
          {!isBranding && brandQueue.length > 0 && brandQueue.every(a => a.status === "done") && (
            <span style={{ color: "#8cc63f", fontSize: "0.85rem" }}>Done — panel reloaded</span>
          )}
        </div>
      </div>
      )}

      {/* Actions */}
      <div style={{ display: "flex", gap: "0.75rem", alignItems: "center" }}>
        <button
          onClick={save}
          style={{
            padding: "0.65rem 1.5rem",
            background: "#007acc",
            color: "#fff",
            border: "none",
            borderRadius: "8px",
            cursor: "pointer",
            fontSize: "0.9rem",
          }}
        >
          Save
        </button>
        <button
          onClick={reset}
          style={{
            padding: "0.65rem 1.1rem",
            background: "#222",
            color: "#aaa",
            border: "1px solid #444",
            borderRadius: "8px",
            cursor: "pointer",
            fontSize: "0.9rem",
          }}
        >
          Reset to defaults
        </button>
        {saved && <span style={{ color: "#4caf50", fontSize: "0.85rem" }}>Saved</span>}
        {error && <span style={{ color: "#f44336", fontSize: "0.85rem" }}>{error}</span>}
      </div>
    </div>
  );
}
