// sortof — main React app
// Fake state machine: empty → loading → partial → success/error/cold
// Three empty variants, three success variants, exposed via Tweaks.

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

// Live data shim: leaves keep reading D.X; we swap _liveSortData on each
// successful /api/sort response. Tweaks-preview mode leaves it null and
// falls back to the canned dataset.
let _liveSortData = null;
const D = new Proxy({}, {
  get(_, key) {
    const src = _liveSortData ?? window.SORTOF_DATA;
    return src ? src[key] : undefined;
  }
});

// ── icons ───────────────────────────────────────────────────────────────────
const IconCopy = () => (
  <svg className="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
    <rect x="9" y="9" width="13" height="13" rx="2"></rect>
    <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
  </svg>
);
const IconCheck = () => (
  <svg className="ico" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
    <polyline points="20 6 9 17 4 12"></polyline>
  </svg>
);
const IconGit = () => (
  <svg viewBox="0 0 24 24" fill="currentColor" width="14" height="14">
    <path d="M12 .5C5.7.5.5 5.7.5 12c0 5.1 3.3 9.4 7.9 10.9.6.1.8-.3.8-.6v-2c-3.2.7-3.9-1.5-3.9-1.5-.5-1.3-1.3-1.7-1.3-1.7-1-.7.1-.7.1-.7 1.2.1 1.8 1.2 1.8 1.2 1 1.8 2.7 1.3 3.4 1 .1-.8.4-1.3.8-1.6-2.6-.3-5.3-1.3-5.3-5.7 0-1.3.4-2.3 1.2-3.1-.1-.3-.5-1.5.1-3.1 0 0 1-.3 3.2 1.2.9-.3 1.9-.4 2.9-.4 1 0 2 .1 2.9.4 2.2-1.5 3.2-1.2 3.2-1.2.6 1.6.2 2.8.1 3.1.7.8 1.2 1.8 1.2 3.1 0 4.4-2.7 5.4-5.3 5.7.4.4.8 1.1.8 2.2v3.3c0 .3.2.7.8.6 4.6-1.5 7.9-5.8 7.9-10.9C23.5 5.7 18.3.5 12 .5z"/>
  </svg>
);

// ── small helpers ───────────────────────────────────────────────────────────
function CopyBtn({ text, label = 'copy', minimal = false }) {
  const [copied, setCopied] = useState(false);
  const onClick = async () => {
    try {
      await navigator.clipboard.writeText(text);
    } catch (e) {
      // fallback
      const ta = document.createElement('textarea');
      ta.value = text; document.body.appendChild(ta); ta.select();
      try { document.execCommand('copy'); } catch (_) {}
      document.body.removeChild(ta);
    }
    setCopied(true);
    setTimeout(() => setCopied(false), 1200);
  };
  if (minimal) {
    return (
      <button className={'cg-cp' + (copied ? ' copied' : '')} onClick={onClick} title="copy">
        {copied ? <IconCheck /> : <IconCopy />}
      </button>
    );
  }
  return (
    <button className={'copy-btn' + (copied ? ' copied' : '')} onClick={onClick}>
      {copied ? <IconCheck /> : <IconCopy />}
      {copied ? 'copied' : label}
    </button>
  );
}

// ── header & footer ─────────────────────────────────────────────────────────
function Header({ tagline }) {
  return (
    <header className="app">
      <div className="brand">
        <span className="wordmark">sortof<span className="dot">.</span></span>
        {tagline && <span className="tagline">{tagline}</span>}
      </div>
      <div className="head-right">
        <a className="icon-btn" href="#" onClick={(e) => e.preventDefault()}>
          <IconGit /> github
        </a>
        <a className="icon-btn" href="#" onClick={(e) => e.preventDefault()}>
          docs
        </a>
      </div>
    </header>
  );
}

function Footer() {
  return (
    <footer className="app">
      <div className="left">
        <span>based on <a href="#" onClick={(e) => e.preventDefault()}>REfRigERatoR's mod load order sorter</a></span>
      </div>
      <div className="right">
        <span>a thing by <a href="#" onClick={(e) => e.preventDefault()}>indifferent broccoli</a></span>
      </div>
    </footer>
  );
}

