/* ADMIN PANEL — login-gated. All data via /api/* endpoints. */

const SWATCH_COLORS = ['#4db8ff', '#00e5ff', '#b388ff', '#46f3a8', '#ffa14d', '#ff7a8d'];
const SIZE_OPTIONS = [
  { v: 'sz-a', l: 'XL · 7×2' },
  { v: 'sz-b', l: 'L · 5×2' },
  { v: 'sz-c', l: 'M · 4×2' },
  { v: 'sz-d', l: 'M · 4×2 alt' },
  { v: 'sz-e', l: 'M · 4×2 alt' },
  { v: 'sz-f', l: 'L · 6×2' },
  { v: 'sz-g', l: 'L · 6×2 alt' },
];
const ADMIN_CATEGORIES = ['Платформы', 'Приложения', 'Веб', 'Боты', 'AI', 'Медиа', 'Продукты'];

function TelegramPanel({ tg, onAdd, onRemove, onTest }) {
  const [newChat, setNewChat] = React.useState('');
  const submitAdd = async (e) => {
    e.preventDefault();
    const v = newChat.trim();
    if (!v) return;
    const ok = await onAdd(v);
    if (ok) setNewChat('');
  };

  return (
    <div className="admin-edit" style={{padding: '24px 28px', overflow: 'auto'}}>
      <div style={{display:'flex', justifyContent:'space-between', alignItems:'flex-start', marginBottom: 20, flexWrap:'wrap', gap: 14}}>
        <div>
          <div style={{fontFamily:'var(--font-mono)', fontSize:11, color:'var(--ink-2)', letterSpacing:'.1em'}}>УВЕДОМЛЕНИЯ TELEGRAM</div>
          <div style={{fontSize: 22, marginTop: 4, letterSpacing: '-0.01em'}}>
            {tg.configured ? `Подключено · ${tg.chats.length} чат${tg.chats.length === 1 ? '' : tg.chats.length < 5 ? 'а' : 'ов'}` : 'Не настроено'}
          </div>
        </div>
        {tg.configured && (
          <button className="btn-sm" onClick={onTest} disabled={tg.testing}>
            {tg.testing ? 'Отправка…' : 'Послать тест во все'}
          </button>
        )}
      </div>

      <div className="field">
        <label>Bot Token</label>
        <input
          readOnly
          value={tg.tokenMasked || ''}
          placeholder="не настроен — запустите node server/set-telegram.js <token> <chatId>"
          style={{cursor: 'default', color: 'var(--ink-1)'}}
        />
        <div style={{fontSize: 11, color: 'var(--ink-2)', marginTop: 6, fontFamily: 'var(--font-mono)'}}>
          Токен задаётся CLI-скриптом — через браузер его менять небезопасно.
        </div>
      </div>

      <div style={{marginTop: 20}}>
        <div style={{fontFamily:'var(--font-mono)', fontSize:11, color:'var(--ink-2)', letterSpacing:'.1em', marginBottom: 10}}>
          СПИСОК ЧАТОВ · {tg.chats.length}
        </div>

        {tg.chats.length === 0 ? (
          <div style={{
            padding: '20px 18px',
            border: '1px dashed var(--line-strong)',
            borderRadius: 14,
            color: 'var(--ink-2)', fontSize: 14, textAlign: 'center',
          }}>
            Чатов нет. Добавьте хотя бы один chat ID ниже — туда будут приходить заявки.
          </div>
        ) : (
          <div style={{display: 'grid', gap: 8}}>
            {tg.chats.map(id => (
              <div key={id} style={{
                display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                padding: '12px 16px',
                border: '1px solid var(--glass-border)',
                borderRadius: 12,
                background: 'rgba(20,32,56,.35)',
              }}>
                <div style={{display:'flex', alignItems:'center', gap:12}}>
                  <span style={{
                    width: 8, height: 8, borderRadius: '50%',
                    background: '#46f3a8', boxShadow: '0 0 8px #46f3a8',
                  }}/>
                  <span style={{fontFamily: 'var(--font-mono)', fontSize: 14}}>{id}</span>
                  <span style={{fontSize: 12, color: 'var(--ink-2)'}}>
                    {id.startsWith('-') ? 'группа' : 'личный'}
                  </span>
                </div>
                <button className="btn-sm danger" onClick={() => onRemove(id)} style={{padding: '6px 12px', fontSize: 12}}>
                  Удалить
                </button>
              </div>
            ))}
          </div>
        )}

        <form onSubmit={submitAdd} style={{marginTop: 16, display: 'flex', gap: 10, alignItems: 'flex-start'}}>
          <div className="field" style={{flex: 1, margin: 0}}>
            <input
              value={newChat}
              onChange={e => setNewChat(e.target.value)}
              placeholder="Новый chat ID — число, отрицательное для групп"
              inputMode="numeric"
            />
          </div>
          <button type="submit" className="btn-sm primary" disabled={!newChat.trim() || !tg.hasToken}>
            + Добавить
          </button>
        </form>
        {!tg.hasToken && (
          <div style={{
            marginTop: 14, padding: '14px 16px',
            border: '1px solid rgba(255, 122, 141, .35)',
            background: 'rgba(255, 122, 141, .08)',
            borderRadius: 12, fontSize: 13, color: 'var(--ink-1)',
            fontFamily: 'var(--font-mono)',
          }}>
            ⚠ Сначала задайте токен бота в терминале:<br/>
            <span style={{color: 'var(--neon-2)'}}>node server/set-telegram.js &lt;token&gt; &lt;chatId&gt;</span>
          </div>
        )}
      </div>

      <div style={{marginTop: 28, padding: '16px 18px', borderRadius: 12, background: 'rgba(8,12,22,.5)', border: '1px solid var(--line)'}}>
        <div style={{fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--ink-2)', letterSpacing: '.1em', marginBottom: 8}}>
          КАК ПОЛУЧИТЬ CHAT ID
        </div>
        <ul style={{fontSize: 13, color: 'var(--ink-1)', lineHeight: 1.7, paddingLeft: 18}}>
          <li>Личный чат: откройте <span style={{color: 'var(--neon-2)'}}>@userinfobot</span> в Telegram — он пришлёт ваш id.</li>
          <li>Группа: добавьте бота в группу, затем напишите <span style={{color: 'var(--neon-2)'}}>@RawDataBot</span> — посмотрите <code>chat.id</code> (отрицательное число).</li>
        </ul>
      </div>
    </div>
  );
}

function CaseGallery({ caseId, images, onChanged, onError }) {
  const fileRef = React.useRef(null);
  const [uploading, setUploading] = React.useState(false);
  const [dropFileOver, setDropFileOver] = React.useState(false);
  const [draggingId, setDraggingId] = React.useState(null);
  const [overId, setOverId] = React.useState(null);

  const sendFiles = async (fileList) => {
    if (!fileList || !fileList.length) return;
    setUploading(true);
    try {
      const fd = new FormData();
      [...fileList].forEach(f => fd.append('files', f));
      const r = await fetch('/api/cases/' + encodeURIComponent(caseId) + '/images', {
        method: 'POST', credentials: 'same-origin', body: fd,
      });
      const j = await r.json().catch(() => ({}));
      if (!r.ok || !j.ok) { onError(j.error || 'Ошибка загрузки'); return; }
      onChanged(j.images);
    } catch (_) {
      onError('Сервер недоступен');
    } finally {
      setUploading(false);
    }
  };

  const removeImage = async (id) => {
    try {
      const r = await fetch(`/api/cases/${encodeURIComponent(caseId)}/images/${id}`, {
        method: 'DELETE', credentials: 'same-origin',
      });
      const j = await r.json().catch(() => ({}));
      if (!r.ok) { onError(j.error || 'Ошибка'); return; }
      onChanged(j.images);
    } catch (_) { onError('Сервер недоступен'); }
  };

  const reorderTo = async (sourceId, targetId) => {
    if (sourceId == null || targetId == null || sourceId === targetId) return;
    const srcIdx = images.findIndex(i => i.id === sourceId);
    const tgtIdx = images.findIndex(i => i.id === targetId);
    if (srcIdx === -1 || tgtIdx === -1) return;
    const next = [...images];
    const [moved] = next.splice(srcIdx, 1);
    next.splice(tgtIdx, 0, moved);
    onChanged(next); // optimistic
    try {
      const r = await fetch(`/api/cases/${encodeURIComponent(caseId)}/images/reorder`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({ ids: next.map(i => i.id) }),
      });
      const j = await r.json().catch(() => ({}));
      if (!r.ok) { onError(j.error || 'Ошибка'); }
      else if (j.images) onChanged(j.images);
    } catch (_) { onError('Сервер недоступен'); }
  };

  const onFileDrop = (e) => {
    e.preventDefault();
    setDropFileOver(false);
    sendFiles(e.dataTransfer.files);
  };

  return (
    <div className="field" style={{marginTop: 8}}>
      <label>Галерея · {images.length}</label>

      {images.length > 0 && (
        <React.Fragment>
          <div style={{
            fontFamily: 'var(--font-mono)', fontSize: 11, color: 'var(--ink-2)',
            letterSpacing: '.06em', marginBottom: 10,
          }}>
            ⇅ перетащите миниатюры для смены порядка · первое фото становится обложкой
          </div>
          <div style={{
            display: 'grid',
            gridTemplateColumns: 'repeat(auto-fill, minmax(140px, 1fr))',
            gap: 10, marginBottom: 12,
          }}>
            {images.map((img, i) => {
              const isDragging = draggingId === img.id;
              const isOver = overId === img.id && draggingId !== img.id;
              return (
                <div
                  key={img.id}
                  draggable
                  onDragStart={(e) => {
                    e.dataTransfer.effectAllowed = 'move';
                    e.dataTransfer.setData('text/plain', String(img.id));
                    setDraggingId(img.id);
                  }}
                  onDragEnd={() => { setDraggingId(null); setOverId(null); }}
                  onDragOver={(e) => {
                    if (draggingId == null || draggingId === img.id) return;
                    e.preventDefault();
                    e.dataTransfer.dropEffect = 'move';
                    if (overId !== img.id) setOverId(img.id);
                  }}
                  onDragLeave={(e) => {
                    if (!e.currentTarget.contains(e.relatedTarget) && overId === img.id) setOverId(null);
                  }}
                  onDrop={(e) => {
                    e.preventDefault();
                    e.stopPropagation();
                    const src = draggingId;
                    setOverId(null);
                    setDraggingId(null);
                    reorderTo(src, img.id);
                  }}
                  style={{
                    position: 'relative', borderRadius: 12, overflow: 'hidden',
                    border: '1px solid var(--glass-border)', aspectRatio: '4 / 3',
                    background: '#0a0e18',
                    opacity: isDragging ? 0.4 : 1,
                    outline: isOver ? '2px solid var(--neon-2)' : 'none',
                    outlineOffset: -2,
                    cursor: 'grab',
                    transition: 'opacity .15s, outline-color .15s, transform .15s',
                    transform: isOver ? 'scale(1.03)' : 'scale(1)',
                  }}
                >
                  <img src={img.url} alt=""
                    draggable={false}
                    style={{width: '100%', height: '100%', objectFit: 'cover', display: 'block', pointerEvents: 'none'}}/>
                  <div style={{
                    position: 'absolute', inset: 0,
                    background: 'linear-gradient(180deg, transparent 50%, rgba(5,7,13,.85))',
                    opacity: 0, transition: 'opacity .2s',
                    display: 'flex', alignItems: 'flex-end', justifyContent: 'flex-end',
                    padding: 8,
                    pointerEvents: draggingId != null ? 'none' : 'auto',
                  }}
                    onMouseEnter={e => { if (draggingId == null) e.currentTarget.style.opacity = 1; }}
                    onMouseLeave={e => e.currentTarget.style.opacity = 0}>
                    <button type="button" className="btn-sm danger" style={{padding: '4px 10px', fontSize: 11}}
                      onClick={() => removeImage(img.id)}>Удалить</button>
                  </div>
                  {i === 0 && (
                    <div style={{
                      position: 'absolute', top: 6, left: 6,
                      fontFamily: 'var(--font-mono)', fontSize: 10,
                      padding: '3px 8px', borderRadius: 100,
                      background: 'rgba(0,229,255,.15)', color: 'var(--neon-2)',
                      border: '1px solid rgba(0,229,255,.35)',
                      pointerEvents: 'none',
                    }}>обложка</div>
                  )}
                </div>
              );
            })}
          </div>
        </React.Fragment>
      )}

      <div
        onDragOver={(e) => {
          // Only highlight the file-dropzone when an actual file is being dragged
          // (not when we're reordering thumbnails inside the gallery).
          if (draggingId != null) return;
          e.preventDefault();
          setDropFileOver(true);
        }}
        onDragLeave={() => setDropFileOver(false)}
        onDrop={(e) => { if (draggingId != null) return; onFileDrop(e); }}
        onClick={() => fileRef.current && fileRef.current.click()}
        style={{
          padding: '20px 14px', borderRadius: 12, textAlign: 'center',
          border: `1px dashed ${dropFileOver ? 'var(--neon)' : 'var(--line-strong)'}`,
          background: dropFileOver ? 'rgba(77,184,255,.08)' : 'rgba(8,12,22,.5)',
          color: 'var(--ink-1)', fontSize: 13, cursor: 'pointer',
          transition: 'border-color .2s, background .2s',
        }}
      >
        {uploading ? 'Загрузка…' : (
          <React.Fragment>
            Перетащите файлы сюда или <span style={{color: 'var(--neon-2)'}}>выберите</span>
            <div style={{marginTop: 4, fontSize: 11, color: 'var(--ink-2)', fontFamily: 'var(--font-mono)'}}>
              jpg / png / webp / gif · до 8 МБ · до 10 файлов за раз
            </div>
          </React.Fragment>
        )}
      </div>
      <input
        ref={fileRef}
        type="file"
        accept="image/jpeg,image/png,image/webp,image/gif"
        multiple
        style={{display: 'none'}}
        onChange={(e) => { sendFiles(e.target.files); e.target.value = ''; }}
      />
    </div>
  );
}

