

const { useState: useStateS, useMemo: useMemoS, useRef: useRefS, useEffect: useEffectS } = React;

function Sidebar({ active, onChange, instancesCount, sidebarStyle, codeStats, codeFolders, onSelectFolder, selectedCode, liveStatus, user, onLogout }) {
  const showLabels = sidebarStyle !== "icons";
  const items = [
  { id: "scheduler", label: "Scheduler", icon: I.Clock },
  { id: "instances", label: "Instances", icon: I.Server, count: instancesCount },
  { id: "refresher", label: "Refresher", icon: I.Refresh }];

  return (
    <aside style={{
      background: 'var(--panel)', borderRight: '1px solid var(--line)',
      display: 'flex', flexDirection: 'column', minHeight: 0
    }}>
      <div style={{ padding: showLabels ? '20px 18px 12px' : '20px 0 12px', display: 'flex', justifyContent: showLabels ? 'flex-start' : 'center', alignItems: 'center', gap: 10 }}>
        <span style={{ fontSize: 20, fontWeight: 700, letterSpacing: '-.015em' }}>{showLabels ? "Epic" : "E"}</span>
        {showLabels && (
          <span title={`server: ${liveStatus || 'unknown'}`} style={{ marginLeft: 'auto', display: 'inline-flex', alignItems: 'center', gap: 6 }}>
            <span className={"dot " + (liveStatus === 'open' ? 'ok' : liveStatus === 'connecting' ? 'warn' : 'bad')}
                  style={liveStatus === 'open' ? null : { animation: 'pulse-soft 1.8s ease-in-out infinite' }} />
            <span className="mono" style={{ fontSize: 11, color: 'var(--ink-4)' }}>{liveStatus || ''}</span>
          </span>
        )}
      </div>

      <nav style={{ flex: 1, padding: showLabels ? '8px 10px' : '8px 6px', display: 'flex', flexDirection: 'column', gap: 2, overflow: 'auto' }}>
        {items.map((it) => <SidebarItem key={it.id} active={active === it.id} onClick={() => onChange(it.id)} {...it} showLabels={showLabels} />)}
      </nav>

      {/* User footer */}
      <div style={{
        borderTop: '1px solid var(--line)',
        padding: showLabels ? '10px 14px' : '10px 0',
        display: 'flex', alignItems: 'center', justifyContent: showLabels ? 'space-between' : 'center', gap: 8,
      }}>
        {showLabels && user && (
          <span className="mono" style={{ fontSize: 11.5, color: 'var(--ink-4)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
            {user}
          </span>
        )}
        {onLogout && (
          <button className="icon-btn" style={{ width: 26, height: 26, flexShrink: 0 }} title="Sign out" onClick={onLogout}>
            <I.LogOut size={13} />
          </button>
        )}
      </div>
    </aside>);

}

function SidebarItem({ active, onClick, label, icon: Cmp, count, showLabels }) {
  return (
    <button onClick={onClick} className="sidebar-item" style={{
      display: 'flex', alignItems: 'center', gap: showLabels ? 12 : 0, justifyContent: showLabels ? 'flex-start' : 'center',
      padding: showLabels ? '9px 11px' : '11px 0', borderRadius: 6,
      background: active ? 'var(--card-hi)' : 'transparent',
      color: active ? 'var(--ink)' : 'var(--ink-3)',
      fontSize: 15, position: 'relative'
    }}
    onMouseEnter={(e) => {if (!active) {e.currentTarget.style.color = 'var(--ink)';e.currentTarget.style.background = 'var(--card)';}}}
    onMouseLeave={(e) => {if (!active) {e.currentTarget.style.color = 'var(--ink-3)';e.currentTarget.style.background = 'transparent';}}}
    title={!showLabels ? label : undefined}>
      {active && <span style={{ position: 'absolute', left: -10, top: 8, bottom: 8, width: 2, background: 'var(--accent)', borderRadius: 2 }}></span>}
      <Cmp size={16} />
      {showLabels && <span style={{ flex: 1, textAlign: 'left' }}>{label}</span>}
      {showLabels && count != null &&
      <span className="mono" style={{ fontSize: 12, color: 'var(--ink-4)' }}>{count}</span>
      }
    </button>);

}

function SidebarFolder({ folder, selected, onSelect }) {
  const colorForCode = useColorForCode();
  const c = colorForCode(folder.code);
  return (
    <div className="sidebar-item" style={{
      display: 'flex', alignItems: 'center', gap: 9,
      padding: '7px 11px 7px 12px', borderRadius: 6,
      background: selected ? 'var(--card-hi)' : 'transparent',
      position: 'relative', cursor: 'pointer'
    }}
    onMouseEnter={(e) => {if (!selected) e.currentTarget.style.background = 'var(--card)';}}
    onMouseLeave={(e) => {if (!selected) e.currentTarget.style.background = 'transparent';}}
    onClick={onSelect}>
      {selected && <span style={{ position: 'absolute', left: -10, top: 7, bottom: 7, width: 2, background: 'var(--accent)', borderRadius: 2 }}></span>}
      <I.Folder size={13} color={c.dot} />
      <span className="mono" style={{
        fontSize: 12.5, flex: 1, color: selected ? 'var(--ink)' : 'var(--ink-2)',
        overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap'
      }}>{folder.code}</span>
      <span className="mono" style={{ fontSize: 11.5, color: 'var(--ink-4)' }}>{folder.size}</span>
    </div>);

}

/* =========================================================================
   TOPBAR
========================================================================= */
function Topbar({ title, subtitle, search, setSearch, right, breadcrumbs }) {
  return (
    <header style={{ height: 68, padding: '0 22px', borderBottom: '1px solid var(--line)', display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexShrink: 0, gap: 18, background: 'var(--bg)' }}>
      <div style={{ display: 'flex', alignItems: 'baseline', gap: 13, minWidth: 0 }}>
        {breadcrumbs ?
        <div style={{ display: 'flex', alignItems: 'baseline', gap: 10 }}>
            {breadcrumbs.map((b, i) =>
          <React.Fragment key={i}>
                {i > 0 && <span style={{ color: 'var(--ink-5)', fontSize: 17 }}>/</span>}
                {b.onClick ?
            <button onClick={b.onClick} style={{ fontSize: i === breadcrumbs.length - 1 ? 19 : 16, fontWeight: i === breadcrumbs.length - 1 ? 600 : 400, color: i === breadcrumbs.length - 1 ? 'var(--ink)' : 'var(--ink-3)' }}>{b.label}</button> :

            <span style={{ fontSize: 19, fontWeight: 600, color: 'var(--ink)' }}>{b.label}</span>
            }
              </React.Fragment>
          )}
          </div> :

        <h1 style={{ margin: 0, fontSize: 19, fontWeight: 600 }}>{title}</h1>
        }
        {subtitle && <span className="mono" style={{ fontSize: 13, color: 'var(--ink-4)' }}>{subtitle}</span>}
      </div>
      <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
        {search != null && <SearchInput value={search} onChange={setSearch} placeholder="Filter…" width={340} />}
        {right}
      </div>
    </header>);

}

function InstancesScreen({
  instances, codes, codeFolders, density, tableZebra,
  selected, setSelected,    
  filterCode, setFilterCode,
  filterStatus, setFilterStatus,
  onLaunch, onStop, onReset, askConfirm
}) {
  const [search, setSearch] = useStateS("");
  const [activeId, setActiveId] = useStateS(null);

  
  const stats = useMemoS(() => {
    const s = { running: 0, booting: 0, idle: 0, stopped: 0, error: 0 };
    instances.forEach((i) => { s[i.status] = (s[i.status] || 0) + 1; });
    return s;
  }, [instances]);

  const filtered = useMemoS(() => {
    let out = instances;
    if (filterCode) out = out.filter((p) => p.code === filterCode);
    if (filterStatus) out = out.filter((p) => p.status === filterStatus);
    if (search.trim()) {
      const s = search.toLowerCase();
      out = out.filter((p) =>
      p.name.toLowerCase().includes(s) ||
      p.id.toLowerCase().includes(s) ||
      (p.code || "").toLowerCase().includes(s) ||
      p.account.toLowerCase().includes(s) ||
      (p.task || "").toLowerCase().includes(s)
      );
    }
    return out;
  }, [instances, search, filterCode, filterStatus]);

  const groups = useMemoS(() => {
    const m = new Map();
    filtered.forEach((i) => {
      const key = i.code || "__none__";
      if (!m.has(key)) m.set(key, []);
      m.get(key).push(i);
    });
    return [...m.entries()].map(([code, list]) => ({ code, list }));
  }, [filtered]);

  const [collapsed, setCollapsed] = useStateS(() => new Set());
  const toggleGroup = (code) => setCollapsed((prev) => {
    const n = new Set(prev);
    n.has(code) ? n.delete(code) : n.add(code);
    return n;
  });

  const visibleOrder = useMemoS(() => {
    const out = [];
    groups.forEach((g) => {if (!collapsed.has(g.code)) out.push(...g.list.map((i) => i.id));});
    return out;
  }, [groups, collapsed]);

  
  const onRowClick = (_e, id) => setActiveId(id);

  
  useEffectS(() => {
    const onKey = (e) => {
      const tag = (e.target.tagName || "").toLowerCase();
      if (tag === "input" || tag === "textarea") return;
      if (e.key === "Escape") setActiveId(null);
    };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, []);

  const active = activeId ? instances.find((p) => p.id === activeId) : null;
  const folderFilter = filterCode ? codeFolders.find((f) => f.code === filterCode) : null;

  const breadcrumbs = folderFilter ?
  [{ label: "Instances", onClick: () => setFilterCode(null) }, { label: folderFilter.code }] :
  [{ label: "Instances" }];

  return (
    <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minWidth: 0, minHeight: 0 }}>
      <Topbar
        breadcrumbs={breadcrumbs}
        subtitle={`${filtered.length} of ${instances.length}`}
        search={search} setSearch={setSearch}
        right={<FilterMenu filterStatus={filterStatus} setFilterStatus={setFilterStatus} />} />
      

      {filterStatus &&
      <div className="anim-fade-in" style={{ padding: '10px 22px', display: 'flex', gap: 9, alignItems: 'center', borderBottom: '1px dashed var(--line)' }}>
          <span className="section-eyebrow">filter</span>
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, padding: '3px 11px', background: 'var(--card)', border: '1px solid var(--line)', borderRadius: 5, fontSize: 13 }}>
            <span className={"dot " + STATUSES[filterStatus].dot}></span>
            <span className="mono">{STATUSES[filterStatus].label}</span>
            <button className="icon-btn" style={{ marginLeft: 2, width: 20, height: 20 }} onClick={() => setFilterStatus(null)}><I.X size={12} /></button>
          </span>
        </div>
      }

      {/* Статистика по статусам — наглядная сводка вместо multi-select toolbar */}
      <div className="anim-fade-in" style={{
        padding: '12px 22px', display: 'flex', alignItems: 'center', gap: 10,
        borderBottom: '1px solid var(--line)', background: 'var(--panel-2)', flexWrap: 'wrap',
      }}>
        {[
          { key: 'running', label: 'in game',  color: 'var(--ok)',    dot: '#5EE083' },
          { key: 'booting', label: 'starting', color: 'var(--warn)',  dot: '#D7B074' },
          { key: 'idle',    label: 'idle',     color: 'var(--ink-3)', dot: '#777'    },
          { key: 'stopped', label: 'stopped',  color: 'var(--ink-4)', dot: '#555'    },
          { key: 'error',   label: 'error',    color: 'var(--bad)',   dot: '#FF6B6B' },
        ].map(({ key, label, dot }) => {
          const v = stats[key] || 0;
          const dim = v === 0;
          return (
            <div key={key} title={label} style={{
              display: 'inline-flex', alignItems: 'center', gap: 7,
              padding: '4px 11px', borderRadius: 6,
              background: 'var(--card)',
              border: '1px solid ' + (dim ? 'var(--line)' : 'var(--line-strong)'),
              opacity: dim ? 0.55 : 1,
              transition: 'opacity var(--t) var(--ease), border-color var(--t) var(--ease)',
            }}>
              <span style={{ width: 6, height: 6, borderRadius: '50%', background: dot, flexShrink: 0 }}/>
              <span className="mono" style={{ fontSize: 12, color: 'var(--ink-3)' }}>
                <span style={{ color: dim ? 'var(--ink-5)' : 'var(--ink)', fontWeight: 600 }}>{v}</span>
                <span style={{ marginLeft: 5 }}>{label}</span>
              </span>
            </div>
          );
        })}
      </div>

      <div style={{ flex: 1, display: 'flex', minHeight: 0 }}>
        <div style={{ flex: 1, minWidth: 0, overflow: 'auto' }}>
          {filtered.length === 0 ?
          <EmptyState /> :

          <table style={{ width: '100%', borderCollapse: 'separate', borderSpacing: 0 }}>
              <thead>
                <tr style={{ position: 'sticky', top: 0, background: 'var(--bg)', zIndex: 3 }}>
                  <Th style={{ paddingLeft: 22 }}>Instance</Th>
                  <Th width={170}>Task</Th>
                  <Th width={130}>Status</Th>
                  <Th width={170}>Map (live)</Th>
                  <Th width={200} style={{ paddingRight: 22 }}>Account</Th>
                </tr>
              </thead>
              <tbody>
                {groups.map((g) =>
              <GroupBlock key={g.code}
              group={g}
              isCollapsed={collapsed.has(g.code)}
              onToggle={() => toggleGroup(g.code)}
              activeId={activeId}
              density={density}
              zebra={tableZebra}
              onRowClick={onRowClick} />
              )}
              </tbody>
            </table>
          }
        </div>

        {active &&
        <InstanceDetail
          instance={active}
          codes={codes}
          onClose={() => setActiveId(null)}
          onLaunch={onLaunch}
          onStop={onStop}
          onReset={onReset} />
        }
      </div>
    </div>);

}