// ── empty-state variants ────────────────────────────────────────────────────
function EmptyRight({ variant }) {
  if (variant === 'bare') {
    return (
      <div className="right-empty">
        <span className="big">paste workshop ids on the left, then hit sort.</span>
        <span><span className="arrow">›</span> output appears here. one line per servertest field.</span>
      </div>
    );
  }
  if (variant === 'worked') {
    return (
      <div className="right-empty">
        <span className="big">what you'll get back</span>
        <div className="ex">
          <div><span style={{ color: 'var(--acc-blue)' }}>WorkshopItems</span>=2169435993;2392709985;…
          </div>
          <div><span style={{ color: 'var(--acc-blue)' }}>Mods</span>=modoptions;tsarslib;…</div>
          <div><span style={{ color: 'var(--acc-blue)' }}>Map</span>=Muldraugh, KY</div>
          <div className="ink-mut" style={{ marginTop: 6, fontSize: 10.5 }}>↑ paste these into your servertest.ini</div>
        </div>
      </div>
    );
  }
  // docs
  return (
    <div className="docs-panel">
      <div className="dp-head">
        <span>how this works</span>
        <span style={{ color: 'var(--fg-3)', fontSize: 10.5 }}>read once, never again</span>
      </div>
      <div className="dp-body">
        <ol>
          <li>paste numeric workshop ids (one per line) <em>or</em> a single <code>steamcommunity.com</code> collection url.</li>
          <li>optionally provide <code>sorting_rules.txt</code> to pin libs first / maps last / mark incompatibles.</li>
          <li>hit sort. we resolve dependencies, apply your rules, and emit three lines for <code>servertest.ini</code>.</li>
          <li>copy each line. paste. boot the server. probably works.</li>
        </ol>
        <div className="hint">we cache mod metadata for ~24h. cold-cache lookups take roughly 30s. you've been warned.</div>
      </div>
    </div>
  );
}

// ── status strip ────────────────────────────────────────────────────────────
function StatusStrip({ state, counts, progress }) {
  if (state === 'idle' || state === 'success' || state === 'error' || state === 'cold') {
    return (
      <div className="status-strip">
        <span className="status-pill idle">
          <span className="dot-led"></span>
          {state === 'idle' && 'ready when you are'}
          {state === 'success' && `done. ${counts.cached} mods, ${counts.warnings} warnings`}
          {state === 'error' && 'something went sideways'}
          {state === 'cold' && 'cache miss — be patient'}
        </span>
      </div>
    );
  }
  return (
    <div className="status-strip">
      <span className="status-pill cached">
        <span className="dot-led"></span>{counts.cached} cached
      </span>
      <span className="status-pill queued">
        <span className="dot-led"></span>{counts.queued} queued
      </span>
      <span className="status-pill parse">
        <span className="dot-led"></span>{counts.parsing} parsing
      </span>
      <div className="progress-bar"><i style={{ width: `${progress}%` }}></i></div>
    </div>
  );
}

// ── output: three success variants ──────────────────────────────────────────
function SuccessStacked({ data }) {
  return (
    <>
      <div className="code-block">
        <div className="cb-head">
          <span><span className="cb-key">WorkshopItems</span> <span className="cb-meta">· {data.count} ids</span></span>
          <CopyBtn text={`WorkshopItems=${D.WORKSHOP_ITEMS_LINE}`} />
        </div>
        <pre><span className="ink-key">WorkshopItems</span><span className="ink-sep">=</span>{D.WORKSHOP_ITEMS_LINE}</pre>
      </div>
      <div className="code-block">
        <div className="cb-head">
          <span><span className="cb-key">Mods</span> <span className="cb-meta">· {data.count} mods</span></span>
          <CopyBtn text={`Mods=${D.MODS_LINE}`} />
        </div>
        <pre><span className="ink-key">Mods</span><span className="ink-sep">=</span>{D.MODS_LINE}</pre>
      </div>
      <div className="code-block">
        <div className="cb-head">
          <span><span className="cb-key">Map</span> <span className="cb-meta">· 1 map detected</span></span>
          <CopyBtn text={`Map=${D.MAP_LINE}`} />
        </div>
        <pre><span className="ink-key">Map</span><span className="ink-sep">=</span>{D.MAP_LINE}</pre>
      </div>
    </>
  );
}