function emptyCase() {
  return {
    id: 'c' + Date.now(),
    tag: 'NEW',
    title: 'Новый кейс',
    summary: 'Краткое описание задачи и решения.',
    year: new Date().getFullYear(),
    scope: 'Web',
    color: '#4db8ff',
    category: 'Продукты',
    size: 'sz-c',
    metrics: [],
  };
}

/* --- LOGIN FORM --- */
function AdminLogin({ onSuccess, onClose }) {
  const [username, setUsername] = React.useState('');
  const [password, setPassword] = React.useState('');
  const [error, setError] = React.useState('');
  const [busy, setBusy] = React.useState(false);
  const [retryAfter, setRetryAfter] = React.useState(0);

  React.useEffect(() => {
    if (!retryAfter) return;
    const id = setInterval(() => setRetryAfter(s => s > 1 ? s - 1 : 0), 1000);
    return () => clearInterval(id);
  }, [retryAfter]);

  const submit = async (ev) => {
    ev.preventDefault();
    if (busy || retryAfter > 0) return;
    setBusy(true);
    setError('');
    try {
      const r = await fetch('/api/auth/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({ username, password }),
      });
      const j = await r.json().catch(() => ({}));
      if (r.ok) {
        onSuccess();
        return;
      }
      if (r.status === 429) {
        setRetryAfter(j.retryAfter || 300);
        setError(j.error || 'Слишком много попыток');
      } else {
        const remaining = typeof j.remaining === 'number' ? ` (осталось попыток: ${j.remaining})` : '';
        setError((j.error || 'Ошибка авторизации') + remaining);
      }
    } catch (_) {
      setError('Сервер недоступен');
    } finally {
      setBusy(false);
    }
  };

  return (
    <div className="admin-overlay" onClick={(e) => { if (e.target.classList.contains('admin-overlay')) onClose(); }}>
      <div className="admin-shell" style={{maxWidth: 480, height: 'auto', display: 'block'}}>
        <div className="admin-head">
          <div className="title">
            <span style={{fontWeight:600, letterSpacing:'-0.01em', fontSize: 18}}>Вход в админку</span>
            <span className="pill">D-one studio</span>
          </div>
          <button className="btn-sm" onClick={onClose}>Закрыть ✕</button>
        </div>
        <form onSubmit={submit} style={{padding: '24px 28px'}} noValidate>
          <div className="field">
            <label>Логин</label>
            <input
              autoFocus
              autoComplete="username"
              value={username}
              onChange={e => setUsername(e.target.value)}
              placeholder="admin"
            />
          </div>
          <div className="field">
            <label>Пароль</label>
            <input
              type="password"
              autoComplete="current-password"
              value={password}
              onChange={e => setPassword(e.target.value)}
              placeholder="••••••••"
            />
          </div>
          {error && (
            <div className="err-msg" style={{marginBottom: 12}}>
              {error}{retryAfter > 0 && ` · повтор через ${retryAfter}с`}
            </div>
          )}
          <button
            type="submit"
            className="form-submit"
            disabled={busy || retryAfter > 0 || !username || !password}
            style={{marginTop: 4}}
          >
            {busy ? 'Проверка…' : retryAfter > 0 ? `Подождите ${retryAfter}с` : 'Войти'}
          </button>
        </form>
      </div>
    </div>
  );
}

