// Config Editor — single server, Form view + JSON editor
const { useLang } = window.CIE_I18N;

function ScenarioPicker({ value, onChange, scenarios }) {
  const { t } = useLang();
  const list = scenarios || [];
  const [open, setOpen] = useState(false);
  const [search, setSearch] = useState('');
  const known = list.find(s => s.id === value);
  const filtered = list.filter(s =>
    !search || s.name.toLowerCase().includes(search.toLowerCase()) || s.id.toLowerCase().includes(search.toLowerCase())
  );
  return (
    <div style={{position:'relative', flex:1}}>
      <div style={{display:'flex', gap:6}}>
        <input
          type="text"
          value={value || ''}
          onChange={e => onChange(e.target.value)}
          style={{flex:1, fontSize:11}}
          placeholder="{...}Missions/..."
        />
        <button
          type="button"
          className="btn ghost sm"
          style={{flexShrink:0, fontSize:10, padding:'2px 8px'}}
          onClick={() => setOpen(o => !o)}
        >▾ {t('form.scenario_pick')}</button>
      </div>
      {known && (
        <div style={{fontFamily:'var(--mono)', fontSize:10, color:'var(--accent)', marginTop:3}}>
          ✓ {known.name}
        </div>
      )}
      {open && (
        <div style={{
          position:'absolute', top:'100%', right:0, zIndex:50, marginTop:4,
          background:'var(--surface-2)', border:'1px solid var(--border)',
          borderRadius:4, width:320, boxShadow:'0 8px 24px rgba(0,0,0,0.4)',
        }}>
          <div style={{padding:'8px 10px', borderBottom:'1px solid var(--line)'}}>
            <input
              value={search}
              onChange={e => setSearch(e.target.value)}
              placeholder={t('search_placeholder')}
              autoFocus
              style={{width:'100%', fontFamily:'var(--mono)', fontSize:11, background:'transparent'}}
            />
          </div>
          {filtered.map(s => (
            <div key={s.id} onClick={() => { onChange(s.id); setOpen(false); setSearch(''); }}
              style={{
                padding:'8px 12px', cursor:'pointer', borderBottom:'1px solid var(--line)',
                background: s.id === value ? 'rgba(120,200,130,0.08)' : undefined,
              }}
              onMouseEnter={e => e.currentTarget.style.background = 'var(--surface-3)'}
              onMouseLeave={e => e.currentTarget.style.background = s.id === value ? 'rgba(120,200,130,0.08)' : 'transparent'}
            >
              <div style={{fontSize:12, fontWeight:500}}>{s.name}</div>
              <div style={{fontFamily:'var(--mono)', fontSize:9.5, color:'var(--text-mute)', marginTop:2, wordBreak:'break-all'}}>{s.id}</div>
            </div>
          ))}
          {filtered.length === 0 && (
            <div style={{padding:'12px', fontFamily:'var(--mono)', fontSize:11, color:'var(--text-mute)', textAlign:'center'}}>
              {t('dash.no_match')}
            </div>
          )}
        </div>
      )}
    </div>
  );
}

function normalizeEditorServer(s) {
  return {
    id:            s.id,
    name:          s.name,
    region:        s.region,
    status:        s.status || 'offline',
    cfgVersion:    s.cfg_version || s.cfgVersion || '—',
  };
}

// ---------- FORM VIEW ----------

function FormToggle({ on, onChange }) {
  return (
    <div
      className={`toggle ${on ? 'on' : ''}`}
      onClick={() => onChange(!on)}
      style={{cursor:'pointer'}}
    />
  );
}

function FormRow({ label, jsonKey, children }) {
  return (
    <div className="form-row">
      <div className="lbl">
        {label}
        {jsonKey && <span className="key">{jsonKey}</span>}
      </div>
      <div className="ctl">{children}</div>
    </div>
  );
}

// ---------- DIFF MODAL ----------

function diffLines(oldStr, newStr) {
  const a = oldStr.split('\n');
  const b = newStr.split('\n');
  const result = [];
  let ai = 0, bi = 0;
  while (ai < a.length || bi < b.length) {
    if (ai < a.length && bi < b.length && a[ai] === b[bi]) {
      result.push({ type: 'same', line: a[ai] });
      ai++; bi++;
    } else if (bi < b.length && (ai >= a.length || !a.includes(b[bi], ai))) {
      result.push({ type: 'add', line: b[bi] });
      bi++;
    } else {
      result.push({ type: 'del', line: a[ai] });
      ai++;
    }
  }
  return result;
}