function SuccessCompact({ data }) {
  return (
    <div className="compact-grid">
      <div className="cg-row">
        <div className="cg-key">WorkshopItems</div>
        <div className="cg-val">{D.WORKSHOP_ITEMS_LINE}</div>
        <CopyBtn text={`WorkshopItems=${D.WORKSHOP_ITEMS_LINE}`} minimal />
      </div>
      <div className="cg-row">
        <div className="cg-key">Mods</div>
        <div className="cg-val">{D.MODS_LINE}</div>
        <CopyBtn text={`Mods=${D.MODS_LINE}`} minimal />
      </div>
      <div className="cg-row">
        <div className="cg-key">Map</div>
        <div className="cg-val">{D.MAP_LINE}</div>
        <CopyBtn text={`Map=${D.MAP_LINE}`} minimal />
      </div>
    </div>
  );
}

function SuccessNumbered({ data }) {
  const blocks = [
    { num: '01', key: 'WorkshopItems', val: D.WORKSHOP_ITEMS_LINE, meta: `${data.count} ids · ${(D.WORKSHOP_ITEMS_LINE.length)} chars` },
    { num: '02', key: 'Mods',          val: D.MODS_LINE,           meta: `${data.count} mods` },
    { num: '03', key: 'Map',           val: D.MAP_LINE,            meta: '1 map' },
  ];
  return (
    <>
      {blocks.map(b => (
        <div key={b.key} className="numbered-block">
          <div className="nb-head">
            <span className="nb-num">{b.num}</span>
            <span className="nb-key">{b.key}</span>
            <span className="nb-meta">{b.meta}</span>
          </div>
          <pre className="nb-pre">{b.val}</pre>
          <div className="nb-cp">
            <CopyBtn text={`${b.key}=${b.val}`} />
          </div>
        </div>
      ))}
    </>
  );
}