/* --- ADMIN PANEL --- */
function Admin({ onClose, onLoggedOut }) {
  const [cases, setCases] = React.useState([]);
  const [requests, setRequests] = React.useState([]);
  const [team, setTeam] = React.useState([]);
  const [posts, setPosts] = React.useState([]);
  const [tab, setTab] = React.useState('cases');
  const [activeId, setActiveId] = React.useState(null);
  const [activeMemberId, setActiveMemberId] = React.useState(null);
  const [activePostId, setActivePostId] = React.useState(null);
  const [toast, setToast] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [tg, setTg] = React.useState({
    configured: null, hasToken: false, tokenMasked: null,
    chats: [], testing: false,
  });
  const debounceRef = React.useRef({});
  const memberDebounceRef = React.useRef({});
  const postDebounceRef = React.useRef({});
  const active = cases.find(c => c.id === activeId);
  const activeMember = team.find(m => m.id === activeMemberId);
  const activePost = posts.find(p => p.id === activePostId);

  const refreshTelegram = React.useCallback(() => {
    fetch('/api/telegram/status', { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : Promise.reject())
      .then(j => setTg(t => ({
        ...t,
        configured: !!j.configured,
        hasToken: !!j.hasToken,
        tokenMasked: j.tokenMasked || null,
        chats: Array.isArray(j.chats) ? j.chats : [],
      })))
      .catch(() => setTg(t => ({ ...t, configured: false })));
  }, []);

  const addChat = async (chatId) => {
    if (!/^-?\d+$/.test(String(chatId || '').trim())) {
      showToast('Невалидный chatId');
      return false;
    }
    try {
      const r = await fetch('/api/telegram/chats', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({ chatId: String(chatId).trim() }),
      });
      const j = await r.json().catch(() => ({}));
      if (!r.ok || !j.ok) { showToast(j.error || 'Ошибка'); return false; }
      setTg(t => ({ ...t, chats: j.chats, configured: t.hasToken && j.chats.length > 0 }));
      showToast('Чат добавлен');
      return true;
    } catch (_) { showToast('Сервер недоступен'); return false; }
  };

  const removeChat = async (chatId) => {
    try {
      const r = await fetch('/api/telegram/chats/' + encodeURIComponent(chatId), {
        method: 'DELETE', credentials: 'same-origin',
      });
      const j = await r.json().catch(() => ({}));
      if (!r.ok) { showToast(j.error || 'Ошибка'); return; }
      setTg(t => ({ ...t, chats: j.chats, configured: t.hasToken && j.chats.length > 0 }));
      showToast('Удалено');
    } catch (_) { showToast('Сервер недоступен'); }
  };

  const testTelegram = async () => {
    if (tg.testing) return;
    setTg(t => ({ ...t, testing: true }));
    try {
      const r = await fetch('/api/telegram/test', {
        method: 'POST', credentials: 'same-origin',
      });
      const j = await r.json().catch(() => ({}));
      if (r.ok && j.ok) {
        const failed = (j.results || []).filter(x => !x.ok);
        if (failed.length) showToast(`Отправлено частично: ${failed.length} ошибок`);
        else showToast('Тест отправлен ✓');
      } else {
        showToast(j.error || 'Не удалось отправить');
      }
    } catch (_) {
      showToast('Сервер недоступен');
    } finally {
      setTg(t => ({ ...t, testing: false }));
    }
  };

  const showToast = (msg) => {
    setToast(msg);
    setTimeout(() => setToast(null), 2200);
  };

  const fetchAll = React.useCallback(async () => {
    setLoading(true);
    try {
      const [cs, rs, tm, bp] = await Promise.all([
        window.apiListCases(),
        fetch('/api/requests', { credentials: 'same-origin' }).then(r => {
          if (r.status === 401) throw new Error('unauth');
          return r.json();
        }),
        fetch('/api/team', { credentials: 'same-origin' }).then(r => r.ok ? r.json() : { team: [] }),
        fetch('/api/blog/all', { credentials: 'same-origin' }).then(r => {
          if (r.status === 401) throw new Error('unauth');
          return r.ok ? r.json() : { posts: [] };
        }),
      ]);
      setCases(cs);
      setRequests(rs.requests || []);
      setTeam(tm.team || []);
      setPosts(bp.posts || []);
      setActiveId(prev => prev || (cs[0] && cs[0].id) || null);
      setActiveMemberId(prev => prev || ((tm.team || [])[0] && (tm.team || [])[0].id) || null);
      setActivePostId(prev => prev || ((bp.posts || [])[0] && (bp.posts || [])[0].id) || null);
    } catch (e) {
      if (e && e.message === 'unauth') { onLoggedOut(); return; }
      showToast('Ошибка загрузки');
    } finally {
      setLoading(false);
    }
  }, [onLoggedOut]);

  React.useEffect(() => { fetchAll(); }, [fetchAll]);
  React.useEffect(() => { refreshTelegram(); }, [refreshTelegram]);

  const update = (k, v) => {
    if (!active) return;
    const next = cases.map(c => c.id === activeId ? { ...c, [k]: v } : c);
    setCases(next);
    // debounce per-case save
    const key = activeId;
    if (debounceRef.current[key]) clearTimeout(debounceRef.current[key]);
    debounceRef.current[key] = setTimeout(async () => {
      try {
        const fresh = next.find(c => c.id === key);
        await window.apiUpsertCase(fresh);
        window.notifyCasesUpdated();
      } catch (_) {
        showToast('Не удалось сохранить');
      }
    }, 500);
  };

  const addCase = async () => {
    const nu = emptyCase();
    try {
      await window.apiCreateCase(nu);
      const list = await window.apiListCases();
      setCases(list);
      setActiveId(nu.id);
      window.notifyCasesUpdated();
      showToast('Кейс создан');
    } catch (_) {
      showToast('Не удалось создать');
    }
  };

  const remove = async (id) => {
    if (!confirm('Удалить кейс?')) return;
    try {
      await window.apiDeleteCase(id);
      const list = await window.apiListCases();
      setCases(list);
      if (id === activeId) setActiveId((list[0] && list[0].id) || null);
      window.notifyCasesUpdated();
      showToast('Удалено');
    } catch (_) {
      showToast('Не удалось удалить');
    }
  };

  const move = async (id, dir) => {
    const idx = cases.findIndex(c => c.id === id);
    const swap = idx + dir;
    if (swap < 0 || swap >= cases.length) return;
    const next = [...cases];
    [next[idx], next[swap]] = [next[swap], next[idx]];
    setCases(next);
    try {
      await window.apiReorderCases(next.map(c => c.id));
      window.notifyCasesUpdated();
    } catch (_) {
      showToast('Не удалось переставить');
      fetchAll();
    }
  };

  const resetAll = async () => {
    if (!confirm('Сбросить все кейсы к значениям по умолчанию?')) return;
    try {
      const list = await window.apiResetCases();
      setCases(list);
      setActiveId((list[0] && list[0].id) || null);
      window.notifyCasesUpdated();
      showToast('Сброшено');
    } catch (_) {
      showToast('Не удалось сбросить');
    }
  };

  const removeRequest = async (id) => {
    try {
      const r = await fetch('/api/requests/' + id, { method: 'DELETE', credentials: 'same-origin' });
      if (!r.ok) throw new Error('fail');
      setRequests(requests.filter(x => x.id !== id));
    } catch (_) {
      showToast('Не удалось удалить');
    }
  };

  // ---------- TEAM ----------
  const notifyTeamUpdated = () => window.dispatchEvent(new CustomEvent('scutoid:team-updated'));
  const addMember = async () => {
    try {
      const r = await fetch('/api/team', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({}),
      });
      const j = await r.json().catch(() => ({}));
      if (!r.ok || !j.ok) { showToast(j.error || 'Ошибка'); return; }
      const fresh = await fetch('/api/team', { credentials: 'same-origin' }).then(r => r.json());
      setTeam(fresh.team || []);
      setActiveMemberId(j.member.id);
      notifyTeamUpdated();
      showToast('Участник добавлен');
    } catch (_) { showToast('Сервер недоступен'); }
  };
  const updateMemberField = (k, v) => {
    if (!activeMember) return;
    const next = team.map(m => m.id === activeMemberId ? { ...m, [k]: v } : m);
    setTeam(next);
    const key = activeMemberId;
    if (memberDebounceRef.current[key]) clearTimeout(memberDebounceRef.current[key]);
    memberDebounceRef.current[key] = setTimeout(async () => {
      const fresh = next.find(m => m.id === key);
      try {
        const r = await fetch('/api/team/' + key, {
          method: 'PUT',
          headers: { 'Content-Type': 'application/json' },
          credentials: 'same-origin',
          body: JSON.stringify(fresh),
        });
        if (!r.ok) showToast('Не удалось сохранить');
        else notifyTeamUpdated();
      } catch (_) { showToast('Сервер недоступен'); }
    }, 500);
  };
  const removeMember = async (id) => {
    if (!confirm('Удалить участника?')) return;
    try {
      const r = await fetch('/api/team/' + id, { method: 'DELETE', credentials: 'same-origin' });
      if (!r.ok) throw new Error('fail');
      const next = team.filter(m => m.id !== id);
      setTeam(next);
      if (activeMemberId === id) setActiveMemberId(next[0]?.id || null);
      notifyTeamUpdated();
      showToast('Удалено');
    } catch (_) { showToast('Не удалось удалить'); }
  };
  const moveMember = async (id, dir) => {
    const idx = team.findIndex(m => m.id === id);
    const swap = idx + dir;
    if (swap < 0 || swap >= team.length) return;
    const next = [...team];
    [next[idx], next[swap]] = [next[swap], next[idx]];
    setTeam(next);
    try {
      await fetch('/api/team/reorder', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({ ids: next.map(m => m.id) }),
      });
      notifyTeamUpdated();
    } catch (_) { showToast('Не удалось переставить'); }
  };

  // ---------- BLOG ----------
  const addPost = async () => {
    try {
      const r = await fetch('/api/blog', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({}),
      });
      const j = await r.json().catch(() => ({}));
      if (!r.ok || !j.ok) { showToast(j.error || 'Ошибка'); return; }
      const fresh = await fetch('/api/blog/all', { credentials: 'same-origin' }).then(r => r.json());
      setPosts(fresh.posts || []);
      setActivePostId(j.post.id);
      showToast('Заметка создана');
    } catch (_) { showToast('Сервер недоступен'); }
  };
  const updatePostField = (k, v) => {
    if (!activePost) return;
    const next = posts.map(p => p.id === activePostId ? { ...p, [k]: v } : p);
    setPosts(next);
    const key = activePostId;
    if (postDebounceRef.current[key]) clearTimeout(postDebounceRef.current[key]);
    postDebounceRef.current[key] = setTimeout(async () => {
      const fresh = next.find(p => p.id === key);
      try {
        const r = await fetch('/api/blog/' + key, {
          method: 'PUT',
          headers: { 'Content-Type': 'application/json' },
          credentials: 'same-origin',
          body: JSON.stringify(fresh),
        });
        const j = await r.json().catch(() => ({}));
        if (!r.ok) { showToast('Не удалось сохранить'); return; }
        // server may have re-slugified — sync local state
        if (j.post) {
          setPosts(prev => prev.map(p => p.id === key ? j.post : p));
        }
      } catch (_) { showToast('Сервер недоступен'); }
    }, 500);
  };
  const removePost = async (id) => {
    if (!confirm('Удалить заметку?')) return;
    try {
      const r = await fetch('/api/blog/' + id, { method: 'DELETE', credentials: 'same-origin' });
      if (!r.ok) throw new Error('fail');
      const next = posts.filter(p => p.id !== id);
      setPosts(next);
      if (activePostId === id) setActivePostId(next[0]?.id || null);
      showToast('Удалено');
    } catch (_) { showToast('Не удалось удалить'); }
  };
  const movePost = async (id, dir) => {
    const idx = posts.findIndex(p => p.id === id);
    const swap = idx + dir;
    if (swap < 0 || swap >= posts.length) return;
    const next = [...posts];
    [next[idx], next[swap]] = [next[swap], next[idx]];
    setPosts(next);
    try {
      await fetch('/api/blog/reorder', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({ ids: next.map(p => p.id) }),
      });
    } catch (_) { showToast('Не удалось переставить'); }
  };

  const logout = async () => {
    try { await fetch('/api/auth/logout', { method: 'POST', credentials: 'same-origin' }); }
    catch (_) {}
    onLoggedOut();
  };

  return (
    <div className="admin-overlay" onClick={(e) => { if (e.target.classList.contains('admin-overlay')) onClose(); }}>
      <div className="admin-shell">
        <div className="admin-head">
          <div className="title">
            <span style={{fontWeight:600, letterSpacing:'-0.01em', fontSize: 18}}>Админка</span>
            <span className="pill">D-one studio</span>
            <div style={{display:'flex', gap:6, marginLeft: 12}}>
              <button className={"chip " + (tab==='cases'?'active':'')} onClick={() => setTab('cases')}>
                Кейсы · {cases.length}
              </button>
              <button className={"chip " + (tab==='team'?'active':'')} onClick={() => setTab('team')}>
                Команда · {team.length}
              </button>
              <button className={"chip " + (tab==='blog'?'active':'')} onClick={() => setTab('blog')}>
                Блог · {posts.length}
              </button>
              <button className={"chip " + (tab==='requests'?'active':'')} onClick={() => setTab('requests')}>
                Запросы · {requests.length}
              </button>
              <button className={"chip " + (tab==='telegram'?'active':'')} onClick={() => setTab('telegram')}>
                Telegram · {tg.chats.length}
                <span style={{
                  display: 'inline-block', width: 6, height: 6, borderRadius: '50%',
                  marginLeft: 6,
                  background: tg.configured === null ? 'var(--ink-3)'
                    : tg.configured ? '#46f3a8' : '#ff7a8d',
                  boxShadow: tg.configured ? '0 0 8px #46f3a8' : 'none',
                  verticalAlign: 'middle',
                }}/>
              </button>
            </div>
          </div>
          <div style={{display:'flex', gap:10, alignItems:'center'}}>
            {tab === 'cases' && (
              <React.Fragment>
                <button className="btn-sm" onClick={resetAll}>Сбросить</button>
                <button className="btn-sm primary" onClick={addCase}>+ Новый кейс</button>
              </React.Fragment>
            )}
            {tab === 'team' && (
              <button className="btn-sm primary" onClick={addMember}>+ Новый участник</button>
            )}
            {tab === 'blog' && (
              <button className="btn-sm primary" onClick={addPost}>+ Новая заметка</button>
            )}
            <button className="btn-sm" onClick={logout} title="Выйти из админки">Выйти</button>
            <button className="btn-sm" onClick={onClose}>Закрыть ✕</button>
          </div>
        </div>

        {loading ? (
          <div style={{padding:'80px 24px', textAlign:'center', color:'var(--ink-2)'}}>Загрузка…</div>
        ) : tab === 'cases' ? (
          <div className="admin-body">
            <div className="admin-list">
              {cases.map((c, i) => (
                <div key={c.id} className={"li " + (c.id === activeId ? "active" : "")}
                  onClick={() => setActiveId(c.id)}>
                  <div style={{display:'flex', justifyContent:'space-between', alignItems:'center'}}>
                    <div className="li-tag" style={{color: c.color}}>{c.tag}</div>
                    <div style={{display:'flex', gap:4}}>
                      <button className="btn-sm" style={{padding:'4px 8px', fontSize:11}} onClick={(e) => { e.stopPropagation(); move(c.id, -1); }}>↑</button>
                      <button className="btn-sm" style={{padding:'4px 8px', fontSize:11}} onClick={(e) => { e.stopPropagation(); move(c.id, 1); }}>↓</button>
                    </div>
                  </div>
                  <div className="li-title">{c.title}</div>
                  <div className="li-meta">{c.category} · {c.year} · {c.size}</div>
                </div>
              ))}
              {cases.length === 0 && (
                <div style={{padding:'40px 14px', textAlign:'center', color:'var(--ink-2)', fontSize:14}}>
                  Кейсов нет. Нажмите «+ Новый кейс».
                </div>
              )}
            </div>

            <div className="admin-edit">
              {active ? (
                <React.Fragment>
                  <div style={{display:'flex', justifyContent:'space-between', alignItems:'flex-start', marginBottom: 18}}>
                    <div>
                      <div style={{fontFamily:'var(--font-mono)', fontSize:11, color:'var(--ink-2)', letterSpacing:'.1em'}}>РЕДАКТИРОВАНИЕ КЕЙСА · {active.id}</div>
                      <div style={{fontSize:22, marginTop:4, letterSpacing:'-0.01em'}}>{active.title}</div>
                    </div>
                    <button className="btn-sm danger" onClick={() => remove(active.id)}>Удалить</button>
                  </div>

                  <div className="grid-2">
                    <div className="field">
                      <label>Заголовок</label>
                      <input value={active.title} onChange={e => update('title', e.target.value)}/>
                    </div>
                    <div className="field">
                      <label>Тэг</label>
                      <input value={active.tag} onChange={e => update('tag', e.target.value.toUpperCase())}/>
                    </div>
                  </div>

                  <div className="field">
                    <label>Краткое описание</label>
                    <textarea value={active.summary} onChange={e => update('summary', e.target.value)} style={{minHeight: 80}}/>
                  </div>

                  <div className="grid-2">
                    <div className="field">
                      <label>Год</label>
                      <input type="number" value={active.year} onChange={e => update('year', parseInt(e.target.value)||0)}/>
                    </div>
                    <div className="field">
                      <label>Скоуп / стек</label>
                      <input value={active.scope} onChange={e => update('scope', e.target.value)}/>
                    </div>
                  </div>

                  <div className="grid-2">
                    <div className="field">
                      <label>Категория</label>
                      <select value={active.category} onChange={e => update('category', e.target.value)}>
                        {ADMIN_CATEGORIES.map(c => <option key={c}>{c}</option>)}
                      </select>
                    </div>
                    <div className="field">
                      <label>Размер плитки</label>
                      <select value={active.size} onChange={e => update('size', e.target.value)}>
                        {SIZE_OPTIONS.map(s => <option key={s.v} value={s.v}>{s.l}</option>)}
                      </select>
                    </div>
                  </div>

                  <div className="field">
                    <label>Цвет акцента</label>
                    <div className="color-swatches">
                      {SWATCH_COLORS.map(col => (
                        <button key={col} type="button"
                          className={"sw " + (active.color === col ? "active" : "")}
                          style={{background: col, color: col}}
                          onClick={() => update('color', col)}/>
                      ))}
                    </div>
                  </div>

                  <div className="field">
                    <label>Метрики (через запятую)</label>
                    <input
                      value={(active.metrics || []).join(', ')}
                      onChange={e => update('metrics', e.target.value.split(',').map(s => s.trim()).filter(Boolean))}
                      placeholder="+38% retention, 120ms p95"/>
                  </div>

                  <CaseGallery
                    caseId={active.id}
                    images={active.images || []}
                    onChanged={(images) => {
                      setCases(cs => cs.map(c => c.id === active.id ? { ...c, images } : c));
                      window.notifyCasesUpdated();
                    }}
                    onError={showToast}
                  />
                </React.Fragment>
              ) : (
                <div style={{padding:'60px 14px', textAlign:'center', color:'var(--ink-2)'}}>
                  Выберите кейс слева или создайте новый.
                </div>
              )}
            </div>
          </div>
        ) : tab === 'requests' ? (
          <div className="admin-edit" style={{padding: '20px 28px', overflow:'auto'}}>
            <div style={{fontFamily:'var(--font-mono)', fontSize:11, color:'var(--ink-2)', letterSpacing:'.1em', marginBottom: 14}}>
              ВХОДЯЩИЕ ЗАПРОСЫ · {requests.length}
            </div>
            {requests.length === 0 ? (
              <div style={{padding:'60px 14px', textAlign:'center', color:'var(--ink-2)'}}>
                Запросов пока нет. Отправьте тестовый через форму контактов.
              </div>
            ) : (
              <div style={{display:'grid', gap:10}}>
                {requests.map(r => (
                  <div key={r.id} style={{
                    padding: '16px 18px',
                    border:'1px solid var(--glass-border)',
                    borderRadius: 14,
                    background:'rgba(20,32,56,.35)',
                    display:'grid', gridTemplateColumns: '1fr auto', gap:10
                  }}>
                    <div>
                      <div style={{display:'flex', gap:10, alignItems:'center', marginBottom:4, flexWrap:'wrap'}}>
                        <span style={{fontWeight:500}}>{r.name}</span>
                        <span style={{color:'var(--ink-2)', fontSize:13}}>· {r.email}</span>
                        {r.telegram && (
                          <a href={`https://t.me/${r.telegram}`} target="_blank" rel="noopener noreferrer"
                             style={{color:'var(--accent, #4db8ff)', fontSize:13, textDecoration:'none'}}>
                            · @{r.telegram}
                          </a>
                        )}
                        {r.company && <span style={{color:'var(--ink-2)', fontSize:13}}>· {r.company}</span>}
                      </div>
                      <div style={{display:'flex', gap:10, fontFamily:'var(--font-mono)', fontSize:11, color:'var(--ink-2)', marginBottom: 8, flexWrap:'wrap'}}>
                        <span>{new Date(r.at).toLocaleString('ru-RU')}</span>
                        {r.service && <span>· {r.service}</span>}
                        {r.budget && <span>· {r.budget}</span>}
                      </div>
                      <div style={{fontSize: 14, color:'var(--ink-1)', lineHeight:1.5, whiteSpace:'pre-wrap'}}>{r.message}</div>
                    </div>
                    <button className="btn-sm danger" onClick={() => removeRequest(r.id)} style={{alignSelf:'flex-start'}}>✕</button>
                  </div>
                ))}
              </div>
            )}
          </div>
        ) : tab === 'team' ? (
          <TeamPanel
            team={team}
            active={activeMember}
            activeId={activeMemberId}
            onSelect={setActiveMemberId}
            onMove={moveMember}
            onUpdate={updateMemberField}
            onRemove={removeMember}
          />
        ) : tab === 'blog' ? (
          <BlogPanel
            posts={posts}
            active={activePost}
            activeId={activePostId}
            onSelect={setActivePostId}
            onMove={movePost}
            onUpdate={updatePostField}
            onRemove={removePost}
          />
        ) : (
          <TelegramPanel
            tg={tg}
            onAdd={addChat}
            onRemove={removeChat}
            onTest={testTelegram}
          />
        )}
      </div>

      {toast && (
        <div className="toast">
          <span style={{width:8, height:8, borderRadius:'50%', background:'#46f3a8', boxShadow:'0 0 10px #46f3a8'}}></span>
          {toast}
        </div>
      )}
    </div>
  );
}