function GroupBlock({ group, isCollapsed, onToggle, activeId, density, zebra, onRowClick }) {
  const list = group.list;
  const colorForCode = useColorForCode();
  const c = colorForCode(group.code);
  const counts = list.reduce((acc, i) => {acc[i.status] = (acc[i.status] || 0) + 1;return acc;}, {});

  return (
    <>
      <tr style={{ background: 'var(--panel)', borderTop: '1px solid var(--line)', borderBottom: '1px solid var(--line)' }}>
        <td colSpan={5} style={{ padding: 0 }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '9px 22px' }}>
            <button onClick={onToggle} className="icon-btn" style={{ width: 24, height: 24 }}>
              <I.ChevronDown size={13} style={{ transform: isCollapsed ? 'rotate(-90deg)' : 'rotate(0)', transition: 'transform var(--t) var(--ease)' }} />
            </button>
            <I.Folder size={14} color={c.dot} />
            <MapCode code={group.code} size="lg" />
            <span className="mono" style={{ fontSize: 12.5, color: 'var(--ink-4)' }}>{list.length} instance{list.length !== 1 ? 's' : ''}</span>
            <span style={{ display: 'flex', gap: 9, marginLeft: 6 }}>
              {counts.running > 0 && <span className="mono" style={{ fontSize: 12, color: 'var(--ok)' }}>● {counts.running}</span>}
              {counts.booting > 0 && <span className="mono" style={{ fontSize: 12, color: 'var(--warn)' }}>◐ {counts.booting}</span>}
              {counts.stopped > 0 && <span className="mono" style={{ fontSize: 12, color: 'var(--ink-4)' }}>○ {counts.stopped}</span>}
              {counts.error > 0 && <span className="mono" style={{ fontSize: 12, color: 'var(--bad)' }}>✕ {counts.error}</span>}
              {counts.idle > 0 && <span className="mono" style={{ fontSize: 12, color: 'var(--ink-3)' }}>○ {counts.idle}</span>}
            </span>
          </div>
        </td>
      </tr>
      {!isCollapsed && list.map((p, i) =>
      <InstanceRow key={p.id}
      instance={p}
      active={activeId === p.id}
      zebra={zebra && i % 2 === 1}
      density={density}
      onClick={(e) => onRowClick(e, p.id)} />
      )}
    </>);

}

function Th({ children, width, style }) {
  return (
    <th style={{
      textAlign: 'left', fontWeight: 500, fontSize: 11.5, color: 'var(--ink-4)',
      textTransform: 'uppercase', letterSpacing: '.08em',
      padding: '12px 12px', width, borderBottom: '1px solid var(--line)',
      whiteSpace: 'nowrap', userSelect: 'none',
      fontFamily: "IBM Plex Mono, SF Mono, Menlo, monospace",
      ...style
    }}>{children}</th>);

}

function TaskCell({ task, status }) {
  const inactive = status === "stopped" || status === "error" || !task || task === "—";
  const color = inactive ? 'var(--ink-5)' : 'var(--ink-2)';
  return (
    <span className="mono" style={{
      display: 'inline-flex', alignItems: 'center', gap: 7,
      fontSize: 12.5, color,
    }}>
      {!inactive && (status === "running" || status === "booting") &&
      <span style={{
        width: 5, height: 5, borderRadius: '50%',
        background: status === "booting" ? 'var(--warn)' : 'var(--accent)'
      }} className={status === "running" ? "pulse-soft" : ""} />
      }
      {task || "—"}
    </span>);

}