// ── warnings ─────────────────────────────────────────────────────────────────
function Warnings({ items, defaultOpen = true }) {
  const [open, setOpen] = useState(defaultOpen);
  if (!items || items.length === 0) return null;
  const reds = items.filter(w => w.level === 'red').length;
  const ambers = items.length - reds;
  return (
    <div className="panel warn-section">
      <div className="warn-head" onClick={() => setOpen(!open)}>
        <span>warnings</span>
        {reds > 0 && <span className="badge red">{reds}</span>}
        {ambers > 0 && <span className="badge">{ambers}</span>}
        <span style={{ color: 'var(--fg-3)', fontSize: 10.5 }}>
          {reds > 0 ? 'fix these or your server will sulk' : 'non-fatal but read them'}
        </span>
        <span className="chev">{open ? '▾' : '▸'}</span>
      </div>
      {open && (
        <ul className="warn-list">
          {items.map((w, i) => (
            <li key={i}>
              <span className={'w-tag ' + w.level}>{w.tag}</span>
              <span className="w-msg">{w.msg}</span>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

// ── mod details table ───────────────────────────────────────────────────────
function ModTable({ defaultOpen = false }) {
  const [open, setOpen] = useState(defaultOpen);
  const ordered = D.SORTED_ORDER.map(id => D.MOD_DB.find(m => m.modId === id));
  return (
    <div className="panel table-section">
      <div className="tbl-head" onClick={() => setOpen(!open)}>
        <span>mod details</span>
        <span className="count">· {ordered.length} mods</span>
        <span style={{ color: 'var(--fg-3)', fontSize: 10.5, marginLeft: 8 }}>
          why everything ended up where it did
        </span>
        <span className="chev">{open ? '▾' : '▸'}</span>
      </div>
      {open && (
        <table className="mods-table">
          <thead>
            <tr>
              <th>#</th>
              <th>mod id</th>
              <th>workshop id</th>
              <th>category</th>
              <th>dependencies</th>
              <th>load</th>
            </tr>
          </thead>
          <tbody>
            {ordered.map((m, i) => (
              <tr key={m.modId}>
                <td className="idx">{String(i + 1).padStart(2, '0')}</td>
                <td><span className="modid">{m.modId}</span></td>
                <td><span className="wsid">{m.wsid}</span></td>
                <td><span className={'cat ' + m.cat}>{m.cat}</span></td>
                <td><span className="deps">{m.deps && m.deps.length ? m.deps.join(', ') : '—'}</span></td>
                <td>
                  {m.pos === 'first' && <span className="pos first">first</span>}
                  {m.pos === 'last'  && <span className="pos last">last</span>}
                  {!m.pos && <span className="pos">—</span>}
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      )}
    </div>
  );
}

// ── right column dispatcher ────────────────────────────────────────────────
function RightColumn({ state, counts, progress, emptyVariant, successVariant, modTableDefault }) {
  // Right-column content depending on state
  const showWarnings = state === 'success' || state === 'partial';
  const showOutputs = state === 'partial' || state === 'success';

  return (
    <>
      {/* Warnings ABOVE outputs (always-inline treatment) */}
      {showWarnings && (
        <Warnings items={state === 'partial' ? D.WARNINGS.slice(0, 1) : D.WARNINGS} defaultOpen={state === 'success'} />
      )}

      {/* Cold cache banner */}
      {state === 'cold' && (
        <div className="cold-banner">
          <span className="err-tag">cold</span>
          <div className="err-msg">
            indexing <span style={{ color: 'var(--acc-blue)' }}>3 mods</span> we haven't seen before. try again in ~30s. or don't, we're not your boss.
          </div>
        </div>
      )}

      {/* Error banner */}
      {state === 'error' && (
        <div className="err-banner">
          <span className="err-tag">err</span>
          <div className="err-msg">
            steam workshop returned a 503. classic. cached results below are still valid.
            <div className="err-actions">
              <button className="copy-btn">retry</button>
              <button className="copy-btn">copy what we got</button>
            </div>
          </div>
        </div>
      )}

      {/* Output area */}
      {state === 'idle' && <EmptyRight variant={emptyVariant} />}
      {state === 'loading' && (
        <div className="right-empty">
          <span className="big">parsing your collection…</span>
          <span style={{ color: 'var(--fg-3)' }}>resolving dependencies. applying rules. probably fine.</span>
        </div>
      )}

      {showOutputs && (
        <>
          {state === 'partial' && (
            <div style={{
              fontFamily: 'var(--mono)', fontSize: 11, color: 'var(--acc-amber)',
              padding: '4px 2px', display: 'flex', gap: 8, alignItems: 'center'
            }}>
              <span className="dot-led" style={{ background: 'var(--acc-amber)' }}></span>
              showing cached subset · {counts.queued} mods still in flight
            </div>
          )}
          {successVariant === 'stacked'  && <SuccessStacked  data={{ count: D.SORTED_ORDER.length }} />}
          {successVariant === 'compact'  && <SuccessCompact  data={{ count: D.SORTED_ORDER.length }} />}
          {successVariant === 'numbered' && <SuccessNumbered data={{ count: D.SORTED_ORDER.length }} />}
        </>
      )}

      {/* Mod details — only when we have something to show */}
      {(state === 'success' || state === 'partial') && (
        <ModTable defaultOpen={modTableDefault} />
      )}
    </>
  );
}

// ── default tweak values ────────────────────────────────────────────────────
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
  "emptyVariant": "docs",
  "successVariant": "stacked",
  "stateOverride": "auto",
  "showTagline": true,
  "modTableDefault": false,
  "accentHue": 155
}/*EDITMODE-END*/;

// ── main app ────────────────────────────────────────────────────────────────
function App() {
  const [t, setTweak] = useTweaks(TWEAK_DEFAULTS);
  const [state, setState] = useState('idle');           // idle | loading | partial | success | error | cold
  const [counts, setCounts] = useState({ cached: 0, queued: 0, parsing: 0, warnings: 0 });
  const [progress, setProgress] = useState(0);
  const [input, setInput] = useState(D.SAMPLE_INPUT);
  const [rules, setRules] = useState('');

  const tagline = useMemo(() => {
    const taglines = [
      'sorted. sort of.',
      'your mods, sort of in order.',
      'a server-admin\u2019s tiny dignity.',
      'mostly correct, mostly the time.',
    ];
    return taglines[0];
  }, []);

  // accent override
  useEffect(() => {
    const root = document.documentElement;
    root.style.setProperty('--acc-green', `oklch(0.74 0.10 ${t.accentHue})`);
    root.style.setProperty('--acc-green-bg', `oklch(0.74 0.10 ${t.accentHue} / 0.12)`);
  }, [t.accentHue]);

  // state override (preview from Tweaks)
  useEffect(() => {
    if (t.stateOverride && t.stateOverride !== 'auto') {
      runState(t.stateOverride);
    }
    // eslint-disable-next-line
  }, [t.stateOverride]);

  const timersRef = useRef([]);
  const clearTimers = () => { timersRef.current.forEach(clearTimeout); timersRef.current = []; };

  function runState(target) {
    clearTimers();
    if (target === 'idle')    { setState('idle');    setProgress(0); setCounts({ cached: 0, queued: 0, parsing: 0, warnings: 0 }); return; }
    if (target === 'error')   { setState('error');   setProgress(100); setCounts({ cached: 7, queued: 0, parsing: 0, warnings: 1 }); return; }
    if (target === 'cold')    { setState('cold');    setProgress(45); setCounts({ cached: 0, queued: 3, parsing: 0, warnings: 0 }); return; }
    if (target === 'loading') { setState('loading'); setProgress(20); setCounts({ cached: 0, queued: 11, parsing: 1, warnings: 0 }); return; }
    if (target === 'partial') { setState('partial'); setProgress(70); setCounts({ cached: 7, queued: 4, parsing: 1, warnings: 1 }); return; }
    if (target === 'success') { setState('success'); setProgress(100); setCounts({ cached: 11, queued: 0, parsing: 0, warnings: D.WARNINGS.length }); return; }
  }

  async function onSort() {
    if (t.stateOverride !== 'auto') {
      // user is in preview mode; ignore real flow
      return;
    }
    clearTimers();
    setState('loading');
    setProgress(15);
    setCounts({ cached: 0, queued: 0, parsing: 0, warnings: 0 });

    try {
      const res = await fetch('/api/sort', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ input, rules })
      });
      if (!res.ok) {
        const txt = await res.text();
        console.error('sort failed', res.status, txt);
        _liveSortData = null;
        setState('error');
        setProgress(100);
        setCounts({ cached: 0, queued: 0, parsing: 0, warnings: 1 });
        return;
      }
      const json = await res.json();
      _liveSortData = json;
      const cached  = (json.MOD_DB || []).length;
      const queued  = (json.pending || []).length;
      const warns   = (json.WARNINGS || []).length;
      setProgress(100);
      setCounts({ cached, queued, parsing: 0, warnings: warns });
      setState(json.status || 'success');
    } catch (e) {
      console.error('sort threw', e);
      _liveSortData = null;
      setState('error');
      setProgress(100);
      setCounts({ cached: 0, queued: 0, parsing: 0, warnings: 1 });
    }
  }

  function onClear() {
    clearTimers();
    setInput('');
    setRules('');
    setState('idle');
    setProgress(0);
    setCounts({ cached: 0, queued: 0, parsing: 0, warnings: 0 });
  }

  return (
    <div className="shell">
      <Header tagline={t.showTagline ? tagline : null} />

      <main className="app">
        {/* LEFT */}
        <section className="col">
          <div>
            <div className="label-row">
              <span className="label">workshop ids or collection url</span>
              <span className="label-meta">{input.split('\n').filter(Boolean).length} lines</span>
            </div>
            <textarea
              className="field input-main"
              placeholder={"2169435993\n2392709985\nor https://steamcommunity.com/sharedfiles/filedetails/?id=…"}
              value={input}
              onChange={(e) => setInput(e.target.value)}
              spellCheck={false}
            />
          </div>

          <details className="collapsible">
            <summary>sorting rules (optional)</summary>
            <div className="label-row">
              <span className="label">sorting_rules.txt</span>
              <span className="label-meta">{rules ? `${rules.split('\n').length} lines` : 'empty — defaults will apply'}</span>
            </div>
            <textarea
              className="field input-rules"
              placeholder={D.SAMPLE_RULES}
              value={rules}
              onChange={(e) => setRules(e.target.value)}
              spellCheck={false}
            />
          </details>

          <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
            <button
              className="sort-btn"
              onClick={onSort}
              disabled={state === 'loading' || !input.trim()}
            >
              {state === 'loading' && <span className="spin"></span>}
              {state === 'loading' ? 'sorting…' : 'sort'}
            </button>
            {(input.trim() || state !== 'idle') && (
              <button className="copy-btn" onClick={onClear} style={{ height: 40, padding: '0 14px' }}>
                clear
              </button>
            )}
          </div>

          <StatusStrip state={state} counts={counts} progress={progress} />

          <div className="state-tag">
            <span className="dot-led"></span>
            state: {state}{t.stateOverride !== 'auto' ? ' (forced via tweaks)' : ''}
          </div>
        </section>

        {/* RIGHT */}
        <section className="col">
          <RightColumn
            state={state}
            counts={counts}
            progress={progress}
            emptyVariant={t.emptyVariant}
            successVariant={t.successVariant}
            modTableDefault={t.modTableDefault}
          />
        </section>
      </main>

      <Footer />

      {(typeof window !== 'undefined' &&
        new URLSearchParams(window.location.search).has('tweaks')) && (
        <TweaksPanel title="Tweaks">
          <TweakSection label="State preview" />
          <TweakSelect
            label="Force state"
            value={t.stateOverride}
            options={[
              { value: 'auto',    label: 'auto (drive via Sort)' },
              { value: 'idle',    label: 'idle / empty' },
              { value: 'loading', label: 'loading' },
              { value: 'partial', label: 'partial (cached + in-flight)' },
              { value: 'success', label: 'success' },
              { value: 'error',   label: 'error' },
              { value: 'cold',    label: 'cold cache miss' },
            ]}
            onChange={(v) => setTweak('stateOverride', v)}
          />
          <TweakButton label="run a fresh sort" onClick={() => { setTweak('stateOverride', 'auto'); setTimeout(onSort, 50); }} />

          <TweakSection label="Empty-state variant" />
          <TweakRadio
            label="Layout"
            value={t.emptyVariant}
            options={[
              { value: 'bare',   label: 'bare' },
              { value: 'worked', label: 'worked' },
              { value: 'docs',   label: 'docs' },
            ]}
            onChange={(v) => setTweak('emptyVariant', v)}
          />

          <TweakSection label="Success-state variant" />
          <TweakRadio
            label="Output style"
            value={t.successVariant}
            options={[
              { value: 'stacked',  label: 'stacked' },
              { value: 'compact',  label: 'compact' },
              { value: 'numbered', label: 'numbered' },
            ]}
            onChange={(v) => setTweak('successVariant', v)}
          />

          <TweakSection label="Chrome" />
          <TweakToggle  label="Show tagline"             value={t.showTagline}      onChange={(v) => setTweak('showTagline', v)} />
          <TweakToggle  label="Mod details open by default" value={t.modTableDefault} onChange={(v) => setTweak('modTableDefault', v)} />
          <TweakSlider  label="Accent hue (green band)" value={t.accentHue} min={120} max={170} step={1} onChange={(v) => setTweak('accentHue', v)} />
        </TweaksPanel>
      )}
    </div>
  );
}

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