/* global React, Pill */
const { useState: useCS, useMemo: useCM } = React;

function Controls(props) { return null; }

// ---------- Atoms ----------
function Toggle({ on, onChange, labelOn = "On", labelOff = "Off" }) {
  const showLabel = labelOn || labelOff;
  return (
    <div className="toggle-row">
      <button className={`toggle ${on ? "on" : ""}`} onClick={() => onChange(!on)} aria-pressed={on}/>
      {showLabel && <span className="toggle-state">{on ? labelOn : labelOff}</span>}
    </div>
  );
}

function Combo({ value, onChange, options, width = 220 }) {
  const [open, setOpen] = useCS(false);
  const [pos, setPos] = useCS(null);
  const btnRef = React.useRef(null);
  const cur = options.find(o => o.value === value) || options[0];

  // Find the closest theme root so the dropdown inherits the right CSS vars.
  const themeOf = (el) => {
    let n = el;
    while (n && n !== document.body) {
      if (n.classList && (n.classList.contains("dark") || n.classList.contains("light"))) {
        return n.classList.contains("light") ? "light" : "dark";
      }
      n = n.parentElement;
    }
    return "dark";
  };

  const toggle = () => {
    if (open) { setOpen(false); return; }
    const r = btnRef.current.getBoundingClientRect();
    const dropH = Math.min(options.length * 36 + 8, 280);
    const below = window.innerHeight - r.bottom;
    const placeAbove = below < dropH + 12 && r.top > dropH + 12;
    setPos({
      left: r.left,
      top: placeAbove ? r.top - dropH - 4 : r.bottom + 4,
      width: Math.max(r.width, width),
      theme: themeOf(btnRef.current),
    });
    setOpen(true);
  };

  React.useEffect(() => {
    if (!open) return;
    const onDoc = (e) => {
      if (btnRef.current && btnRef.current.contains(e.target)) return;
      setOpen(false);
    };
    const onScroll = () => setOpen(false);
    document.addEventListener("mousedown", onDoc);
    window.addEventListener("scroll", onScroll, true);
    return () => {
      document.removeEventListener("mousedown", onDoc);
      window.removeEventListener("scroll", onScroll, true);
    };
  }, [open]);

  return (
    <>
      <button ref={btnRef} className="combo" style={{ minWidth: width }} onClick={toggle}>
        <span className="combo-value">{cur?.label}</span>
        <span className="combo-arrow">&#xE70D;</span>
      </button>
      {open && pos && ReactDOM.createPortal(
        <div className={pos.theme === "light" ? "winwin light" : "winwin dark"} style={{
          position: "fixed", left: pos.left, top: pos.top, width: pos.width,
          maxHeight: 280, overflowY: "auto",
          background: "var(--mica)", border: "1px solid var(--card-stroke)",
          borderRadius: 4, boxShadow: "0 16px 48px rgba(0,0,0,0.4)",
          zIndex: 10000, padding: 4,
          width: undefined, // ensure width applies
          minWidth: pos.width,
        }}>
          {options.map(o => (
            <div key={o.value} onClick={() => { onChange(o.value); setOpen(false); }} style={{
              padding: "8px 12px", cursor: "pointer", borderRadius: 4,
              background: o.value === value ? "var(--control-fill)" : "transparent",
              color: "var(--text-primary)", fontSize: 13,
            }} onMouseEnter={(e) => e.currentTarget.style.background = "var(--control-fill-hover)"}
               onMouseLeave={(e) => e.currentTarget.style.background = o.value === value ? "var(--control-fill)" : "transparent"}>
              {o.label}
            </div>
          ))}
        </div>,
        document.body
      )}
    </>
  );
}

function Radio({ checked, onChange, children }) {
  return (
    <label className={`radio ${checked ? "checked" : ""}`} onClick={onChange}>
      <span className="dot"/>
      <span>{children}</span>
    </label>
  );
}

function Slider({ value, min = 0, max = 100, onChange, width = 200 }) {
  const pct = ((value - min) / (max - min)) * 100;
  return (
    <div className="slider" style={{ width }} onClick={(e) => {
      const r = e.currentTarget.getBoundingClientRect();
      const x = Math.max(0, Math.min(1, (e.clientX - r.left) / r.width));
      onChange(min + x * (max - min));
    }}>
      <div className="track"/>
      <div className="fill" style={{ width: `${pct}%` }}/>
      <div className="thumb" style={{ left: `${pct}%` }}/>
    </div>
  );
}