function DiffModal({ oldJson, newJson, onConfirm, onCancel }) {
  const { t } = useLang();
  const lines = useMemo(() => {
    const a = JSON.stringify(JSON.parse(oldJson), null, 2);
    const b = JSON.stringify(JSON.parse(newJson), null, 2);
    return diffLines(a, b).filter((l, i, arr) => {
      if (l.type !== 'same') return true;
      // показуємо контекст ±2 рядки біля змін
      return arr.slice(Math.max(0,i-2), i+3).some(x => x.type !== 'same');
    });
  }, [oldJson, newJson]);

  const added   = lines.filter(l => l.type === 'add').length;
  const removed = lines.filter(l => l.type === 'del').length;

  return (
    <div style={{position:'fixed', inset:0, background:'rgba(0,0,0,0.7)', display:'flex', alignItems:'center', justifyContent:'center', zIndex:200}}>
      <div style={{background:'var(--surface)', border:'1px solid var(--border-strong)', borderRadius:6, width:700, maxWidth:'95vw', maxHeight:'80vh', display:'flex', flexDirection:'column'}}>
        <div style={{padding:'14px 18px', borderBottom:'1px solid var(--border)', display:'flex', alignItems:'center', gap:12}}>
          <div style={{flex:1}}>
            <div style={{fontWeight:600}}>{t('editor.diff_title')}</div>
            <div style={{fontFamily:'var(--mono)', fontSize:10.5, color:'var(--text-mute)', marginTop:2}}>
              <span style={{color:'var(--ok)'}}>+{added}</span>
              {' · '}
              <span style={{color:'var(--err)'}}>−{removed}</span>
            </div>
          </div>
          <button className="btn ghost sm" onClick={onCancel}>{t('cancel')}</button>
          <button className="btn primary sm" onClick={onConfirm}>{t('editor.diff_confirm')}</button>
        </div>
        <div style={{flex:1, overflowY:'auto', fontFamily:'var(--mono)', fontSize:11.5, lineHeight:1.6}}>
          {lines.map((l, i) => (
            <div key={i} style={{
              padding:'1px 16px',
              background: l.type === 'add' ? 'rgba(60,200,100,0.10)' : l.type === 'del' ? 'rgba(255,70,70,0.10)' : 'transparent',
              color: l.type === 'add' ? 'var(--ok)' : l.type === 'del' ? 'var(--err)' : 'var(--text-dim)',
              whiteSpace:'pre',
            }}>
              {l.type === 'add' ? '+' : l.type === 'del' ? '−' : ' '} {l.line}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

function FormView({ cfg, onCfgChange, scenarios }) {
  const { t } = useLang();
  if (!cfg) return null;
  const g  = cfg.game || {};
  const gp = g.gameProperties || {};
  const op = cfg.operating || {};

  // Хелпер: рядок → число або рядок
  const num = (val) => { const n = Number(val); return isNaN(n) ? val : n; };

  const ch = (path, cast) => (e) => onCfgChange(path, cast ? cast(e.target.value) : e.target.value);
  const tog = (path, curVal) => () => onCfgChange(path, !curVal);

  return (
    <div className="form-shell">
      <div className="form-section">
        <div className="head"><h4><span className="ix">A1</span>{t('form.general')}</h4></div>
        <div className="body">
          <FormRow label={t('form.server_name')} jsonKey="game.name">
            <input type="text" value={g.name ?? ''} onChange={ch('game.name')} />
          </FormRow>
          <FormRow label={t('form.scenario')} jsonKey="game.scenarioId">
            <ScenarioPicker value={g.scenarioId ?? ''} onChange={v => onCfgChange('game.scenarioId', v)} scenarios={scenarios} />
          </FormRow>
          <FormRow label={t('form.max_players')} jsonKey="game.maxPlayers">
            <div className="input-with-unit">
              <input type="number" value={g.maxPlayers ?? ''} onChange={ch('game.maxPlayers', num)} />
              <span className="unit">{t('form.slots')}</span>
            </div>
          </FormRow>
          <FormRow label={t('form.server_pw')} jsonKey="game.password">
            <input type="text" value={g.password ?? ''} onChange={ch('game.password')} placeholder={t('form.server_pw_empty')} />
          </FormRow>
          <FormRow label={t('form.admin_pw')} jsonKey="game.passwordAdmin">
            <input type="password" value={g.passwordAdmin ?? ''} onChange={ch('game.passwordAdmin')} />
          </FormRow>
          <FormRow label={t('form.visible')} jsonKey="game.visible">
            <FormToggle on={!!g.visible} onChange={v => onCfgChange('game.visible', v)} />
          </FormRow>
          <FormRow label={t('form.cross_platform')} jsonKey="game.crossPlatform">
            <FormToggle on={!!g.crossPlatform} onChange={v => onCfgChange('game.crossPlatform', v)} />
          </FormRow>
        </div>
      </div>

      <div className="form-section">
        <div className="head"><h4><span className="ix">A2</span>{t('form.network')}</h4></div>
        <div className="body">
          <FormRow label={t('form.bind_addr')} jsonKey="bindAddress">
            <input type="text" value={cfg.bindAddress ?? ''} onChange={ch('bindAddress')} />
          </FormRow>
          <FormRow label={t('form.bind_port')} jsonKey="bindPort">
            <input type="number" value={cfg.bindPort ?? ''} onChange={ch('bindPort', num)} />
          </FormRow>
          <FormRow label={t('form.public_addr')} jsonKey="publicAddress">
            <input type="text" value={cfg.publicAddress ?? ''} onChange={ch('publicAddress')} />
          </FormRow>
          <FormRow label={t('form.public_port')} jsonKey="publicPort">
            <input type="number" value={cfg.publicPort ?? ''} onChange={ch('publicPort', num)} />
          </FormRow>
          <FormRow label={t('form.rcon_port')} jsonKey="rcon.port">
            <input type="number" value={cfg.rcon?.port ?? ''} onChange={ch('rcon.port', num)} />
          </FormRow>
        </div>
      </div>

      <div className="form-section">
        <div className="head"><h4><span className="ix">A3</span>{t('form.gameplay')}</h4></div>
        <div className="body">
          <FormRow label={t('form.server_view')} jsonKey="gameProperties.serverMaxViewDistance">
            <div className="input-with-unit">
              <input type="number" value={gp.serverMaxViewDistance ?? ''} onChange={ch('game.gameProperties.serverMaxViewDistance', num)} />
              <span className="unit">{t('form.dist_m')}</span>
            </div>
          </FormRow>
          <FormRow label={t('form.net_view')} jsonKey="gameProperties.networkViewDistance">
            <div className="input-with-unit">
              <input type="number" value={gp.networkViewDistance ?? ''} onChange={ch('game.gameProperties.networkViewDistance', num)} />
              <span className="unit">{t('form.dist_m')}</span>
            </div>
          </FormRow>
          <FormRow label={t('form.min_grass')} jsonKey="gameProperties.serverMinGrassDistance">
            <div className="input-with-unit">
              <input type="number" value={gp.serverMinGrassDistance ?? ''} onChange={ch('game.gameProperties.serverMinGrassDistance', num)} />
              <span className="unit">{t('form.dist_m')}</span>
            </div>
          </FormRow>
          <FormRow label={t('form.no_third')} jsonKey="gameProperties.disableThirdPerson">
            <FormToggle on={!!gp.disableThirdPerson} onChange={v => onCfgChange('game.gameProperties.disableThirdPerson', v)} />
          </FormRow>
          <FormRow label={t('form.battleye')} jsonKey="gameProperties.battlEye">
            <FormToggle on={!!gp.battlEye} onChange={v => onCfgChange('game.gameProperties.battlEye', v)} />
          </FormRow>
          <FormRow label={t('form.von_ui')} jsonKey="gameProperties.VONDisableUI">
            <FormToggle on={!!gp.VONDisableUI} onChange={v => onCfgChange('game.gameProperties.VONDisableUI', v)} />
          </FormRow>
        </div>
      </div>

      <div className="form-section">
        <div className="head"><h4><span className="ix">A4</span>{t('form.operating')}</h4></div>
        <div className="body">
          <FormRow label={t('form.player_save')} jsonKey="operating.playerSaveTime">
            <div className="input-with-unit">
              <input type="number" value={op.playerSaveTime ?? ''} onChange={ch('operating.playerSaveTime', num)} />
              <span className="unit">{t('form.sec')}</span>
            </div>
          </FormRow>
          <FormRow label={t('form.ai_limit')} jsonKey="operating.aiLimit">
            <input type="number" value={op.aiLimit ?? ''} onChange={ch('operating.aiLimit', num)} />
          </FormRow>
          <FormRow label={t('form.slot_res')} jsonKey="operating.slotReservationTimeout">
            <div className="input-with-unit">
              <input type="number" value={op.slotReservationTimeout ?? ''} onChange={ch('operating.slotReservationTimeout', num)} />
              <span className="unit">{t('form.sec')}</span>
            </div>
          </FormRow>
          <FormRow label={t('form.join_queue')} jsonKey="operating.joinQueueMaxSize">
            <input type="number" value={op.joinQueueMaxSize ?? ''} onChange={ch('operating.joinQueueMaxSize', num)} />
          </FormRow>
        </div>
      </div>

      {g.mods?.length > 0 && (
        <div className="form-section">
          <div className="head"><h4><span className="ix">A5</span>{t('form.mods')}</h4></div>
          <div className="body" style={{padding:0}}>
            <table className="tbl" style={{margin:0}}>
              <thead>
                <tr>
                  <th>{t('form.mod_name')}</th>
                  <th style={{width:180}}>{t('form.mod_workshop')}</th>
                  <th style={{width:80}}>{t('form.mod_version')}</th>
                </tr>
              </thead>
              <tbody>
                {g.mods.map(m => (
                  <tr key={m.modId}>
                    <td style={{fontWeight:500}}>{m.name}</td>
                    <td className="mono dim" style={{fontSize:11}}>{m.modId}</td>
                    <td className="mono">{m.version || <span style={{color:'rgba(100,170,255,0.6)', fontStyle:'italic'}}>auto</span>}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </div>
      )}
    </div>
  );
}

// ---------- MAIN EDITOR ----------

function ConfigEditor({ servers: rawServers, initialServerId, goto }) {
  const { t } = useLang();
  const servers = useMemo(() => (rawServers || []).map(normalizeEditorServer), [rawServers]);

  const [activeId, setActiveId] = useState(initialServerId || null);
  const [tab, setTab] = useState('json');
  const [search, setSearch] = useState('');
  const [sidebarOpen, setSidebarOpen] = useState(() => localStorage.getItem('editor_sidebar') !== 'closed');
  const [cfg, setCfg] = useState(null);
  const [jsonText, setJsonText] = useState('');
  const [jsonValid, setJsonValid] = useState(true);
  const [dirty, setDirty] = useState(false);
  const [loading, setLoading] = useState(false);
  const [saving, setSaving] = useState(false);
  const [savedMsg, setSavedMsg] = useState('');
  const [loadErr, setLoadErr] = useState('');
  const [showDiff, setShowDiff] = useState(false);
  const [origJson, setOrigJson] = useState(''); // збережена версія для diff
  const [scenarios, setScenarios] = useState([]);

  // Monaco editor
  const monacoEditorRef   = useRef(null);  // monaco editor instance
  const editorContainerRef = useRef(null); // DOM div
  const suppressChange    = useRef(false); // prevent onChange loop when we setValue programmatically

  // Re-layout Monaco when sidebar collapses/expands
  useEffect(() => {
    const t = setTimeout(() => {
      if (monacoEditorRef.current) monacoEditorRef.current.layout();
    }, 280);
    return () => clearTimeout(t);
  }, [sidebarOpen]);

  // Load scenarios list once
  useEffect(() => {
    CIE_API.getScenarios()
      .then(data => setScenarios(data || []))
      .catch(() => {});
  }, []);

  // Initialize Monaco editor once the container div is mounted
  useEffect(() => {
    let disposed = false;
    // Use window.require explicitly — Monaco's AMD loader, not CommonJS
    window.require(['vs/editor/editor.main'], () => {
      if (disposed || !editorContainerRef.current || monacoEditorRef.current) return;

      const m = window.monaco;

      // Custom dark theme that matches panel colors
      m.editor.defineTheme('cie-dark', {
        base: 'vs-dark',
        inherit: true,
        rules: [
          { token: 'string.key.json',   foreground: '7ec8a0' },
          { token: 'string.value.json', foreground: 'ce9178' },
          { token: 'number',            foreground: 'b5cea8' },
          { token: 'keyword.json',      foreground: '569cd6' },
          { token: 'delimiter',         foreground: '808080' },
        ],
        colors: {
          'editor.background':          '#0e1117',
          'editor.foreground':          '#c9d1d9',
          'editorLineNumber.foreground':'#444c56',
          'editorLineNumber.activeForeground': '#768390',
          'editor.lineHighlightBackground': '#161b22',
          'editor.selectionBackground': '#264f78',
          'editorCursor.foreground':    '#7ec8a0',
          'editorGutter.background':    '#0e1117',
          'scrollbarSlider.background': '#30363d',
        },
      });

      const editor = m.editor.create(editorContainerRef.current, {
        value: '',
        language: 'json',
        theme: 'cie-dark',
        fontFamily: "'IBM Plex Mono', 'Cascadia Code', monospace",
        fontSize: 12.5,
        lineHeight: 20,
        minimap: { enabled: false },
        scrollBeyondLastLine: false,
        automaticLayout: false,   // manual layout — we call layout() ourselves
        tabSize: 2,
        wordWrap: 'off',
        lineNumbers: 'on',
        glyphMargin: true,
        folding: true,
        bracketPairColorization: { enabled: true },
        formatOnPaste: true,
        renderWhitespace: 'none',
        padding: { top: 12, bottom: 12 },
      });

      editor.onDidChangeModelContent(() => {
        if (suppressChange.current) return;
        const val = editor.getValue();
        setJsonText(val);
        setDirty(true);
        setSavedMsg('');
        try { JSON.parse(val); setJsonValid(true); }
        catch { setJsonValid(false); }
      });

      monacoEditorRef.current = editor;

      // Initial layout after mount
      requestAnimationFrame(() => editor.layout());

      // Keep layout in sync with container size changes
      if (window.ResizeObserver) {
        const ro = new ResizeObserver(() => {
          if (!disposed) editor.layout();
        });
        ro.observe(editorContainerRef.current);
        // Store for cleanup
        editorContainerRef.current._ro = ro;
      }
    });

    return () => {
      disposed = true;
      // Disconnect ResizeObserver
      if (editorContainerRef.current?._ro) {
        editorContainerRef.current._ro.disconnect();
      }
      if (monacoEditorRef.current) {
        monacoEditorRef.current.dispose();
        monacoEditorRef.current = null;
      }
    };
  }, []);

  // Re-layout Monaco whenever it becomes visible (tab switch or loading finishes)
  useEffect(() => {
    if (tab !== 'json' || loading || loadErr) return;
    const raf = requestAnimationFrame(() => {
      monacoEditorRef.current?.layout();
    });
    return () => cancelAnimationFrame(raf);
  }, [tab, loading, loadErr]);

  // Pick first server if none pre-selected
  useEffect(() => {
    if (!activeId && servers.length > 0) {
      setActiveId(servers[0].id);
    }
  }, [servers]);

  // Fetch config when active server changes
  useEffect(() => {
    if (!activeId) return;
    setLoading(true);
    setLoadErr('');
    setCfg(null);
    setDirty(false);
    setSavedMsg('');
    CIE_API.getConfig(activeId)
      .then(data => {
        setCfg(data);
        const text = JSON.stringify(data, null, 2);
        setJsonText(text);
        setOrigJson(text);
        setJsonValid(true);
        // Push new content into Monaco without triggering our onChange
        if (monacoEditorRef.current) {
          suppressChange.current = true;
          monacoEditorRef.current.setValue(text);
          suppressChange.current = false;
          // Scroll to top
          monacoEditorRef.current.setScrollPosition({ scrollTop: 0 });
        }
      })
      .catch(err => setLoadErr(err.message))
      .finally(() => setLoading(false));
  }, [activeId]);

  const handleJsonChange = (e) => {
    const val = e.target.value;
    setJsonText(val);
    setDirty(true);
    setSavedMsg('');
    try { JSON.parse(val); setJsonValid(true); }
    catch { setJsonValid(false); }
  };

  // Змінює одне поле у cfg через dot-path ("game.name", "rcon.port", …)
  const handleFormChange = (path, value) => {
    setCfg(prev => {
      const next = JSON.parse(JSON.stringify(prev));
      const keys = path.split('.');
      let obj = next;
      for (let i = 0; i < keys.length - 1; i++) {
        if (obj[keys[i]] == null) obj[keys[i]] = {};
        obj = obj[keys[i]];
      }
      obj[keys[keys.length - 1]] = value;
      setJsonText(JSON.stringify(next, null, 2));
      setJsonValid(true);
      setDirty(true);
      setSavedMsg('');
      return next;
    });
  };

  const handleSave = () => {
    if (!jsonValid) return;
    setShowDiff(true);
  };

  const doSave = () => {
    setShowDiff(false);
    let parsed;
    try { parsed = JSON.parse(jsonText); }
    catch { return; }
    setSaving(true);
    CIE_API.putConfig(activeId, parsed)
      .then(() => {
        setDirty(false);
        setCfg(parsed);
        setOrigJson(jsonText);
        setSavedMsg(t('editor.committed'));
        setTimeout(() => setSavedMsg(''), 4000);
      })
      .catch(err => {
        setSavedMsg('✗ ' + err.message);
        setTimeout(() => setSavedMsg(''), 6000);
      })
      .finally(() => setSaving(false));
  };

  const handleServerSwitch = (id) => {
    if (dirty && !window.confirm(t('editor.unsaved_confirm'))) return;
    setActiveId(id);
  };

  const activeServer = servers.find(s => s.id === activeId);
  const visible = servers.filter(s =>
    !search || `${s.id} ${s.name}`.toLowerCase().includes(search.toLowerCase())
  );
  const lines = jsonText.split('\n').length;
  const bytes = new Blob([jsonText]).size;

  return (
    <div className="view">
      {showDiff && origJson && (
        <DiffModal
          oldJson={origJson}
          newJson={jsonText}
          onConfirm={doSave}
          onCancel={() => setShowDiff(false)}
        />
      )}
      <div className="view-head">
        <div>
          <h1>{t('editor.title')}</h1>
          <div className="sub">
            <span style={{color:'var(--text-dim)'}}>{activeId || '—'}</span>
            <span style={{margin:'0 8px', color:'var(--text-faint)'}}>·</span>
            {t('editor.gitea_ok')}
            {loading && <span style={{color:'var(--text-mute)', marginLeft:10}}>{t('loading')}</span>}
            {loadErr && <span style={{color:'var(--err)', marginLeft:10}}>✗ {loadErr}</span>}
            {!loading && dirty && !saving && (
              <span style={{color: jsonValid ? 'var(--warn)' : 'var(--err)', marginLeft:10}}>
                {jsonValid ? t('editor.unsaved_warn') : t('editor.invalid_json')}
              </span>
            )}
            {savedMsg && <span style={{color: savedMsg.startsWith('✗') ? 'var(--err)' : 'var(--ok)', marginLeft:10}}>{savedMsg}</span>}
          </div>
        </div>
        <div className="actions">
          <button className="btn ghost sm" onClick={() => goto('dashboard')}>{t('editor.back')}</button>
          <button
            className="btn primary sm"
            disabled={!dirty || !jsonValid || saving || loading}
            onClick={handleSave}
          >
            {saving ? t('saving') : t('editor.save_btn')}
          </button>
        </div>
      </div>

      <div className="editor-shell" style={{gridTemplateColumns: sidebarOpen ? '280px 1fr' : '0px 1fr', gap: sidebarOpen ? 14 : 0, transition:'grid-template-columns 0.25s ease, gap 0.25s ease'}}>
        {/* Server list */}
        <div className="target-list" style={{overflow:'hidden', minWidth:0, transition:'all 0.25s ease'}}>
          <div className="head">
            <span>{t('servers')}</span>
            <span style={{color:'var(--text-dim)'}}>{servers.length}</span>
          </div>
          <div style={{padding:'8px 10px', borderBottom:'1px solid var(--line)'}}>
            <div className="search" style={{padding:'3px 8px'}}>
              <span className="ico">⌕</span>
              <input
                value={search}
                onChange={e => setSearch(e.target.value)}
                placeholder="filter"
                style={{fontSize:10.5}}
              />
            </div>
          </div>
          <div className="scroll">
            {visible.map(s => (
              <div
                key={s.id}
                className={`target-row ${activeId === s.id ? 'active' : ''}`}
                onClick={() => handleServerSwitch(s.id)}
              >
                <div style={{flex:1, minWidth:0}}>
                  <div className="name" style={{whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis'}}>{s.id}</div>
                  <div className="meta">
                    {s.region} · {s.cfgVersion}
                  </div>
                </div>
              </div>
            ))}
          </div>
        </div>

        {/* Editor pane */}
        <div className="editor-pane">
          <div className="editor-tabs">
            <button
              onClick={() => { const next = !sidebarOpen; setSidebarOpen(next); localStorage.setItem('editor_sidebar', next ? 'open' : 'closed'); }}
              style={{background:'none', border:'none', color:'var(--text-dim)', cursor:'pointer', padding:'0 10px', fontSize:13, flexShrink:0}}
              title="Toggle server list"
            >{sidebarOpen ? '‹' : '›'}</button>
            <div className={`tab ${tab==='form'?'on':''}`} onClick={() => setTab('form')}>{t('editor.tab_form')}</div>
            <div className={`tab ${tab==='json'?'on':''}`} onClick={() => {
              // Sync Monaco with any form edits before switching to JSON view
              if (monacoEditorRef.current) {
                const cur = monacoEditorRef.current.getValue();
                if (cur !== jsonText) {
                  suppressChange.current = true;
                  monacoEditorRef.current.setValue(jsonText);
                  suppressChange.current = false;
                }
                requestAnimationFrame(() => monacoEditorRef.current?.layout());
              }
              setTab('json');
            }}>{t('editor.tab_json')}</div>
            <div className="spacer" />
            <div className="right">
              <span className={`dot ${!dirty ? 'clean' : ''}`} />
              <span>{saving ? t('saving') : loading ? t('loading') : dirty ? t('editor.state_unsaved') : t('editor.state_synced')}</span>
              <span style={{color:'var(--text-faint)'}}>·</span>
              <span style={{color:'var(--text-mute)', fontSize:10}}>{activeId}</span>
            </div>
          </div>

          {/*
            Monaco must ALWAYS be in the DOM with real dimensions —
            display:none kills its layout engine (black screen).
            We use a position:relative wrapper and absolutely overlay
            loading / error / form states on top of it instead.
          */}
          <div style={{flex:1, minHeight:0, position:'relative'}}>

            {/* Monaco — always rendered, always has full size */}
            <div
              ref={editorContainerRef}
              style={{position:'absolute', inset:0}}
            />

            {/* Overlay: loading */}
            {loading && (
              <div style={{
                position:'absolute', inset:0, zIndex:2,
                background:'var(--surface)',
                display:'flex', alignItems:'center', justifyContent:'center',
              }}>
                <span style={{fontFamily:'var(--mono)', fontSize:11, color:'var(--text-mute)'}}>
                  {t('editor.loading_cfg')}
                </span>
              </div>
            )}

            {/* Overlay: load error */}
            {!loading && loadErr && (
              <div style={{
                position:'absolute', inset:0, zIndex:2,
                background:'var(--surface)',
                display:'flex', alignItems:'center', justifyContent:'center',
              }}>
                <span style={{fontFamily:'var(--mono)', fontSize:11, color:'var(--err)'}}>
                  {t('editor.load_fail')} {loadErr}
                </span>
              </div>
            )}

            {/* Overlay: Form tab */}
            {!loading && !loadErr && tab === 'form' && (
              <div style={{position:'absolute', inset:0, zIndex:2, background:'var(--surface)', overflowY:'auto'}}>
                <FormView cfg={cfg} onCfgChange={handleFormChange} scenarios={scenarios} />
              </div>
            )}
          </div>

          <div className="editor-foot">
            <span>{t('editor.lines')} <span className="v">{lines}</span></span>
            <span>·</span>
            <span>{t('editor.bytes')} <span className="v">{bytes.toLocaleString()}</span></span>
            <span>·</span>
            <span>
              {jsonValid
                ? <span style={{color:'var(--ok)'}}>✓ VALID</span>
                : <span style={{color:'var(--err)'}}>✗ JSON ERROR</span>
              }
            </span>
            <div className="right">
              <span style={{color:'var(--text-faint)'}}>UTF-8 · JSON · 2 sp</span>
              {tab === 'json' && (
                <button
                  className="btn ghost sm"
                  style={{fontSize:9, padding:'1px 8px', letterSpacing:'0.08em'}}
                  onClick={() => monacoEditorRef.current?.getAction('editor.action.formatDocument').run()}
                  title="Format JSON (Shift+Alt+F)"
                >
                  FORMAT
                </button>
              )}
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

window.ConfigEditor = ConfigEditor;