function InstanceRow({ instance, active, zebra, density, onClick }) {
  const padY = density === "compact" ? 8 : 12;
  const className = active ? "row-active anim-row" : "anim-row";
  const rowBg = active ? 'var(--card-hi)' : zebra ? 'var(--panel-2)' : 'transparent';

  return (
    <tr className={className} style={{
      background: rowBg,
      borderBottom: '1px solid var(--line-soft)',
      cursor: 'pointer'
    }}
    onClick={onClick}
    onMouseEnter={(e) => {if (!active) e.currentTarget.style.background = 'var(--panel-2)';}}
    onMouseLeave={(e) => {if (!active) e.currentTarget.style.background = zebra ? 'var(--panel-2)' : 'transparent';}}>
      <Td style={{ padding: `${padY}px 12px ${padY}px 22px` }}>
        <div style={{ minWidth: 0 }}>
          <div className="mono" style={{ fontSize: 13.5, color: 'var(--ink)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{instance.name}</div>
          <div className="mono" style={{ fontSize: 11.5, color: 'var(--ink-4)', marginTop: 2 }}>{instance.id}</div>
        </div>
      </Td>
      <Td style={{ padding: `${padY}px 12px` }}>
        <TaskCell task={instance.task} status={instance.status} />
      </Td>
      <Td style={{ padding: `${padY}px 12px` }}><StatusPill status={instance.status} /></Td>
      <Td style={{ padding: `${padY}px 12px` }}>
        {instance.code ?
        <MapCode code={instance.code} size="sm" /> :
        <span className="mono" style={{ fontSize: 12, color: 'var(--ink-5)' }}>—</span>}
      </Td>
      <Td style={{ padding: `${padY}px 22px ${padY}px 12px` }}>
        <span className="mono" style={{ fontSize: 13, color: 'var(--ink-2)' }}>{instance.account}</span>
      </Td>
    </tr>);

}
function Td({ children, style, stop }) {
  return <td style={{ verticalAlign: 'middle', ...style }} onClick={stop ? (e) => e.stopPropagation() : undefined}>{children}</td>;
}

function FilterMenu({ filterStatus, setFilterStatus }) {
  const [open, setOpen] = useStateS(false);
  const ref = useRefS(null);
  return (
    <>
      <button ref={ref} className="btn btn-sm" onClick={() => setOpen((o) => !o)}>
        <I.Filter size={13} /> Status {filterStatus && <span style={{ color: 'var(--accent)' }}>· 1</span>}
      </button>
      <Popover open={open} onClose={() => setOpen(false)} anchorRef={ref} width={230}>
        <div style={{ padding: 5 }}>
          {Object.entries(STATUSES).map(([id, s]) =>
          <div key={id} className="menu-item" onClick={() => {setFilterStatus(filterStatus === id ? null : id);setOpen(false);}} style={{
            padding: '7px 10px', borderRadius: 5, cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 10,
            background: filterStatus === id ? 'var(--panel-2)' : 'transparent',
            color: filterStatus === id ? 'var(--ink)' : 'var(--ink-2)', fontSize: 13.5
          }}
          onMouseEnter={(e) => e.currentTarget.style.background = 'var(--panel-2)'}
          onMouseLeave={(e) => {if (filterStatus !== id) e.currentTarget.style.background = 'transparent';}}>
              <span className={"dot " + s.dot}></span>
              <span className="mono">{s.label}</span>
              {filterStatus === id && <I.Check size={13} style={{ marginLeft: 'auto', color: 'var(--accent)' }} />}
            </div>
          )}
        </div>
      </Popover>
    </>);

}

function EmptyState() {
  return (
    <div className="anim-fade-in" style={{ height: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', flexDirection: 'column', gap: 16, padding: 42, color: 'var(--ink-3)' }}>
      <I.Server size={32} color="var(--ink-4)" />
      <div style={{ textAlign: 'center', maxWidth: 340 }}>
        <div style={{ fontSize: 15, color: 'var(--ink-2)' }}>No instances match the filter</div>
      </div>
    </div>);

}

/* =========================================================================
   MAP CODES — clean list view
========================================================================= */
function MapCodesScreen({ instances, codes, onCreateCode }) {
  const [search, setSearch] = useStateS("");
  const [createOpen, setCreateOpen] = useStateS(false);
  const colorForCode = useColorForCode();

  const counts = useMemoS(() => {
    const m = {};
    codes.forEach((c) => m[c] = 0);
    instances.forEach((i) => {if (m[i.code] != null) m[i.code]++;});
    return m;
  }, [instances, codes]);

  const filtered = codes.filter((c) => !search || c.toLowerCase().includes(search.toLowerCase()));
  const total = instances.length;

  return (
    <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minWidth: 0 }}>
      <Topbar title="Map codes" subtitle={`${codes.length} registered`} search={search} setSearch={setSearch}
      right={<button className="btn btn-sm btn-primary" onClick={() => setCreateOpen(true)}><I.Plus size={12} /> New code</button>} />
      <div style={{ flex: 1, overflow: 'auto', padding: '24px 28px' }}>
        <div style={{ maxWidth: 760 }}>
          <div style={{ background: 'var(--card)', border: '1px solid var(--line)', borderRadius: 10, overflow: 'hidden' }}>
            {filtered.map((c, i) => {
              const color = colorForCode(c);
              const count = counts[c];
              const pct = total ? count / total * 100 : 0;
              return (
                <div key={c} className="anim-fade-in" style={{
                  display: 'grid', gridTemplateColumns: 'auto 1fr auto auto', alignItems: 'center', gap: 18,
                  padding: '14px 18px',
                  borderTop: i === 0 ? 'none' : '1px solid var(--line-soft)',
                  transition: 'background var(--t) var(--ease)'
                }}
                onMouseEnter={(e) => e.currentTarget.style.background = 'var(--card-hi)'}
                onMouseLeave={(e) => e.currentTarget.style.background = 'transparent'}>
                  <span style={{ width: 10, height: 10, borderRadius: 3, background: color.dot, flexShrink: 0 }}></span>
                  <div style={{ display: 'flex', alignItems: 'baseline', gap: 10, minWidth: 0 }}>
                    <span className="mono" style={{ fontSize: 14, color: 'var(--ink)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{c}</span>
                    <span className="mono" style={{ fontSize: 12, color: 'var(--ink-5)' }}>{color.name}</span>
                  </div>
                  <div style={{ width: 110, height: 4, background: 'var(--panel-2)', borderRadius: 2, overflow: 'hidden' }}>
                    <div style={{ width: `${pct}%`, height: '100%', background: color.dot, transition: 'width var(--t-slow) var(--ease)' }} />
                  </div>
                  <span className="mono" style={{ fontSize: 14, color: count > 0 ? 'var(--ink)' : 'var(--ink-5)', minWidth: 36, textAlign: 'right' }}>{count}</span>
                </div>);

            })}
            {filtered.length === 0 &&
            <div style={{ padding: '18px', color: 'var(--ink-5)', fontSize: 13.5 }}>No codes match the filter</div>
            }
          </div>
        </div>
      </div>
      {createOpen && <CreateCodeModal codes={codes} onClose={() => setCreateOpen(false)} onCreate={onCreateCode} />}
    </div>);

}

// Fortnite map code: ровно 12 цифр через дефисы 0000-0000-0000.
const MAP_CODE_RE_S = /^\d{4}-\d{4}-\d{4}$/;
function formatMapCodeS(raw) {
  const d = (raw || "").replace(/\D/g, "").slice(0, 12);
  const parts = [];
  if (d.length > 0) parts.push(d.slice(0, 4));
  if (d.length > 4) parts.push(d.slice(4, 8));
  if (d.length > 8) parts.push(d.slice(8, 12));
  return parts.join("-");
}

function CreateCodeModal({ codes, onClose, onCreate }) {
  const [name, setName] = useStateS("");
  const trimmed = name.trim();
  const exists = codes.includes(trimmed);
  const valid = MAP_CODE_RE_S.test(trimmed) && !exists;
  const previewColor = CODE_PALETTE[codes.length % CODE_PALETTE.length];

  const submit = () => {if (valid) {onCreate(trimmed);onClose();}};

  return (
    <>
      <div className="scrim" onClick={onClose}></div>
      <div className="anim-modal" style={{
        position: 'fixed', left: '50%', top: '50%', transform: 'translate(-50%, -50%)',
        width: 460, maxWidth: '92vw',
        background: 'var(--panel)', border: '1px solid var(--line-strong)', borderRadius: 10, zIndex: 95, overflow: 'hidden',
        boxShadow: '0 16px 48px rgba(0,0,0,.12)'
      }}>
        <div style={{ padding: '18px 20px 14px', borderBottom: '1px solid var(--line)' }}>
          <h3 style={{ margin: 0, fontSize: 16, fontWeight: 600 }}>New map code</h3>
          <p style={{ margin: '6px 0 0', fontSize: 13, color: 'var(--ink-4)' }}>
            Fortnite map code in format 0000-0000-0000 (12 digits). Example: 9695-5615-4966
          </p>
        </div>
        <div style={{ padding: '20px' }}>
          <div className="input" style={{
            border: '1px solid ' + (trimmed && !valid ? 'var(--bad)' : 'var(--line-strong)'),
            transition: 'border-color var(--t) var(--ease)',
          }}>
            <I.Hash size={14} color="var(--ink-4)" />
            <input value={name}
              onChange={(e) => setName(formatMapCodeS(e.target.value))}
              onKeyDown={(e) => {if (e.key === 'Enter') submit();}}
              placeholder="0000-0000-0000"
              inputMode="numeric"
              autoFocus className="mono" />
          </div>
          {trimmed && !valid &&
          <div className="mono anim-fade-in" style={{ fontSize: 12.5, color: exists ? 'var(--bad)' : 'var(--warn)', marginTop: 10 }}>
              {exists ? `Code "${trimmed}" already exists` : 'Format: 0000-0000-0000'}
            </div>
          }
          {valid &&
          <div className="anim-fade-in" style={{ display: 'flex', alignItems: 'center', gap: 11, marginTop: 16 }}>
              <span className="section-eyebrow" style={{ fontSize: 10 }}>preview</span>
              <span className="code code-brackets" style={{
              color: previewColor.text, background: previewColor.bg, borderColor: previewColor.border,
              fontSize: 14
            }}>{trimmed}</span>
              <span className="mono" style={{ fontSize: 12, color: 'var(--ink-4)' }}>· {previewColor.name}</span>
            </div>
          }
        </div>
        <div style={{ padding: '13px 20px', background: 'var(--panel-2)', display: 'flex', justifyContent: 'flex-end', gap: 9 }}>
          <button className="btn btn-sm" onClick={onClose}>Cancel</button>
          <button className="btn btn-sm btn-primary" disabled={!valid} onClick={submit}>Create</button>
        </div>
      </div>
    </>);

}

function StatsScreen({ instances, codes, codeFolders, tzSync, onTzSyncChange }) {
  const stats = useMemoS(() => {
    const out = { total: instances.length, running: 0, idle: 0, booting: 0, stopped: 0, error: 0 };
    instances.forEach((i) => out[i.status] = (out[i.status] || 0) + 1);
    return out;
  }, [instances]);
  const colorForCode = useColorForCode();

  const segs = [
  { k: 'running', l: 'Running', col: 'var(--ok)' },
  { k: 'booting', l: 'Booting', col: 'var(--warn)' },
  { k: 'idle', l: 'Idle', col: 'var(--ink-3)' },
  { k: 'stopped', l: 'Stopped', col: 'var(--ink-5)' },
  { k: 'error', l: 'Error', col: 'var(--bad)' }];

  return (
    <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minWidth: 0 }}>
      <Topbar title="Stats" />
      <div style={{ flex: 1, overflow: 'auto', padding: '28px 32px' }}>
        <div style={{ maxWidth: 720, display: 'flex', flexDirection: 'column', gap: 32 }}>

          {}
          <section className="anim-fade-in">
            <div className="mono" style={{ fontSize: 13, color: 'var(--ink-4)', letterSpacing: '.03em' }}>Total instances</div>
            <div className="mono" style={{ fontSize: 52, fontWeight: 600, color: 'var(--ink)', lineHeight: 1.1, letterSpacing: '-.02em', marginTop: 6 }}>{stats.total}</div>
            <div style={{ display: 'flex', height: 6, borderRadius: 3, overflow: 'hidden', background: 'var(--panel-2)', marginTop: 14 }}>
              {segs.map((s) => {
                const v = stats[s.k] || 0;
                const pct = stats.total ? v / stats.total * 100 : 0;
                return <div key={s.k} style={{ width: `${pct}%`, background: s.col, transition: 'width var(--t-slow) var(--ease)' }} />;
              })}
            </div>
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 18, marginTop: 14 }}>
              {segs.map((s) =>
              <div key={s.k} style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                  <span style={{ width: 7, height: 7, borderRadius: 2, background: s.col }}></span>
                  <span style={{ fontSize: 13, color: 'var(--ink-3)' }}>{s.l}</span>
                  <span className="mono" style={{ fontSize: 13, color: 'var(--ink)' }}>{stats[s.k] || 0}</span>
                </div>
              )}
            </div>
          </section>

          {/* By folder — clean list */}
          <section className="anim-fade-in">
            <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 10 }}>
              <h3 style={{ margin: 0, fontSize: 14, fontWeight: 600, color: 'var(--ink)' }}>By folder</h3>
              <span className="mono" style={{ fontSize: 12, color: 'var(--ink-4)' }}>{codeFolders.length} active</span>
            </div>
            <div style={{ background: 'var(--card)', border: '1px solid var(--line)', overflow: 'hidden', backgroundColor: "rgb(26, 26, 28)", borderColor: "rgb(42, 42, 45)", borderRadius: "10px", borderStyle: "solid" }}>
              {codeFolders.length === 0 &&
              <div style={{ padding: '18px', color: 'var(--ink-5)', fontSize: 13.5 }}>No folders in use</div>
              }
              {codeFolders.map((f, i) => {
                const folderInstances = instances.filter((x) => x.code === f.code);
                const fStats = folderInstances.reduce((acc, x) => {acc[x.status] = (acc[x.status] || 0) + 1;return acc;}, {});
                const col = colorForCode(f.code);
                const pct = stats.total ? folderInstances.length / stats.total * 100 : 0;
                return (
                  <div key={f.code} style={{
                    display: 'grid', gridTemplateColumns: 'auto 1fr auto auto',
                    alignItems: 'center', gap: 16, padding: '14px 18px',
                    borderTop: i === 0 ? 'none' : '1px solid var(--line-soft)'
                  }}>
                    <span style={{ width: 9, height: 9, borderRadius: 2, background: col.dot }}></span>
                    <div style={{ display: 'flex', alignItems: 'baseline', gap: 12, minWidth: 0 }}>
                      <span className="mono" style={{ fontSize: 13.5, color: 'var(--ink)' }}>{f.code}</span>
                      <div style={{ display: 'flex', gap: 10, fontSize: 12, color: 'var(--ink-4)' }} className="mono">
                        {fStats.running > 0 && <span style={{ color: 'var(--ok)' }}>{fStats.running} live</span>}
                        {fStats.error > 0 && <span style={{ color: 'var(--bad)' }}>{fStats.error} err</span>}
                        {!fStats.running && !fStats.error && <span>inactive</span>}
                      </div>
                    </div>
                    <div style={{ width: 80, height: 3, background: 'var(--panel-2)', borderRadius: 2, overflow: 'hidden' }}>
                      <div style={{ width: `${pct}%`, height: '100%', background: col.dot }} />
                    </div>
                    <span className="mono" style={{ fontSize: 14, color: 'var(--ink)', minWidth: 30, textAlign: 'right' }}>{folderInstances.length}</span>
                  </div>);

              })}
            </div>
          </section>

          {/* Global controls */}
          <section className="anim-fade-in">
            <h3 style={{ margin: '0 0 10px', fontSize: 14, fontWeight: 600, color: 'var(--ink)' }}>Global</h3>
            <div style={{ background: 'var(--card)', border: '1px solid var(--line)', borderRadius: 10 }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 18, padding: '16px 18px' }}>
                <div style={{ flex: 1 }}>
                  <div style={{ fontSize: 14, color: 'var(--ink)' }}>Timezone sync</div>
                </div>
                <Switch checked={tzSync} onChange={onTzSyncChange} />
              </div>
            </div>
          </section>

        </div>
      </div>
    </div>);

}