/* --- TEAM PANEL --- */
function TeamPanel({ team, active, activeId, onSelect, onMove, onUpdate, onRemove }) {
  return (
    <div className="admin-body">
      <div className="admin-list">
        {team.map((m, i) => (
          <div key={m.id} className={"li " + (m.id === activeId ? "active" : "")}
            onClick={() => onSelect(m.id)}>
            <div style={{display:'flex', justifyContent:'space-between', alignItems:'center'}}>
              <div className="li-tag" style={{color: m.color}}>{m.initials}</div>
              <div style={{display:'flex', gap:4}}>
                <button className="btn-sm" style={{padding:'4px 8px', fontSize:11}} onClick={(e) => { e.stopPropagation(); onMove(m.id, -1); }}>↑</button>
                <button className="btn-sm" style={{padding:'4px 8px', fontSize:11}} onClick={(e) => { e.stopPropagation(); onMove(m.id, 1); }}>↓</button>
              </div>
            </div>
            <div className="li-title">{m.name}</div>
            <div className="li-meta">{m.role}</div>
          </div>
        ))}
        {team.length === 0 && (
          <div style={{padding:'40px 14px', textAlign:'center', color:'var(--ink-2)', fontSize:14}}>
            Команда пуста. Нажмите «+ Новый участник».
          </div>
        )}
      </div>

      <div className="admin-edit">
        {active ? (
          <React.Fragment>
            <div style={{display:'flex', justifyContent:'space-between', alignItems:'flex-start', marginBottom: 18}}>
              <div>
                <div style={{fontFamily:'var(--font-mono)', fontSize:11, color:'var(--ink-2)', letterSpacing:'.1em'}}>УЧАСТНИК КОМАНДЫ · #{active.id}</div>
                <div style={{fontSize:22, marginTop:4, letterSpacing:'-0.01em'}}>{active.name}</div>
              </div>
              <button className="btn-sm danger" onClick={() => onRemove(active.id)}>Удалить</button>
            </div>

            <div className="grid-2">
              <div className="field">
                <label>Имя</label>
                <input value={active.name} onChange={e => onUpdate('name', e.target.value)}/>
              </div>
              <div className="field">
                <label>Инициалы (1–3 символа)</label>
                <input value={active.initials}
                  onChange={e => onUpdate('initials', e.target.value.slice(0, 3).toUpperCase())}
                  maxLength={3}/>
              </div>
            </div>

            <div className="field">
              <label>Роль</label>
              <input value={active.role} onChange={e => onUpdate('role', e.target.value)}
                placeholder="Head of Mobile / CTO / Designer ..."/>
            </div>

            <div className="field">
              <label>Описание</label>
              <textarea value={active.bio} onChange={e => onUpdate('bio', e.target.value)}
                style={{minHeight: 100}}
                placeholder="Короткое био: опыт, фокус, ex-companies."/>
            </div>

            <div className="field">
              <label>Цвет акцента</label>
              <div className="color-swatches">
                {SWATCH_COLORS.map(col => (
                  <button key={col} type="button"
                    className={"sw " + (active.color === col ? "active" : "")}
                    style={{background: col, color: col}}
                    onClick={() => onUpdate('color', col)}/>
                ))}
              </div>
            </div>
          </React.Fragment>
        ) : (
          <div style={{padding:'60px 14px', textAlign:'center', color:'var(--ink-2)'}}>
            Выберите участника слева или создайте нового.
          </div>
        )}
      </div>
    </div>
  );
}