function Card({ icon, label, desc, children, expander, open, onToggle, body }) {
  return (
    <div className={`scard ${expander ? "expander" : ""} ${open ? "open" : ""}`}>
      <div className="scard-row" onClick={expander ? onToggle : undefined}>
        {icon && <span className="icon-wrap">{icon}</span>}
        <div className="meta">
          <p className="label">{label}</p>
          {desc && <p className="desc">{desc}</p>}
        </div>
        <div className="control">
          {children}
          {expander && <span className="chevron">&#xE70D;</span>}
        </div>
      </div>
      {expander && open && <div className="expander-body">{body}</div>}
    </div>
  );
}

function GroupLabel({ children }) { return <div className="group-label">{children}</div>; }

// ---------- Pages ----------
function HomePage({ data, setTab }) {
  return (
    <div>
      <div className="hero">
        <div className="hero-text">
          <p className="greeting">Welcome back</p>
          <h2 className="hello">Dimmy is ready to listen</h2>
          <p className="hello-sub">Press <strong style={{ color: "var(--text-primary)" }}>Win + Alt</strong> anywhere to start dictating, then release.</p>
          <div style={{ display: "flex", gap: 8, marginTop: 16 }}>
            <button className="btn accent"><span className="icon">&#xE720;</span>Test microphone</button>
            <button className="btn" onClick={() => setTab("shortcut")}><span className="icon">&#xE765;</span>Change shortcut</button>
          </div>
        </div>
        <div className="pill-stage">
          <Pill state="idle" border="rainbow" waveform="bars" llmDot={data.llmDotColor}/>
        </div>
      </div>

      <div className="qstats">
        <div className="qstat">
          <span className="qsi">&#xE8D7;</span>
          <span className="qsv">103</span>
          <span className="qsl">Words dictated</span>
        </div>
        <div className="qstat">
          <span className="qsi">&#xE916;</span>
          <span className="qsv">0:56</span>
          <span className="qsl">Speaking time</span>
        </div>
        <div className="qstat">
          <span className="qsi">&#xE945;</span>
          <span className="qsv">~1m</span>
          <span className="qsl">Time saved</span>
        </div>
      </div>

      <GroupLabel>Current setup</GroupLabel>
      <Card icon={<span style={{ fontFamily: "Segoe Fluent Icons" }}>&#xE720;</span>}
            label="Speech-to-text" desc="Groq · whisper-large-v3-turbo (free) · Cloud">
        <button className="btn" onClick={() => setTab("voice")}>Configure</button>
      </Card>
      <Card icon={<span style={{ fontFamily: "Segoe Fluent Icons" }}>&#xE945;</span>}
            label="Output style" desc="Comprehensible · Groq llama-3.3-70b">
        <span style={{ width: 10, height: 10, borderRadius: 99, background: "#38BDF8", boxShadow: "0 0 8px #38BDF866", marginRight: 8 }}/>
        <button className="btn" onClick={() => setTab("output")}>Configure</button>
      </Card>
      <Card icon={<span style={{ fontFamily: "Segoe Fluent Icons" }}>&#xE765;</span>}
            label="Hotkey" desc="Toggle mode">
        <span className="keycap">Win</span>
        <span style={{ color: "var(--text-tertiary)", margin: "0 4px" }}>+</span>
        <span className="keycap">Alt</span>
      </Card>

      <GroupLabel>Recent dictations</GroupLabel>
      <div className="recent">
        {[
          { t: "10:42", text: "Schedule a follow-up with the design team for Thursday afternoon.", style: "Professional", color: "#F472B6" },
          { t: "10:38", text: "Stavo per dire qualcosa di brillante, ma Dimmy ha capito questo.", style: "Imbruttito", color: "#EF4444" },
          { t: "09:14", text: "Refactor the audio buffer to clamp NaN values before they propagate.", style: "Correct", color: "#2DD4BF" },
        ].map((r, i) => (
          <div key={i} className="recent-row">
            <span className="time">{r.t}</span>
            <span className="text">{r.text}</span>
            <span className="style-tag"><span style={{ width: 6, height: 6, borderRadius: 99, background: r.color }}/>{r.style}</span>
          </div>
        ))}
      </div>
    </div>
  );
}

function VoicePage({ data, set }) {
  return (
    <div>
      <GroupLabel>Speech recognition engine</GroupLabel>
      <Card icon={<span style={{ fontFamily: "Segoe Fluent Icons" }}>&#xE753;</span>}
            label="Mode" desc="Choose where transcription runs">
        <div className="radio-group horizontal">
          <Radio checked={data.sttMode === "local"} onChange={() => set({ sttMode: "local" })}>Local (offline)</Radio>
          <Radio checked={data.sttMode === "cloud"} onChange={() => set({ sttMode: "cloud" })}>Cloud</Radio>
        </div>
      </Card>
      <Card icon={<span style={{ fontFamily: "Segoe Fluent Icons" }}>&#xE968;</span>}
            label="Provider" desc={data.sttMode === "cloud" ? "Cloud transcription · ~250 ms latency" : "Runs on your device · 78 MB model"}>
        <Combo value={data.sttProvider} onChange={(v) => set({ sttProvider: v })} width={260} options={[
          { value: "groq-whisper", label: "Groq · whisper-large-v3-turbo (free)" },
          { value: "openai-whisper", label: "OpenAI · whisper-1" },
          { value: "deepgram-nova", label: "Deepgram · nova-3" },
          { value: "gemini", label: "Gemini · flash" },
        ]}/>
      </Card>
      <Card icon={<span style={{ fontFamily: "Segoe Fluent Icons" }}>&#xE192;</span>}
            label="API key" desc="Saved · encrypted locally with AES-256-GCM">
        <span style={{ color: "#6CCB5F", fontFamily: "Segoe Fluent Icons", fontSize: 16, marginRight: 8 }}>&#xE73E;</span>
        <button className="btn">Replace</button>
      </Card>
      <Card icon={<span style={{ fontFamily: "Segoe Fluent Icons" }}>&#xF2B7;</span>}
            label="Language" desc="Helps the model with rare words and names">
        <Combo value={data.lang} onChange={(v) => set({ lang: v })} options={[
          { value: "auto", label: "Auto detect" },
          { value: "en", label: "English" },
          { value: "it", label: "Italian" },
          { value: "es", label: "Spanish" },
          { value: "fr", label: "French" },
          { value: "de", label: "German" },
        ]}/>
      </Card>

      <GroupLabel>Microphone</GroupLabel>
      <Card icon={<span style={{ fontFamily: "Segoe Fluent Icons" }}>&#xE720;</span>}
            label="Input device">
        <Combo value={data.micDevice} onChange={(v) => set({ micDevice: v })} width={260} options={[
          { value: "realtek", label: "Microphone Array (Realtek Audio)" },
          { value: "headset", label: "Headset Microphone (Jabra Evolve)" },
          { value: "external", label: "Yeti X (USB)" },
        ]}/>
      </Card>
      <Card label="Input level" desc="Speak now · bars should reach the middle">
        <div className="meter"><div className="fill" style={{ width: "62%" }}/></div>
      </Card>
      <Card label="Microphone volume">
        <Slider value={data.micVolume} min={0} max={100} onChange={(v) => set({ micVolume: Math.round(v) })}/>
        <span style={{ marginLeft: 8, color: "var(--text-secondary)", fontSize: 13, minWidth: 32 }}>{Math.round(data.micVolume)}</span>
      </Card>

      <GroupLabel>Audio processing</GroupLabel>
      <Card label="Voice activity detection" desc="Stops recording after 3 seconds of silence">
        <Toggle on={data.vad} onChange={(v) => set({ vad: v })}/>
      </Card>
      <Card label="Preprocessing" desc="Reduce noise and normalize volume before transcribing">
        <Toggle on={data.preprocessing} onChange={(v) => set({ preprocessing: v })}/>
      </Card>
      {data.advanced && (
        <Card label="Chunk streaming" desc="Stream audio in 250 ms chunks for faster partial results">
          <Toggle on={data.chunkStream} onChange={(v) => set({ chunkStream: v })}/>
        </Card>
      )}

      <GroupLabel>Vocabulary</GroupLabel>
      <Card label="Custom vocabulary" desc="Words and phrases the model should expect">
        <button className="btn"><span className="icon">&#xE70F;</span>Edit list</button>
      </Card>
    </div>
  );
}

function OutputPage({ data, set }) {
  const styles = [
    ["off", "Off", "#41B0B1"], ["correct", "Correct", "#2DD4BF"],
    ["summarize", "Summarize", "#FBBF24"], ["elaborate", "Elaborate", "#4ADE80"],
    ["comprehensible", "Comprehensible", "#38BDF8"], ["professional", "Professional", "#F472B6"],
    ["prompt", "Prompt", "#A78BFA"], ["genz", "Gen Z", "#E879F9"],
    ["boomer", "Boomer", "#F97316"], ["emoji", "Emoji", "#FACC15"],
    ["acronyms", "Acronyms", "#22D3EE"], ["imbruttito", "Imbruttito", "#EF4444"],
    ["custom", "Custom…", "#FB923C"],
  ];
  return (
    <div>
      <GroupLabel>LLM enhancement</GroupLabel>
      <div className="scard" style={{ padding: "16px 16px 18px", display: "flex", flexDirection: "column", gap: 10 }}>
        <div className="meta">
          <p className="label">Rewrite style</p>
          <p className="desc">After transcribing, Dimmy rewrites your text in this style before pasting.</p>
        </div>
        <div className="chips">
          {styles.map(([v, l, c]) => (
            <button key={v} className={`chip ${data.llmStyle === v ? "selected" : ""}`} onClick={() => set({ llmStyle: v, llmDotColor: c })}>
              <span className="dot" style={{ background: c }}/>{l}
            </button>
          ))}
        </div>
      </div>
      <Card label="Mode" desc="Where the rewrite runs">
        <div className="radio-group horizontal">
          <Radio checked={data.llmMode === "local"} onChange={() => set({ llmMode: "local" })}>Local (offline)</Radio>
          <Radio checked={data.llmMode === "cloud"} onChange={() => set({ llmMode: "cloud" })}>Cloud</Radio>
        </div>
      </Card>
      <Card label="Provider">
        <Combo value={data.llmProvider} onChange={(v) => set({ llmProvider: v })} width={240} options={[
          { value: "groq-llama", label: "Groq · llama-3.3-70b (free)" },
          { value: "anthropic", label: "Anthropic · Claude Haiku 4.5" },
          { value: "openai", label: "OpenAI · gpt-4o-mini" },
          { value: "ollama", label: "Local (Ollama)" },
        ]}/>
      </Card>
      <Card label="Use same API key as STT" desc="Reuse the speech-to-text key for the LLM provider">
        <Toggle on={data.sameKey} onChange={(v) => set({ sameKey: v })}/>
      </Card>

      <GroupLabel>Clipboard</GroupLabel>
      <Card label="Auto-paste into focused app" desc="Paste your transcription into whatever app has focus">
        <Toggle on={data.autoPaste} onChange={(v) => set({ autoPaste: v })}/>
      </Card>
      <Card label="Keep in clipboard history" desc="Save your last 20 dictations locally">
        <Toggle on={data.clipboardHistory} onChange={(v) => set({ clipboardHistory: v })}/>
      </Card>
    </div>
  );
}

function PillPage({ data, set }) {
  const positions = [
    ["top-left", "TL"], ["top-center", "TC"], ["top-right", "TR"],
    ["middle-left", "ML"], ["middle-center", "MC"], ["middle-right", "MR"],
    ["bottom-left", "BL"], ["bottom-center", "BC"], ["bottom-right", "BR"],
  ];
  return (
    <div>
      <GroupLabel>Live preview</GroupLabel>
      <div className="preview-stage" style={{ marginBottom: 16 }}>
        <Pill state={data.previewState} border={data.borderStyle} waveform={data.waveformStyle} llmDot={data.llmDotColor} timer={data.previewState === "recording" ? "0:04" : null} scale={1.4}/>
        <span className="stage-tag">Preview · {data.previewState}</span>
      </div>
      <div style={{ display: "flex", gap: 6, marginBottom: 16, flexWrap: "wrap" }}>
        {["idle", "recording", "transcribing", "processing", "done", "error"].map(s => (
          <button key={s} className={`chip ${data.previewState === s ? "selected" : ""}`} onClick={() => set({ previewState: s })}>{s}</button>
        ))}
      </div>

      <GroupLabel>Position</GroupLabel>
      <Card icon={<span style={{ fontFamily: "Segoe Fluent Icons" }}>&#xE759;</span>}
            label="Default position" desc="You can always drag the pill to reposition it">
        <div className="pospicker">
          {positions.map(([id]) => (
            <button key={id} className={data.position === id ? "selected" : ""} onClick={() => set({ position: id })}/>
          ))}
        </div>
      </Card>
      <Card label="Reset position" desc="Snap the pill back to your default location">
        <button className="btn"><span className="icon">&#xE777;</span>Reset</button>
      </Card>

      <GroupLabel>Appearance</GroupLabel>
      <Card icon={<span style={{ fontFamily: "Segoe Fluent Icons" }}>&#xE790;</span>}
            label="Border style" desc="Color and motion of the pill border while recording">
        <Combo value={data.borderStyle} onChange={(v) => set({ borderStyle: v })} options={[
          { value: "rainbow", label: "Rainbow (rotating)" },
          { value: "blue", label: "Blue (pulsing)" },
          { value: "green", label: "Green (pulsing)" },
          { value: "purple", label: "Purple (pulsing)" },
          { value: "orange", label: "Orange (pulsing)" },
          { value: "none", label: "None" },
        ]}/>
      </Card>
      <Card label="Waveform style" desc="How sound is visualized inside the pill">
        <Combo value={data.waveformStyle} onChange={(v) => set({ waveformStyle: v })} options={[
          { value: "bars", label: "Bars (centered)" },
          { value: "dots", label: "Dots" },
          { value: "line", label: "Line" },
        ]}/>
      </Card>
      <Card label="Always on top" desc="Float above other windows">
        <Toggle on={data.alwaysOnTop} onChange={(v) => set({ alwaysOnTop: v })}/>
      </Card>
    </div>
  );
}

function ShortcutPage({ data, set }) {
  return (
    <div>
      <GroupLabel>Hotkey</GroupLabel>
      <Card icon={<span style={{ fontFamily: "Segoe Fluent Icons" }}>&#xE765;</span>}
            label="Activation hotkey" desc="Click to capture a new key combination">
        <span className="keycap">Win</span>
        <span style={{ color: "var(--text-tertiary)", margin: "0 4px" }}>+</span>
        <span className="keycap">Alt</span>
        <button className="btn" style={{ marginLeft: 12 }}>Change</button>
      </Card>
      <Card label="Mode" desc="How the hotkey controls recording">
        <div className="radio-group horizontal">
          <Radio checked={data.hotkeyMode === "ptt"} onChange={() => set({ hotkeyMode: "ptt" })}>Push-to-talk</Radio>
          <Radio checked={data.hotkeyMode === "toggle"} onChange={() => set({ hotkeyMode: "toggle" })}>Toggle</Radio>
        </div>
      </Card>

      <GroupLabel>Behavior</GroupLabel>
      <Card label="Cancel with Escape" desc="Press Esc while recording to discard">
        <Toggle on={data.cancelEsc} onChange={(v) => set({ cancelEsc: v })}/>
      </Card>
      {data.advanced && (
        <Card label="Double-tap to lock" desc="Double-tap the hotkey to record hands-free until tapped again">
          <Toggle on={data.doubleTapLock} onChange={(v) => set({ doubleTapLock: v })}/>
        </Card>
      )}
    </div>
  );
}

function PrivacyPage({ data, set }) {
  return (
    <div>
      <div className="infobar info">
        <span className="icon">&#xE946;</span>
        <div className="body">
          <div className="ititle">Your transcriptions stay yours</div>
          <div className="imsg">Dimmy never sends your transcriptions, audio, API keys, microphone names, or anything personal. Anonymous usage data and crash reports help us spot bugs and decide what to build next.</div>
        </div>
      </div>

      <GroupLabel>Telemetry</GroupLabel>
      <Card label="Send anonymous usage data" desc='Things like "app started", "transcription completed in 2.3s", "cloud provider: groq". No content, no identifiers.'>
        <Toggle on={data.usageData} onChange={(v) => set({ usageData: v })}/>
      </Card>
      <Card label="Send crash reports" desc="If Dimmy crashes, sends a stack trace (no environment variables, no paths with your username) so we can fix it.">
        <Toggle on={data.crashReports} onChange={(v) => set({ crashReports: v })}/>
      </Card>

      <GroupLabel>Anonymous identifier</GroupLabel>
      <Card label="Local ID" desc="48e033dd… · a random ID generated locally on first launch. Resetting takes effect after restart.">
        <button className="btn"><span className="icon">&#xE777;</span>Reset</button>
      </Card>

      <GroupLabel>Feedback</GroupLabel>
      <div className="scard" style={{ padding: 16, display: "flex", flexDirection: "column", gap: 10 }}>
        <p className="label" style={{ margin: 0, fontSize: 14, color: "var(--text-primary)" }}>Send feedback</p>
        <Combo value={data.feedbackKind} onChange={(v) => set({ feedbackKind: v })} options={[
          { value: "general", label: "General" },
          { value: "bug", label: "Bug report" },
          { value: "idea", label: "Idea or feature request" },
        ]}/>
        <textarea className="textarea" placeholder="What's on your mind? Bugs, ideas, anything." defaultValue=""/>
        <input className="input" placeholder="Your email (optional, only if you want a reply)"/>
        <div><button className="btn accent"><span className="icon">&#xE724;</span>Send</button></div>
      </div>

      <GroupLabel>More</GroupLabel>
      <Card label="Privacy policy" desc="What we collect and why">
        <a href="#" className="link">Open</a>
      </Card>
      <Card label="What we collect" desc="A specific, line-by-line breakdown">
        <a href="#" className="link">Open</a>
      </Card>
    </div>
  );
}

function AboutPage({ data, set }) {
  return (
    <div>
      <div className="hero">
        <div className="hero-text">
          <p className="greeting">Voice dictation that stays out of your way</p>
          <h2 className="hello">Dimmy 0.7.12</h2>
          <p className="hello-sub">Made with irony · Released April 14, 2026</p>
          <div style={{ display: "flex", gap: 8, marginTop: 16 }}>
            <button className="btn accent"><span className="icon">&#xE777;</span>Check for updates</button>
            <button className="btn"><span className="icon">&#xE7C3;</span>View release notes</button>
          </div>
        </div>
        <div className="pill-stage" style={{ background: "transparent", border: 0 }}>
          <img src="assets/icon_1024.png" alt="" style={{ width: 96, height: 96, filter: "drop-shadow(0 8px 24px rgba(56,189,248,0.35))" }}/>
        </div>
      </div>

      <GroupLabel>Updates</GroupLabel>
      <Card label="Automatic updates" desc="Get new versions as they ship">
        <Toggle on={data.autoUpdate} onChange={(v) => set({ autoUpdate: v })}/>
      </Card>
      <Card label="Update channel" desc="Stable releases or early previews">
        <Combo value={data.channel} onChange={(v) => set({ channel: v })} options={[
          { value: "stable", label: "Stable" },
          { value: "beta", label: "Beta" },
          { value: "nightly", label: "Nightly" },
        ]}/>
      </Card>

      <GroupLabel>Resources</GroupLabel>
      <Card label="Documentation" desc="Guides, shortcuts, troubleshooting">
        <a href="#" className="link">dimmy.app/docs &rarr;</a>
      </Card>
      <Card label="GitHub repository" desc="Source, issues, releases">
        <a href="#" className="link">github.com/KonradDallaOrg/dimmy &rarr;</a>
      </Card>
      <Card label="License" desc="MIT · open source, free to fork">
        <a href="#" className="link">View license</a>
      </Card>
    </div>
  );
}

function RulesPage({ data, set }) {
  const styleMap = {
    off: ["Off", "#41B0B1"], correct: ["Correct", "#2DD4BF"],
    summarize: ["Summarize", "#FBBF24"], elaborate: ["Elaborate", "#4ADE80"],
    comprehensible: ["Comprehensible", "#38BDF8"], professional: ["Professional", "#F472B6"],
    prompt: ["Prompt", "#A78BFA"], genz: ["Gen Z", "#E879F9"],
    boomer: ["Boomer", "#F97316"], emoji: ["Emoji", "#FACC15"],
    acronyms: ["Acronyms", "#22D3EE"], imbruttito: ["Imbruttito", "#EF4444"],
    custom: ["Custom", "#FB923C"],
  };
  const styleOpts = Object.entries(styleMap).map(([v, [l]]) => ({ value: v, label: l }));
  const toneOpts = [
    { value: "auto", label: "Auto (match app)" },
    { value: "casual", label: "Casual" },
    { value: "neutral", label: "Neutral" },
    { value: "formal", label: "Formal" },
    { value: "technical", label: "Technical" },
  ];
  const rules = data.rules || [];
  const update = (id, patch) => set({ rules: rules.map(r => r.id === id ? { ...r, ...patch } : r) });
  const remove = (id) => set({ rules: rules.filter(r => r.id !== id) });
  const add = () => {
    const id = `r${Date.now()}`;
    set({ rules: [...rules, { id, app: "New rule", exe: "", icon: "\uECAA", color: "#888", style: "correct", tone: "auto", enabled: true }] });
  };
  const [dragId, setDragId] = useCS(null);
  const [overId, setOverId] = useCS(null);
  const [dropPos, setDropPos] = useCS("before"); // before | after
  const onDragStart = (e, id) => {
    setDragId(id);
    e.dataTransfer.effectAllowed = "move";
    try { e.dataTransfer.setData("text/plain", id); } catch (_) {}
  };
  const onDragOver = (e, id) => {
    if (!dragId || dragId === id) return;
    e.preventDefault();
    e.dataTransfer.dropEffect = "move";
    const rect = e.currentTarget.getBoundingClientRect();
    const before = (e.clientY - rect.top) < rect.height / 2;
    setOverId(id);
    setDropPos(before ? "before" : "after");
  };
  const onDragLeaveRow = (e, id) => {
    if (overId === id && !e.currentTarget.contains(e.relatedTarget)) setOverId(null);
  };
  const onDrop = (e, id) => {
    e.preventDefault();
    if (!dragId || dragId === id) { setDragId(null); setOverId(null); return; }
    const from = rules.findIndex(r => r.id === dragId);
    const to = rules.findIndex(r => r.id === id);
    if (from < 0 || to < 0) { setDragId(null); setOverId(null); return; }
    const next = [...rules];
    const [moved] = next.splice(from, 1);
    let target = next.findIndex(r => r.id === id);
    if (dropPos === "after") target += 1;
    next.splice(target, 0, moved);
    set({ rules: next });
    setDragId(null); setOverId(null);
  };
  const onDragEnd = () => { setDragId(null); setOverId(null); };

  return (
    <div>
      <div className="infobar info">
        <span className="icon">&#xE9CE;</span>
        <div className="body">
          <div className="ititle">Match the rewrite to the app you're in</div>
          <div className="imsg">Dimmy detects the focused app and applies its rule before pasting. Slack gets Gen Z, Outlook gets Professional, your terminal gets nothing · your call. Rules are evaluated top-down; the first match wins.</div>
        </div>
      </div>

      <GroupLabel>Auto-switching</GroupLabel>
      <Card icon={<span style={{ fontFamily: "Segoe Fluent Icons" }}>&#xE7C4;</span>}
            label="Enable app-aware rewriting" desc="When off, Dimmy always uses the default style from the Output page.">
        <Toggle on={data.rulesEnabled} onChange={(v) => set({ rulesEnabled: v })}/>
      </Card>
      <Card label="If no rule matches" desc="Fallback when the focused app isn't in the list">
        <Combo value={data.fallbackStyle || "comprehensible"} onChange={(v) => set({ fallbackStyle: v })} options={styleOpts} width={200}/>
      </Card>
      <Card label="Suggest rules from usage" desc="Dimmy can suggest rules based on apps you dictate into often. No data leaves your device.">
        <Toggle on={data.suggestRules} onChange={(v) => set({ suggestRules: v })}/>
      </Card>

      <GroupLabel>Rules · {rules.length}</GroupLabel>

      <div className="scard" style={{ padding: 0 }}>
        <div style={{ display: "grid", gridTemplateColumns: "20px 32px 1.6fr 1.4fr 1.2fr 48px 32px", padding: "10px 16px", borderBottom: "1px solid var(--divider)", fontSize: 11, color: "var(--text-tertiary)", textTransform: "uppercase", letterSpacing: "0.05em", fontWeight: 600, alignItems: "center", gap: 12 }}>
          <span></span><span></span><span>App</span><span>Rewrite style</span><span>Tone</span><span>Active</span><span></span>
        </div>
        {rules.map((r, i) => {
          const isDragging = dragId === r.id;
          const isOver = overId === r.id && dragId && dragId !== r.id;
          return (
            <div key={r.id}
                 onDragOver={(e) => onDragOver(e, r.id)}
                 onDragLeave={(e) => onDragLeaveRow(e, r.id)}
                 onDrop={(e) => onDrop(e, r.id)}
                 style={{
                   display: "grid", gridTemplateColumns: "20px 32px 1.6fr 1.4fr 1.2fr 48px 32px",
                   padding: "12px 16px",
                   borderBottom: i < rules.length - 1 ? "1px solid var(--divider)" : 0,
                   alignItems: "center", gap: 12,
                   opacity: r.enabled ? (isDragging ? 0.4 : 1) : 0.5,
                   background: isDragging ? "var(--card-hover)" : "transparent",
                   boxShadow: isOver
                     ? (dropPos === "before"
                         ? "inset 0 2px 0 0 var(--accent)"
                         : "inset 0 -2px 0 0 var(--accent)")
                     : "none",
                   transition: "background 120ms ease",
                 }}>
              <span draggable
                    onDragStart={(e) => onDragStart(e, r.id)}
                    onDragEnd={onDragEnd}
                    title="Drag to reorder"
                    style={{
                      cursor: "grab", display: "inline-flex", alignItems: "center", justifyContent: "center",
                      width: 20, height: 28, color: "var(--text-tertiary)",
                      fontFamily: "Segoe Fluent Icons", fontSize: 14, userSelect: "none",
                    }}>&#xE76F;</span>
              <span style={{ display: "inline-flex", alignItems: "center", justifyContent: "center", width: 28, height: 28, borderRadius: 6, background: `${r.color}22`, color: r.color, fontFamily: "Segoe Fluent Icons", fontSize: 14 }}>{r.icon}</span>
              <div style={{ minWidth: 0 }}>
                <div style={{ fontSize: 13, color: "var(--text-primary)", fontWeight: 500, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{r.app}</div>
                <div style={{ fontSize: 11, color: "var(--text-tertiary)", fontFamily: "Cascadia Mono, ui-monospace, monospace", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{r.exe || "·"}</div>
              </div>
              <div style={{ display: "inline-flex", alignItems: "center", gap: 8 }}>
                <span style={{ width: 8, height: 8, borderRadius: 99, background: styleMap[r.style]?.[1] || "#888", flexShrink: 0 }}/>
                <Combo value={r.style} onChange={(v) => update(r.id, { style: v })} options={styleOpts} width={140}/>
              </div>
              <Combo value={r.tone} onChange={(v) => update(r.id, { tone: v })} options={toneOpts} width={140}/>
              <Toggle on={r.enabled} onChange={(v) => update(r.id, { enabled: v })} labelOn="" labelOff=""/>
              <button className="btn"
                      onClick={() => remove(r.id)}
                      title="Delete rule"
                      style={{ height: 28, width: 28, padding: 0, border: 0, background: "transparent", color: "var(--text-tertiary)" }}>
                <span className="icon">&#xE74D;</span>
              </button>
            </div>
          );
        })}
        {rules.length === 0 && (
          <div style={{ padding: 32, textAlign: "center", color: "var(--text-secondary)", fontSize: 13 }}>
            No rules yet. Add your first one · Dimmy will switch styles automatically when those apps are in focus.
          </div>
        )}
      </div>

      <div style={{ display: "flex", gap: 8, marginTop: 12 }}>
        <button className="btn accent" onClick={add}><span className="icon">&#xE710;</span>Add rule</button>
        <button className="btn"><span className="icon">&#xE721;</span>Detect installed apps</button>
      </div>

      <GroupLabel>Currently focused</GroupLabel>
      <Card icon={<span style={{ fontFamily: "Segoe Fluent Icons", color: "#5865F2" }}>&#xE8BD;</span>}
            label="Slack · #design-team" desc="Will use: Gen Z · Casual">
        <span className="status-chip"><span className="ldot"/>Live detection</span>
      </Card>
    </div>
  );
}

function AdvancedPage({ data, set }) {
  return (
    <div>
      <div className="infobar warning">
        <span className="icon" style={{ color: "#fce100" }}>&#xE7BA;</span>
        <div className="body">
          <div className="ititle">Advanced settings</div>
          <div className="imsg">These options can affect performance and stability. Change them only if you know what you're doing.</div>
        </div>
      </div>

      <GroupLabel>Performance</GroupLabel>
      <Card label="GPU acceleration" desc="GPU enabled · no prior crashes detected.">
        <span style={{ color: "#6CCB5F", fontSize: 12, fontWeight: 600 }}>● Active</span>
      </Card>

      <GroupLabel>Diagnostics</GroupLabel>
      <Card label="LLM log enabled" desc="Write all prompts and responses to disk for debugging">
        <Toggle on={data.llmLog} onChange={(v) => set({ llmLog: v })}/>
      </Card>
      <Card label="Audio debug" desc="Save raw audio of each recording locally">
        <Toggle on={data.audioDebug} onChange={(v) => set({ audioDebug: v })}/>
      </Card>
      <Card label="ggml debug logging" desc="Verbose model load · useful for diagnosing local model issues">
        <Toggle on={data.ggmlDebug} onChange={(v) => set({ ggmlDebug: v })}/>
      </Card>

      <GroupLabel>Reset</GroupLabel>
      <Card label="Open log folder" desc="Inspect raw logs">
        <button className="btn"><span className="icon">&#xE838;</span>Open</button>
      </Card>
      <Card label="Reset all settings" desc="Restore Dimmy to factory defaults">
        <button className="btn">Reset</button>
      </Card>
    </div>
  );
}

window.SettingsPages = { HomePage, VoicePage, OutputPage, PillPage, ShortcutPage, PrivacyPage, AboutPage, AdvancedPage, RulesPage };