function Switch({ checked, onChange }) {
  return (
    <button onClick={() => onChange(!checked)} style={{
      width: 42, height: 24, borderRadius: 13,
      background: checked ? 'var(--accent)' : 'var(--panel-2)',
      border: '1px solid ' + (checked ? 'var(--accent)' : 'var(--line-strong)'),
      position: 'relative', cursor: 'pointer',
      transition: 'background var(--t) var(--ease), border-color var(--t) var(--ease)'
    }}>
      <span style={{
        position: 'absolute', top: 2, left: checked ? 20 : 2,
        width: 18, height: 18, borderRadius: '50%',
        background: '#fff',
        transition: 'left var(--t) var(--ease)'
      }} />
    </button>);

}

/* =========================================================================
   REFRESHER SCREEN — управление аккаунтами Epic: куки, рефреш, статус
========================================================================= */
function RefresherScreen() {
  const PAGE_SIZE = 100;
  const [accounts, setAccounts] = useStateS([]);
  const [total, setTotal] = useStateS(0);
  const [search, setSearch] = useStateS("");
  const [loading, setLoading] = useStateS(true);
  const [hasMore, setHasMore] = useStateS(true);
  const [refreshing, setRefreshing] = useStateS(new Set());
  const [detail, setDetail] = useStateS(null); // account_id для детальной панели
  const [workerOnline, setWorkerOnline] = useStateS(null); // null=checking, true=ok, false=err
  const scrollRef = useRefS(null);
  const loadingRef = useRefS(false);

  const load = (append = false) => {
    if (loadingRef.current) return;
    loadingRef.current = true;
    setLoading(true);
    const offset = append ? accounts.length : 0;
    fetch(`/api/accounts?limit=${PAGE_SIZE}&offset=${offset}`)
      .then((r) => { setWorkerOnline(r.ok); return r.json(); })
      .then((d) => {
        const next = d.accounts || [];
        const nextTotal = d.total || 0;
        setAccounts((prev) => {
          if (!append) return next;
          const merged = new Map(prev.map((a) => [a.account_id, a]));
          next.forEach((a) => merged.set(a.account_id, a));
          return Array.from(merged.values());
        });
        setTotal(nextTotal);
        const loaded = append ? accounts.length + next.length : next.length;
        setHasMore(next.length > 0 && loaded < nextTotal);
      })
      .catch(() => { setWorkerOnline(false); })
      .finally(() => {
        loadingRef.current = false;
        setLoading(false);
      });
  };

  useEffectS(() => { load(); }, []);

  useEffectS(() => {
    const el = scrollRef.current;
    if (!el || loading || !hasMore) return;
    if (el.scrollHeight <= el.clientHeight + 40) load(true);
  }, [accounts.length, loading, hasMore]);

  const handleAccountsScroll = (e) => {
    const el = e.currentTarget;
    if (loading || !hasMore) return;
    if (el.scrollHeight - el.scrollTop - el.clientHeight < 180) load(true);
  };

  const filtered = useMemoS(() => {
    if (!search.trim()) return accounts;
    const s = search.toLowerCase();
    return accounts.filter((a) =>
      a.email.toLowerCase().includes(s) ||
      (a.display_name || "").toLowerCase().includes(s) ||
      a.account_id.toLowerCase().includes(s)
    );
  }, [accounts, search]);

  const handleRefresh = async (account_id) => {
    setRefreshing((prev) => new Set([...prev, account_id]));
    try {
      const r = await fetch("/api/refresh", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ account_id }),
      });
      const d = await r.json();
      if (!d.ok) console.warn("refresh failed:", d.message);
    } catch (e) {
      console.error(e);
    } finally {
      setRefreshing((prev) => { const n = new Set(prev); n.delete(account_id); return n; });
      load();
    }
  };

  const handleRefreshAll = async () => {
    await fetch("/api/refresh/all", { method: "POST" });
    setTimeout(load, 3000);
  };

  const [codeModal, setCodeModal] = useStateS(null); // {email, code, error}

  const handleMailCode = async (account_id, email) => {
    try {
      const r = await fetch(`/api/accounts/${account_id}/code`, { method: "POST" });
      const d = await r.json();
      if (d.code) {
        await navigator.clipboard.writeText(d.code).catch(() => {});
        setCodeModal({ email, code: d.code, error: null });
      } else {
        setCodeModal({ email, code: null, error: d.info || d.detail || 'unknown' });
      }
    } catch (e) {
      setCodeModal({ email, code: null, error: e.message });
    }
  };

  const stats = useMemoS(() => {
    const ok = accounts.filter((a) => a.refresh_status === "ok").length;
    const err = accounts.filter((a) => a.refresh_status === "error").length;
    const pending = accounts.filter((a) => a.refresh_status === "pending" || !a.refresh_status).length;
    return { ok, err, pending };
  }, [accounts]);

  return (
    <div style={{ display: 'flex', flexDirection: 'column', flex: 1, minWidth: 0, minHeight: 0 }}>
      <Topbar
        title="Refresher"
        subtitle={`${total} accounts`}
        search={search}
        setSearch={setSearch}
        right={
          <div style={{ display: 'flex', gap: 8 }}>
            <button className="btn btn-sm" onClick={() => load(false)}><I.Refresh size={13} /> Reload</button>
            <button className="btn btn-sm btn-primary" onClick={handleRefreshAll}>
              <I.Refresh size={13} /> Refresh all
            </button>
          </div>
        }
      />

      {/* Статистика */}
      <div style={{ padding: '12px 28px', borderBottom: '1px solid var(--line)', display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap' }}>
        <StatPill label="OK" value={stats.ok} color="var(--ok)" bg="color-mix(in oklab, var(--ok) 12%, transparent)" border="color-mix(in oklab, var(--ok) 28%, transparent)" />
        <StatPill label="Error" value={stats.err} color="var(--bad)" bg="color-mix(in oklab, var(--bad) 12%, transparent)" border="color-mix(in oklab, var(--bad) 28%, transparent)" />
        <StatPill label="Pending" value={stats.pending} color="var(--ink-3)" bg="var(--panel-2)" border="var(--line)" />
        <StatPill label="Total" value={total} color="var(--ink)" bg="var(--panel-2)" border="var(--line)" />
        <div style={{ marginLeft: 'auto', display: 'flex', alignItems: 'center', gap: 7 }}>
          <span style={{
            width: 7, height: 7, borderRadius: '50%', flexShrink: 0,
            background: workerOnline === null ? 'var(--ink-4)' : workerOnline ? 'var(--ok)' : 'var(--bad)',
            animation: workerOnline ? 'pulse-soft 2.4s ease-in-out infinite' : workerOnline === null ? 'pulse-soft 1.2s ease-in-out infinite' : 'none',
          }} />
          <span className="mono" style={{ fontSize: 12, color: workerOnline === null ? 'var(--ink-4)' : workerOnline ? 'var(--ok)' : 'var(--bad)' }}>
            {workerOnline === null ? '...' : workerOnline ? 'online' : 'offline'}
          </span>
        </div>
      </div>

      <div style={{ flex: 1, display: 'flex', minHeight: 0, minWidth: 0 }}>
        <div
          ref={scrollRef}
          onScroll={handleAccountsScroll}
          style={{ flex: 1, overflowY: 'auto', overflowX: 'auto', minHeight: 0, minWidth: 0 }}
        >
          {loading && accounts.length === 0 ? (
            <div style={{ padding: 48, textAlign: 'center', color: 'var(--ink-4)' }}>Loading…</div>
          ) : filtered.length === 0 ? (
            <div style={{ padding: 48, textAlign: 'center', color: 'var(--ink-4)' }}>
              {accounts.length === 0 ? "No accounts. Register accounts first." : "No accounts match the filter."}
            </div>
          ) : (
            <table style={{ width: '100%', borderCollapse: 'separate', borderSpacing: 0 }}>
              <thead>
                <tr style={{ position: 'sticky', top: 0, background: 'var(--bg)', zIndex: 3 }}>
                  <Th style={{ paddingLeft: 28 }}>Account</Th>
                  <Th width={160}>Session expires</Th>
                  <Th width={160}>Last refresh</Th>
                  <Th width={110}>Cookies</Th>
                  <Th width={110}>Status</Th>
                  <Th width={110}>Device</Th>
                  <Th width={130} style={{ paddingRight: 24 }}></Th>
                </tr>
              </thead>
              <tbody>
                {filtered.map((a) => (
                  <AccountRefresherRow
                    key={a.account_id}
                    account={a}
                    isRefreshing={refreshing.has(a.account_id)}
                    onRefresh={() => handleRefresh(a.account_id)}
                    onMailCode={() => handleMailCode(a.account_id, a.email)}
                    onDetail={() => setDetail(detail === a.account_id ? null : a.account_id)}
                    isDetailed={detail === a.account_id}
                  />
                ))}
              </tbody>
            </table>
          )}
          {loading && accounts.length > 0 && (
            <div className="mono" style={{ padding: '12px 28px', color: 'var(--ink-4)', fontSize: 12 }}>
              Loading more...
            </div>
          )}
        </div>

        {detail && (
          <AccountDetailPanel
            accountId={detail}
            account={accounts.find((a) => a.account_id === detail)}
            onClose={() => setDetail(null)}
          />
        )}

        {codeModal && (
          <CodeModal
            data={codeModal}
            onClose={() => setCodeModal(null)}
          />
        )}
      </div>
    </div>
  );
}

function StatBadge({ label, value, color }) {
  return (
    <div style={{ display: 'flex', alignItems: 'baseline', gap: 7 }}>
      <span className="mono" style={{ fontSize: 22, fontWeight: 600, color }}>{value}</span>
      <span style={{ fontSize: 12, color: 'var(--ink-4)' }}>{label}</span>
    </div>
  );
}

function StatPill({ label, value, color, bg, border }) {
  return (
    <div style={{
      display: 'inline-flex', alignItems: 'center', gap: 7,
      padding: '5px 12px', borderRadius: 6,
      background: bg, border: '1px solid ' + border,
    }}>
      <span style={{ width: 6, height: 6, borderRadius: '50%', background: color, flexShrink: 0 }} />
      <span className="mono" style={{ fontSize: 12, color: 'var(--ink-2)' }}>
        <span style={{ color, fontWeight: 600 }}>{value}</span>
        <span style={{ color: 'var(--ink-4)', marginLeft: 5 }}>{label}</span>
      </span>
    </div>
  );
}

function fmtTs(ts) {
  if (!ts) return "—";
  const d = new Date(ts * 1000);
  const now = Date.now();
  const diff = (d.getTime() - now) / 1000;
  // для expires — показываем "через N дней" если в будущем
  if (diff > 0) {
    const days = Math.floor(diff / 86400);
    if (days > 0) return `${days}d left`;
    const h = Math.floor(diff / 3600);
    return `${h}h left`;
  }
  // в прошлом
  const ago = -diff;
  if (ago < 60) return "just now";
  if (ago < 3600) return `${Math.floor(ago / 60)}m ago`;
  if (ago < 86400) return `${Math.floor(ago / 3600)}h ago`;
  const days = Math.floor(ago / 86400);
  return `${days}d ago`;
}

function fmtDate(ts) {
  if (!ts) return "—";
  const d = new Date(ts * 1000);
  return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`;
}

function AccountRefresherRow({ account: a, isRefreshing, onRefresh, onMailCode, onDetail, isDetailed }) {
  const refreshColor = {
    ok:      'var(--ok)',
    error:   'var(--bad)',
    pending: 'var(--ink-4)',
  }[a.refresh_status] || 'var(--ink-4)';

  const deviceColor = {
    ok:      'var(--ok)',
    dead:    'var(--bad)',
    unknown: 'var(--ink-4)',
  }[a.device_auth_status] || 'var(--ink-4)';

  const sessionExpires = a.session_ap_expires;
  const sessionColor = !sessionExpires ? 'var(--ink-5)'
    : sessionExpires * 1000 < Date.now() ? 'var(--bad)'
    : sessionExpires * 1000 < Date.now() + 30 * 86400 * 1000 ? 'var(--warn)'
    : 'var(--ok)';

  return (
    <tr
      className="anim-row"
      style={{ borderBottom: '1px solid var(--line-soft)', background: isDetailed ? 'var(--card-hi)' : 'transparent', cursor: 'pointer' }}
      onMouseEnter={(e) => { if (!isDetailed) e.currentTarget.style.background = 'var(--panel-2)'; }}
      onMouseLeave={(e) => { if (!isDetailed) e.currentTarget.style.background = 'transparent'; }}
      onClick={onDetail}
    >
      <Td style={{ padding: '13px 12px 13px 28px' }}>
        <div>
          <div className="mono" style={{ fontSize: 13.5, color: 'var(--ink)' }}>{a.email}</div>
          <div className="mono" style={{ fontSize: 11.5, color: 'var(--ink-4)', marginTop: 2 }}>
            {a.display_name || a.account_id.slice(0, 16) + '…'}
          </div>
        </div>
      </Td>
      <Td style={{ padding: '13px 12px' }}>
        <span className="mono" style={{ fontSize: 13, color: sessionColor }}>
          {sessionExpires ? fmtTs(sessionExpires) : '—'}
        </span>
        {sessionExpires && (
          <div className="mono" style={{ fontSize: 11, color: 'var(--ink-5)', marginTop: 1 }}>
            {fmtDate(sessionExpires)}
          </div>
        )}
      </Td>
      <Td style={{ padding: '13px 12px' }}>
        <span className="mono" style={{ fontSize: 13, color: 'var(--ink-3)' }}>
          {a.last_refresh ? fmtTs(a.last_refresh) : 'never'}
        </span>
      </Td>
      <Td style={{ padding: '13px 12px' }}>
        <span className="mono" style={{ fontSize: 13, color: 'var(--ink-2)' }}>
          {a.cookies_count ?? 0}
        </span>
      </Td>
      <Td style={{ padding: '13px 12px' }}>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12.5 }}>
          <span style={{ width: 7, height: 7, borderRadius: '50%', background: refreshColor, flexShrink: 0 }} />
          <span className="mono" style={{ color: refreshColor }}>{a.refresh_status || 'pending'}</span>
        </span>
      </Td>
      <Td style={{ padding: '13px 12px' }}>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12.5 }}>
          <span style={{ width: 7, height: 7, borderRadius: '50%', background: deviceColor, flexShrink: 0 }} />
          <span className="mono" style={{ color: deviceColor }}>{a.device_auth_status || 'unknown'}</span>
        </span>
      </Td>
      <Td style={{ padding: '13px 24px 13px 12px', textAlign: 'right' }} stop>
        <div style={{ display: 'inline-flex', gap: 6 }}>
          <button
            className="btn btn-sm"
            onClick={(e) => { e.stopPropagation(); onMailCode && onMailCode(); }}
            title="Получить TOTP-код аутентификатора"
          >
            <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
              <path d="m9 12 2 2 4-4"/>
            </svg>
            Code
          </button>
          <button
            className="btn btn-sm"
            disabled={isRefreshing}
            onClick={(e) => { e.stopPropagation(); onRefresh(); }}
            style={{ opacity: isRefreshing ? 0.6 : 1 }}
          >
            <I.Refresh size={12} style={{ animation: isRefreshing ? 'spin 1s linear infinite' : 'none' }} />
            {isRefreshing ? 'Refreshing…' : 'Refresh'}
          </button>
        </div>
      </Td>
    </tr>
  );
}

function CodeModal({ data, onClose }) {
  const { email, code, error } = data;
  return (
    <div
      onClick={onClose}
      style={{
        position: 'fixed', inset: 0, background: 'rgba(0,0,0,.25)',
        display: 'flex', alignItems: 'center', justifyContent: 'center',
        zIndex: 1000, animation: 'fadeIn .12s ease',
      }}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        style={{
          width: 360, background: 'var(--card)',
          border: '1px solid var(--line)', borderRadius: 12,
          padding: 24, boxShadow: '0 12px 40px rgba(0,0,0,.12)',
        }}
      >
        <div style={{ fontSize: 12, color: 'var(--ink-4)', marginBottom: 4 }}>
          {email}
        </div>
        {code ? (
          <>
            <div className="mono" style={{
              fontSize: 36, fontWeight: 500, color: 'var(--ink)',
              letterSpacing: '.12em', marginBottom: 6,
              fontVariantNumeric: 'tabular-nums',
            }}>
              {code}
            </div>
            <div style={{ fontSize: 12, color: 'var(--ink-4)', marginBottom: 20 }}>
              Copied to clipboard
            </div>
          </>
        ) : (
          <>
            <div style={{
              fontSize: 14, color: 'var(--bad)', marginTop: 8, marginBottom: 20,
            }}>
              {error || 'no code'}
            </div>
          </>
        )}
        <button
          className="btn btn-sm"
          style={{ width: '100%' }}
          onClick={onClose}
        >
          Close
        </button>
      </div>
    </div>
  );
}

function AccountDetailPanel({ accountId, account: initialAccount, onClose }) {
  const [account, setAccount] = useStateS(initialAccount || null);
  const [logs, setLogs] = useStateS([]);
  const [exchangeCode, setExchangeCode] = useStateS(null);
  const [loadingExchange, setLoadingExchange] = useStateS(false);
  const [copied, setCopied] = useStateS(null);
  const [tab, setTab] = useStateS('info');

  useEffectS(() => {
    setAccount(initialAccount || null);
    fetch(`/api/accounts/${accountId}/refresh-log?limit=10`)
      .then((r) => r.json())
      .then((d) => setLogs(d.logs || []));
  }, [accountId, initialAccount]);

  const copyToClipboard = (key, text) => {
    if (!text || text === "—") return;
    navigator.clipboard.writeText(text).then(() => {
      setCopied(key);
      setTimeout(() => setCopied(null), 1500);
    });
  };

  const getExchange = async () => {
    setLoadingExchange(true);
    setExchangeCode(null);
    try {
      const r = await fetch("/api/exchange", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ account_id: accountId }),
      });
      const d = await r.json();
      if (d.exchange_code) setExchangeCode(d.exchange_code);
      else setExchangeCode("ERROR: " + (d.detail || "unknown"));
    } catch (e) {
      setExchangeCode("ERROR: " + e.message);
    } finally {
      setLoadingExchange(false);
    }
  };

  return (
    <div style={{
      width: 400, borderLeft: '1px solid var(--line)', background: 'var(--panel)',
      display: 'flex', flexDirection: 'column', overflow: 'hidden', flexShrink: 0,
    }}>
      {/* Header */}
      <div style={{ padding: '14px 16px', borderBottom: '1px solid var(--line)', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
        <div>
          <div style={{ fontSize: 14, fontWeight: 600, color: 'var(--ink)' }}>Account detail</div>
          {account && <div className="mono" style={{ fontSize: 11, color: 'var(--ink-5)', marginTop: 1 }}>{account.account_id.slice(0, 16)}…</div>}
        </div>
        <button className="icon-btn" onClick={onClose}><I.X size={14} /></button>
      </div>

      {/* Tabs */}
      {account && (
        <div style={{ display: 'flex', gap: 5, padding: '10px 12px', borderBottom: '1px solid var(--line)', flexShrink: 0 }}>
          {['info', 'cookies', 'log'].map((t) => {
            const on = tab === t;
            return (
              <button key={t} onClick={() => setTab(t)} className="mono" style={{
                flex: 1, padding: '5px 0', fontSize: 12, fontWeight: 500, borderRadius: 7,
                background: on ? 'var(--accent)' : 'var(--card)',
                color: on ? '#fff' : 'var(--ink-3)',
                border: '1px solid ' + (on ? 'var(--accent)' : 'var(--line-strong)'),
                transition: 'background var(--t) var(--ease), color var(--t) var(--ease)',
              }}>{t}</button>
            );
          })}
        </div>
      )}

      <div style={{ flex: 1, overflow: 'auto', padding: '14px 16px' }}>
        {!account ? (
          <div style={{ color: 'var(--ink-4)', fontSize: 13, padding: '20px 0' }}>Loading…</div>
        ) : tab === 'info' ? (
          <>
            {/* Credentials */}
            <div style={{ marginBottom: 4 }}>
              <div className="section-eyebrow" style={{ marginBottom: 8 }}>epic account</div>
              <div style={{ background: 'var(--card)', border: '1px solid var(--line)', borderRadius: 8, overflow: 'hidden' }}>
                <CopyField label="Email" value={account.email} mono copyKey="email" copied={copied} onCopy={copyToClipboard} />
                <CopyField label="Password" value={account.password} mono secret copyKey="epic_pass" copied={copied} onCopy={copyToClipboard} />
                <CopyField label="Display name" value={account.display_name || "—"} copyKey="display_name" copied={copied} onCopy={copyToClipboard} />
                <CopyField label="Account ID" value={account.account_id} mono small copyKey="account_id" copied={copied} onCopy={copyToClipboard} />
              </div>
            </div>

            {/* Email account */}
            <div style={{ marginTop: 14 }}>
              <div className="section-eyebrow" style={{ marginBottom: 8 }}>email account</div>
              <div style={{ background: 'var(--card)', border: '1px solid var(--line)', borderRadius: 8, overflow: 'hidden' }}>
                <CopyField label="Email" value={account.email} mono copyKey="email2" copied={copied} onCopy={copyToClipboard} />
                <CopyField label="Password" value={account.email_password || "—"} mono secret copyKey="email_pass" copied={copied} onCopy={copyToClipboard} />
              </div>
            </div>

            {/* Session */}
            <div style={{ marginTop: 14 }}>
              <div className="section-eyebrow" style={{ marginBottom: 8 }}>session</div>
              <div style={{ background: 'var(--card)', border: '1px solid var(--line)', borderRadius: 8, overflow: 'hidden' }}>
                <CopyField label="Status" value={account.status} noCopy
                  badge={account.status === 'active' ? 'ok' : 'bad'} />
                <CopyField label="Cookies" value={`${account.cookies_count}`} noCopy />
                <CopyField label="Session expires"
                  value={account.session_ap_expires ? fmtDate(account.session_ap_expires) + " · " + fmtTs(account.session_ap_expires) : "—"}
                  copyKey="session_exp" copied={copied} onCopy={copyToClipboard}
                  color={!account.session_ap_expires ? 'var(--ink-5)'
                    : account.session_ap_expires * 1000 < Date.now() ? 'var(--bad)'
                    : account.session_ap_expires * 1000 < Date.now() + 30 * 86400 * 1000 ? 'var(--warn)'
                    : 'var(--ok)'} />
                <CopyField label="Last refresh" value={account.last_refresh ? fmtTs(account.last_refresh) : "never"} noCopy />
                <CopyField label="Refresh" value={account.refresh_status || "pending"} noCopy
                  badge={account.refresh_status === 'ok' ? 'ok' : account.refresh_status === 'error' ? 'bad' : null} />
              </div>
            </div>

            {/* Exchange */}
            <div style={{ marginTop: 14 }}>
              <button className="btn btn-sm btn-primary" style={{ width: '100%', justifyContent: 'center' }}
                onClick={getExchange} disabled={loadingExchange}>
                <I.Play size={12} /> {loadingExchange ? "Getting…" : "Get exchange code"}
              </button>
            </div>
            {exchangeCode && (
              <div style={{ background: 'var(--card)', border: '1px solid var(--line)', borderRadius: 8, padding: '10px 12px', marginTop: 8 }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 6 }}>
                  <span className="section-eyebrow">exchange code · 5 min</span>
                  {!exchangeCode.startsWith("ERROR") && (
                    <button className="btn btn-sm" style={{ padding: '3px 8px', fontSize: 11 }}
                      onClick={() => copyToClipboard('exchange', exchangeCode)}>
                      {copied === 'exchange' ? <I.Check size={11} /> : <I.Copy size={11} />}
                    </button>
                  )}
                </div>
                <div className="mono" style={{ fontSize: 11.5, color: exchangeCode.startsWith("ERROR") ? 'var(--bad)' : 'var(--ok)', wordBreak: 'break-all', userSelect: 'text' }}>
                  {exchangeCode}
                </div>
              </div>
            )}
          </>
        ) : tab === 'cookies' ? (
          <>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 10 }}>
              <span className="section-eyebrow">{account.cookies_count} cookies</span>
              <button className="btn btn-sm" style={{ padding: '3px 8px', fontSize: 11 }}
                onClick={() => copyToClipboard('cookies_json', JSON.stringify(account.cookies))}>
                {copied === 'cookies_json' ? <><I.Check size={11} /> Copied</> : <><I.Copy size={11} /> Copy JSON</>}
              </button>
            </div>
            <div style={{ background: 'var(--card)', border: '1px solid var(--line)', borderRadius: 8, overflow: 'hidden' }}>
              {(account.cookies || []).map((c, i) => (
                <div key={i} style={{
                  display: 'flex', alignItems: 'center', gap: 8, padding: '6px 10px',
                  borderTop: i > 0 ? '1px solid var(--line-soft)' : 'none',
                  cursor: 'pointer',
                }}
                onClick={() => copyToClipboard('ck_'+i, c.value)}
                style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '6px 10px', borderTop: i > 0 ? '1px solid var(--line-soft)' : 'none', cursor: 'pointer', background: copied === 'ck_'+i ? 'color-mix(in oklab, var(--ok) 8%, transparent)' : 'transparent' }}>
                  <span className="mono" style={{ fontSize: 11, color: 'var(--ink-3)', minWidth: 130, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flexShrink: 0 }}>{c.name}</span>
                  <span className="mono" style={{ fontSize: 10.5, color: 'var(--ink-5)', flex: 1, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{c.value}</span>
                  <span style={{ flexShrink: 0, color: copied === 'ck_'+i ? 'var(--ok)' : 'var(--ink-5)' }}>
                    {copied === 'ck_'+i ? <I.Check size={10} /> : <I.Copy size={10} />}
                  </span>
                </div>
              ))}
            </div>
          </>
        ) : (
          /* Refresh log tab */
          logs.length === 0 ? (
            <div style={{ color: 'var(--ink-5)', fontSize: 13, padding: '20px 0', textAlign: 'center' }}>No refresh history</div>
          ) : (
            <div style={{ background: 'var(--card)', border: '1px solid var(--line)', borderRadius: 8, overflow: 'hidden' }}>
              {logs.map((l, i) => (
                <div key={i} style={{ display: 'flex', gap: 10, alignItems: 'flex-start', padding: '8px 12px', borderTop: i > 0 ? '1px solid var(--line-soft)' : 'none' }}>
                  <span style={{ width: 6, height: 6, borderRadius: '50%', marginTop: 5, flexShrink: 0, background: l.status === 'ok' ? 'var(--ok)' : 'var(--bad)' }} />
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div className="mono" style={{ fontSize: 11.5, display: 'flex', justifyContent: 'space-between' }}>
                      <span style={{ color: l.status === 'ok' ? 'var(--ok)' : 'var(--bad)' }}>{l.status}</span>
                      <span style={{ color: 'var(--ink-5)' }}>{fmtTs(l.ts)}</span>
                    </div>
                    {l.message && <div className="mono" style={{ fontSize: 10.5, color: 'var(--ink-5)', marginTop: 2, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{l.message.slice(0, 100)}</div>}
                    {l.proxy && <div className="mono" style={{ fontSize: 10.5, color: 'var(--ink-5)', marginTop: 1 }}>via {l.proxy.split(':')[0]}</div>}
                  </div>
                </div>
              ))}
            </div>
          )
        )}
      </div>
    </div>
  );
}

function CopyField({ label, value, mono, small, secret, copyKey, copied, onCopy, badge, color, noCopy }) {
  const [show, setShow] = useStateS(!secret);
  const isCopied = copied === copyKey;
  const canCopy = !noCopy && value && value !== "—";

  return (
    <div style={{
      display: 'flex', alignItems: 'center', gap: 10,
      padding: '8px 12px', borderBottom: '1px solid var(--line-soft)',
    }}>
      <span style={{ fontSize: 11.5, color: 'var(--ink-5)', flexShrink: 0, minWidth: 90 }}>{label}</span>
      <div style={{ flex: 1, minWidth: 0, display: 'flex', alignItems: 'center', gap: 6, overflow: 'hidden' }}>
        {badge && <span style={{ width: 6, height: 6, borderRadius: '50%', flexShrink: 0,
          background: badge === 'ok' ? 'var(--ok)' : 'var(--bad)' }} />}
        <span
          className={mono ? "mono" : ""}
          style={{
            fontSize: small ? 10.5 : 12.5,
            color: color || 'var(--ink)',
            overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
            filter: secret && !show ? 'blur(5px)' : 'none',
            flex: 1,
          }}
        >{value}</span>
      </div>
      <div style={{ display: 'flex', gap: 4, flexShrink: 0 }}>
        {secret && (
          <button className="icon-btn" style={{ width: 22, height: 22 }} onClick={() => setShow((s) => !s)}>
            <I.Eye size={11} />
          </button>
        )}
        {canCopy && (
          <button className="icon-btn" style={{ width: 22, height: 22, color: isCopied ? 'var(--ok)' : undefined }}
            onClick={() => onCopy(copyKey, secret && !show ? value : value)}>
            {isCopied ? <I.Check size={11} /> : <I.Copy size={11} />}
          </button>
        )}
      </div>
    </div>
  );
}

/* =========================================================================
   LOGIN SCREEN
========================================================================= */
function LoginScreen({ onLogin }) {
  const [username, setUsername] = useStateS("");
  const [password, setPassword] = useStateS("");
  const [error, setError] = useStateS(null);
  const [loading, setLoading] = useStateS(false);
  const [captchaReady, setCaptchaReady] = useStateS(false);

  const hcaptchaRef = useRefS(null);
  const [showHcaptcha, setShowHcaptcha] = useStateS(false);
  const [hcaptchaLoaded, setHcaptchaLoaded] = useStateS(false);
  const [turnstileToken, setTurnstileToken] = useStateS(null);

  // Когда showHcaptcha становится true — грузим скрипт если нужно
  useEffectS(() => {
    if (!showHcaptcha) return;
    if (window.hcaptcha) { setHcaptchaLoaded(true); return; }
    if (document.getElementById('hc-script')) return;
    const s = document.createElement('script');
    s.id = 'hc-script';
    s.src = 'https://js.hcaptcha.com/1/api.js?render=explicit';
    s.async = true;
    s.onload = () => setHcaptchaLoaded(true);
    document.head.appendChild(s);
  }, [showHcaptcha]);

  // Когда скрипт загружен И div уже в DOM — рендерим виджет
  useEffectS(() => {
    if (!showHcaptcha || !hcaptchaLoaded || !hcaptchaRef.current) return;
    if (hcaptchaRef.current._hcMounted) return;
    window.hcaptcha.render(hcaptchaRef.current, {
      sitekey: '441af294-49f0-452c-9ea2-dc30178a20b6',
      theme: 'dark',
    });
    hcaptchaRef.current._hcMounted = true;
  }, [showHcaptcha, hcaptchaLoaded]);

  // Turnstile invisible
  useEffectS(() => {
    if (document.getElementById('cf-ts-script')) return;
    const script = document.createElement('script');
    script.id = 'cf-ts-script';
    script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
    script.async = true;
    script.onload = () => {
      window._cfWidget = window.turnstile.render('#cf-ts-box', {
        sitekey: '0x4AAAAAAC1HuZAb9oE1NMY6',
        size: 'invisible',
        callback: (t) => setTurnstileToken(t),
        'error-callback': () => { setTurnstileToken(null); setShowHcaptcha(true); },
        'expired-callback': () => setTurnstileToken(null),
      });
    };
    document.head.appendChild(script);
  }, []);

  const submit = async (e) => {
    e.preventDefault();
    setError(null);
    setLoading(true);

    let hcaptcha_token = null;
    let turnstile_token = turnstileToken;

    if (!turnstile_token && window.turnstile && window._cfWidget !== undefined) {
      try { window.turnstile.execute(window._cfWidget); } catch (_) {}
      await new Promise((resolve) => {
        let n = 0;
        const id = setInterval(() => {
          try { turnstile_token = window.turnstile.getResponse(window._cfWidget); } catch (_) {}
          if (turnstile_token || ++n > 25) { clearInterval(id); resolve(); }
        }, 200);
      });
    }

    try { if (window.hcaptcha) hcaptcha_token = window.hcaptcha.getResponse(); } catch (_) {}

    try {
      const r = await fetch('/api/auth/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ username, password, hcaptcha_token, turnstile_token }),
      });
      const d = await r.json();
      if (r.ok && d.ok) {
        onLogin(d.user);
      } else {
        const detail = d.detail || '';
        if (detail === 'captcha_required' || detail === 'captcha_invalid') {
          // Показываем hCaptcha — Turnstile не сработал
          setShowHcaptcha(true);
          if (detail === 'captcha_invalid') setError('Captcha check failed, please solve it below');
        } else {
          setError(detail || 'Invalid credentials');
        }
        try { if (window.hcaptcha) window.hcaptcha.reset(); } catch (_) {}
        try { if (window._cfWidget !== undefined) { window.turnstile.reset(window._cfWidget); setTurnstileToken(null); } } catch (_) {}
      }
    } catch (err) {
      setError('Network error: ' + err.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div style={{
      height: '100vh', width: '100vw',
      background: 'var(--bg)',
      display: 'flex', alignItems: 'center', justifyContent: 'center',
    }}>
      <div className="anim-pop" style={{
        width: 360,
        background: 'var(--card)',
        border: '1px solid var(--line)',
        borderRadius: 10,
        boxShadow: '0 4px 20px rgba(0,0,0,.10)',
      }}>
        {/* Logo + title */}
        <div style={{ padding: '24px 24px 20px', borderBottom: '1px solid var(--line)' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <span style={{ fontSize: 22, fontWeight: 700, letterSpacing: '-.015em' }}>Epic</span>
            <span style={{ fontSize: 13, color: 'var(--ink-4)', marginTop: 2 }}>Dashboard</span>
          </div>
          <div style={{ fontSize: 13, color: 'var(--ink-4)', marginTop: 8 }}>Sign in to continue</div>
        </div>

        {/* Form */}
        <form onSubmit={submit} style={{ padding: '20px 24px 24px' }}>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>

            <div className="input">
              <I.User size={14} color="var(--ink-4)" />
              <input
                value={username} onChange={(e) => setUsername(e.target.value)}
                placeholder="Username" autoFocus autoComplete="username"
              />
            </div>

            <div className="input">
              <I.Lock size={14} color="var(--ink-4)" />
              <input
                type="password" value={password} onChange={(e) => setPassword(e.target.value)}
                placeholder="Password" autoComplete="current-password"
                onKeyDown={(e) => e.key === 'Enter' && submit(e)}
              />
            </div>

            {/* Turnstile invisible */}
            <div id="cf-ts-box" style={{ display: 'none' }} />

            {/* hCaptcha — только если Turnstile не прошёл */}
            {showHcaptcha && (
              <div style={{ display: 'flex', justifyContent: 'center' }}>
                <div ref={hcaptchaRef} />
              </div>
            )}

            {error && (
              <div style={{
                padding: '8px 11px', borderRadius: 6, fontSize: 13,
                background: 'color-mix(in oklab, var(--bad) 10%, transparent)',
                border: '1px solid color-mix(in oklab, var(--bad) 28%, transparent)',
                color: 'var(--bad)',
              }}>{error}</div>
            )}

            <button
              type="submit"
              className="btn btn-primary"
              disabled={loading || !username || !password}
              style={{ justifyContent: 'center', marginTop: 2 }}
            >
              {loading ? 'Signing in…' : 'Sign in'}
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}

Object.assign(window, { Sidebar, Topbar, InstancesScreen, MapCodesScreen, StatsScreen, RefresherScreen, LoginScreen });