/* --- BLOG PANEL --- */
function BlogPanel({ posts, active, activeId, onSelect, onMove, onUpdate, onRemove }) {
  return (
    <div className="admin-body">
      <div className="admin-list">
        {posts.map((p, i) => (
          <div key={p.id} className={"li " + (p.id === activeId ? "active" : "")}
            onClick={() => onSelect(p.id)}>
            <div style={{display:'flex', justifyContent:'space-between', alignItems:'center'}}>
              <div className="li-tag" style={{color: p.published ? 'var(--neon-2)' : 'var(--ink-3)'}}>
                {p.published ? 'PUBLISHED' : 'DRAFT'}
              </div>
              <div style={{display:'flex', gap:4}}>
                <button className="btn-sm" style={{padding:'4px 8px', fontSize:11}} onClick={(e) => { e.stopPropagation(); onMove(p.id, -1); }}>↑</button>
                <button className="btn-sm" style={{padding:'4px 8px', fontSize:11}} onClick={(e) => { e.stopPropagation(); onMove(p.id, 1); }}>↓</button>
              </div>
            </div>
            <div className="li-title">{p.title}</div>
            <div className="li-meta">{p.date} · /{p.slug}</div>
          </div>
        ))}
        {posts.length === 0 && (
          <div style={{padding:'40px 14px', textAlign:'center', color:'var(--ink-2)', fontSize:14}}>
            Заметок нет. Нажмите «+ Новая заметка».
          </div>
        )}
      </div>

      <div className="admin-edit">
        {active ? (
          <React.Fragment>
            <div style={{display:'flex', justifyContent:'space-between', alignItems:'flex-start', marginBottom: 18, gap: 12, flexWrap: 'wrap'}}>
              <div style={{minWidth: 0, flex: 1}}>
                <div style={{fontFamily:'var(--font-mono)', fontSize:11, color:'var(--ink-2)', letterSpacing:'.1em'}}>
                  ЗАМЕТКА · #{active.id} · /blog/{active.slug}
                </div>
                <div style={{fontSize:22, marginTop:4, letterSpacing:'-0.01em', wordBreak: 'break-word'}}>{active.title}</div>
              </div>
              <div style={{display: 'flex', gap: 8, alignItems: 'center'}}>
                <label style={{display:'flex', alignItems:'center', gap:6, fontSize:12, color:'var(--ink-1)', cursor:'pointer'}}>
                  <input type="checkbox" checked={!!active.published}
                    onChange={e => onUpdate('published', e.target.checked)}
                    style={{width: 16, height: 16}}/>
                  Опубликовано
                </label>
                {active.published && (
                  <a href={'/blog/' + active.slug} target="_blank" rel="noopener noreferrer"
                    className="btn-sm" style={{fontSize: 12}}>Открыть ↗</a>
                )}
                <button className="btn-sm danger" onClick={() => onRemove(active.id)}>Удалить</button>
              </div>
            </div>

            <div className="grid-2">
              <div className="field">
                <label>Заголовок</label>
                <input value={active.title} onChange={e => onUpdate('title', e.target.value)}/>
              </div>
              <div className="field">
                <label>URL-slug</label>
                <input value={active.slug} onChange={e => onUpdate('slug', e.target.value)}
                  placeholder="adr-001-edge-cache"/>
              </div>
            </div>

            <div className="grid-2">
              <div className="field">
                <label>Дата (YYYY-MM-DD)</label>
                <input type="date" value={active.date} onChange={e => onUpdate('date', e.target.value)}/>
              </div>
              <div className="field">
                <label>Теги (через запятую)</label>
                <input
                  value={(active.tags || []).join(', ')}
                  onChange={e => onUpdate('tags', e.target.value.split(',').map(s => s.trim()).filter(Boolean))}
                  placeholder="ADR, CDN, архитектура"/>
              </div>
            </div>

            <div className="field">
              <label>Краткое описание (excerpt)</label>
              <textarea value={active.excerpt} onChange={e => onUpdate('excerpt', e.target.value)}
                style={{minHeight: 70}}
                placeholder="1–2 предложения, что внутри. Показывается в индексе блога."/>
            </div>

            <div className="field">
              <label>Тело заметки (Markdown-light)</label>
              <textarea value={active.body} onChange={e => onUpdate('body', e.target.value)}
                style={{minHeight: 320, fontFamily: 'var(--font-mono)', fontSize: 13}}
                placeholder={'## Контекст\n\nОбычный абзац...\n\n## Решение\n\n- пункт списка\n- ещё пункт'}/>
              <div style={{
                marginTop: 8, fontFamily: 'var(--font-mono)', fontSize: 11,
                color: 'var(--ink-2)', letterSpacing: '.02em', lineHeight: 1.6,
              }}>
                Синтаксис: «## Заголовок» → h2 · «- пункт» → пункт списка · пустая строка разделяет блоки · остальное — абзац.
              </div>
            </div>
          </React.Fragment>
        ) : (
          <div style={{padding:'60px 14px', textAlign:'center', color:'var(--ink-2)'}}>
            Выберите заметку слева или создайте новую.
          </div>
        )}
      </div>
    </div>
  );
}

window.Admin = Admin;
window.AdminLogin = AdminLogin;
