/* global React, ReactDOM */
const { useState, useEffect, useRef, useCallback } = React;

// ============================================================================
// THEME
// ============================================================================
const themes = {
  light: {
    bg: '#F5F1EB', bgElevated: '#FAF7F2', card: '#FFFFFF', cardSubtle: '#F0EBE3',
    border: 'rgba(63,43,22,0.08)', borderStrong: 'rgba(63,43,22,0.18)',
    text: '#2B1810', textDim: '#5C4A3D', textMute: '#8A7866',
    primary: '#C96442', primaryHover: '#B5563A', primaryDim: 'rgba(201,100,66,0.12)',
    weather: '#5B7FB5', reminder: '#C96442', camera: '#6B8E7F',
    success: '#5C8A5E', error: '#B85450', warn: '#C9893E',
    inputBg: 'rgba(63,43,22,0.04)', chatBg: '#EDE6DA',
  },
  dark: {
    bg: '#1F1E1C', bgElevated: '#262522', card: '#2D2C29', cardSubtle: '#252421',
    border: 'rgba(245,235,215,0.07)', borderStrong: 'rgba(245,235,215,0.14)',
    text: '#F5EBD7', textDim: '#B8AC95', textMute: '#857B69',
    primary: '#D97757', primaryHover: '#E08768', primaryDim: 'rgba(217,119,87,0.16)',
    weather: '#7B9DC9', reminder: '#D97757', camera: '#8DAA9A',
    success: '#7BAE7C', error: '#D4756F', warn: '#E0A05A',
    inputBg: 'rgba(0,0,0,0.22)', chatBg: '#1A1917',
  },
};

const ThemeCtx = React.createContext(themes.light);
const useTheme = () => React.useContext(ThemeCtx);

// ============================================================================
// API HELPER
// ============================================================================
async function api(method, path, body) {
  const opts = {
    method,
    headers: { 'Content-Type': 'application/json' },
    credentials: 'same-origin',
  };
  if (body !== undefined) opts.body = JSON.stringify(body);
  const res = await fetch('/api' + path, opts);
  if (res.status === 401) {
    window._onUnauth?.();
    throw new Error('Не авторизован');
  }
  const data = await res.json();
  if (!res.ok) throw new Error(data.error || 'Ошибка сервера');
  return data;
}
const apiGet  = (p)    => api('GET', p);
const apiPost = (p, b) => api('POST', p, b);
const apiPut  = (p, b) => api('PUT', p, b);
const apiDel  = (p)    => api('DELETE', p);

// ============================================================================
// ICONS
// ============================================================================
const Icon = ({ name, size = 18, color = 'currentColor', strokeWidth = 1.75 }) => {
  const p = {
    dashboard: <><rect x="3" y="3" width="7" height="9" rx="1.5"/><rect x="14" y="3" width="7" height="5" rx="1.5"/><rect x="14" y="12" width="7" height="9" rx="1.5"/><rect x="3" y="16" width="7" height="5" rx="1.5"/></>,
    weather: <><path d="M7 18a4 4 0 010-8 5 5 0 019.6-1.5A4.5 4.5 0 0118 18H7z"/><path d="M12 6V4M5.5 7.5L4 6M18.5 7.5L20 6"/></>,
    bell: <><path d="M6 8a6 6 0 0112 0c0 7 3 7 3 9H3c0-2 3-2 3-9z"/><path d="M10 21a2 2 0 004 0"/></>,
    camera: <><path d="M3 7h4l2-2h6l2 2h4v12H3z"/><circle cx="12" cy="13" r="4"/></>,
    settings: <><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 00.3 1.8l.1.1a2 2 0 11-2.8 2.8l-.1-.1a1.7 1.7 0 00-1.8-.3 1.7 1.7 0 00-1 1.5V21a2 2 0 11-4 0v-.1a1.7 1.7 0 00-1.1-1.5 1.7 1.7 0 00-1.8.3l-.1.1a2 2 0 11-2.8-2.8l.1-.1a1.7 1.7 0 00.3-1.8 1.7 1.7 0 00-1.5-1H3a2 2 0 010-4h.1a1.7 1.7 0 001.5-1.1 1.7 1.7 0 00-.3-1.8l-.1-.1a2 2 0 112.8-2.8l.1.1a1.7 1.7 0 001.8.3H9a1.7 1.7 0 001-1.5V3a2 2 0 014 0v.1a1.7 1.7 0 001 1.5 1.7 1.7 0 001.8-.3l.1-.1a2 2 0 112.8 2.8l-.1.1a1.7 1.7 0 00-.3 1.8V9a1.7 1.7 0 001.5 1H21a2 2 0 010 4h-.1a1.7 1.7 0 00-1.5 1z"/></>,
    users: <><path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75"/></>,
    plus: <><path d="M12 5v14M5 12h14"/></>,
    edit: <><path d="M11 4H4v16h16v-7"/><path d="M18.5 2.5a2.12 2.12 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></>,
    trash: <><path d="M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/></>,
    eye: <><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8S1 12 1 12z"/><circle cx="12" cy="12" r="3"/></>,
    eyeOff: <><path d="M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19m-6.72-1.07a3 3 0 11-4.24-4.24"/><path d="M1 1l22 22"/></>,
    check: <><path d="M20 6L9 17l-5-5"/></>,
    x: <><path d="M18 6L6 18M6 6l12 12"/></>,
    chevronDown: <><path d="M6 9l6 6 6-6"/></>,
    refresh: <><path d="M23 4v6h-6M1 20v-6h6"/><path d="M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"/></>,
    play: <><path d="M5 3l14 9-14 9V3z" fill="currentColor"/></>,
    send: <><path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/></>,
    activity: <><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></>,
    clock: <><circle cx="12" cy="12" r="9"/><path d="M12 7v5l3 2"/></>,
    image: <><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="M21 15l-5-5L5 21"/></>,
    shield: <><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></>,
    wifi: <><path d="M5 12.55a11 11 0 0114 0M8.5 16.05a6 6 0 017 0M2 8.82a15 15 0 0120 0"/><circle cx="12" cy="20" r="1" fill="currentColor"/></>,
    mapPin: <><path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0118 0z"/><circle cx="12" cy="10" r="3"/></>,
    sun: <><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41"/></>,
    moon: <><path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/></>,
    menu: <><path d="M3 12h18M3 6h18M3 18h18"/></>,
    download: <><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3"/></>,
    key: <><path d="M21 2l-2 2m-7.61 7.61a5.5 5.5 0 11-7.778 7.778 5.5 5.5 0 017.777-7.777zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3m-3.5 3.5L19 4"/></>,
    logOut: <><path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4M16 17l5-5-5-5M21 12H9"/></>,
  };
  return (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={color}
      strokeWidth={strokeWidth} strokeLinecap="round" strokeLinejoin="round" style={{flexShrink:0}}>
      {p[name] || null}
    </svg>
  );
};

// ============================================================================
// PRIMITIVES
// ============================================================================
const Card = ({ children, style, ...rest }) => {
  const t = useTheme();
  return <div {...rest} style={{ background: t.card, border: `1px solid ${t.border}`, borderRadius: 12, boxShadow: '0 1px 2px rgba(0,0,0,0.04)', ...style }}>{children}</div>;
};

const Button = ({ children, variant = 'primary', size = 'md', icon, onClick, disabled, style, loading, ...rest }) => {
  const t = useTheme();
  const sizes = { sm: { padding: '6px 12px', fontSize: 12, height: 30 }, md: { padding: '8px 14px', fontSize: 13, height: 36 }, lg: { padding: '10px 18px', fontSize: 14, height: 42 } };
  const variants = {
    primary: { background: t.primary, color: '#fff', border: `1px solid ${t.primary}` },
    ghost: { background: 'transparent', color: t.text, border: `1px solid ${t.borderStrong}` },
    danger: { background: 'transparent', color: t.error, border: `1px solid ${t.error}40` },
    subtle: { background: t.inputBg, color: t.text, border: `1px solid ${t.border}` },
  };
  return (
    <button onClick={onClick} disabled={disabled || loading} {...rest} style={{
      display:'inline-flex', alignItems:'center', gap:6, borderRadius:8,
      fontWeight:500, fontFamily:'inherit', cursor: (disabled||loading)?'not-allowed':'pointer',
      opacity: (disabled||loading) ? 0.5 : 1, transition:'all 0.15s', whiteSpace:'nowrap',
      ...sizes[size], ...variants[variant], ...style,
    }}
    onMouseEnter={e => { if(!disabled&&!loading) { if(variant==='primary') e.currentTarget.style.background=t.primaryHover; else e.currentTarget.style.filter='brightness(0.97)'; } }}
    onMouseLeave={e => { if(variant==='primary') e.currentTarget.style.background=t.primary; else e.currentTarget.style.filter='none'; }}
    >
      {loading ? <span style={{ width:13, height:13, borderRadius:'50%', border:`2px solid ${t.border}`, borderTopColor:'currentColor', animation:'spin 0.8s linear infinite', display:'inline-block' }} /> : icon && <Icon name={icon} size={size==='sm'?13:15} />}
      {children}
    </button>
  );
};

const Input = ({ label, hint, error, icon, suffix, textarea, rows = 4, ...rest }) => {
  const t = useTheme();
  const borderRef = useRef(null);
  const borderColor = error ? t.error : t.border;
  if (textarea) return (
    <label style={{ display:'flex', flexDirection:'column', gap:6 }}>
      {label && <span style={{ fontSize:12, color:t.textDim, fontWeight:500 }}>{label}</span>}
      <textarea {...rest} rows={rows} style={{ background:t.inputBg, border:`1px solid ${borderColor}`, borderRadius:8, color:t.text, fontSize:13, fontFamily:'inherit', padding:10, resize:'vertical', outline:'none', ...rest.style }}
        onFocus={e => { e.target.style.borderColor=t.primary; }} onBlur={e => { e.target.style.borderColor=borderColor; }} />
      {error ? <span style={{fontSize:11,color:t.error}}>{error}</span> : hint ? <span style={{fontSize:11,color:t.textMute}}>{hint}</span> : null}
    </label>
  );
  return (
    <label style={{ display:'flex', flexDirection:'column', gap:6 }}>
      {label && <span style={{ fontSize:12, color:t.textDim, fontWeight:500 }}>{label}</span>}
      <div ref={borderRef} style={{ display:'flex', alignItems:'center', gap:8, background:t.inputBg, border:`1px solid ${borderColor}`, borderRadius:8, padding:'0 10px', transition:'border-color 0.15s' }}>
        {icon && <Icon name={icon} size={14} color={t.textMute} />}
        <input {...rest} style={{ flex:1, background:'transparent', border:'none', outline:'none', color:t.text, fontSize:13, fontFamily:'inherit', padding:'9px 0', ...rest.style }}
          onFocus={e => { borderRef.current.style.borderColor=t.primary; rest.onFocus?.(e); }}
          onBlur={e => { borderRef.current.style.borderColor=borderColor; rest.onBlur?.(e); }} />
        {suffix}
      </div>
      {error ? <span style={{fontSize:11,color:t.error}}>{error}</span> : hint ? <span style={{fontSize:11,color:t.textMute}}>{hint}</span> : null}
    </label>
  );
};

const Select = ({ label, hint, options, value, onChange }) => {
  const t = useTheme();
  return (
    <label style={{ display:'flex', flexDirection:'column', gap:6 }}>
      {label && <span style={{ fontSize:12, color:t.textDim, fontWeight:500 }}>{label}</span>}
      <div style={{ position:'relative' }}>
        <select value={value} onChange={onChange} style={{ width:'100%', background:t.inputBg, border:`1px solid ${t.border}`, borderRadius:8, color:t.text, fontSize:13, fontFamily:'inherit', padding:'10px 32px 10px 12px', appearance:'none', cursor:'pointer', outline:'none' }}>
          {options.map(o => <option key={o.value??o} value={o.value??o} style={{background:t.card,color:t.text}}>{o.label??o}</option>)}
        </select>
        <div style={{ position:'absolute', right:10, top:'50%', transform:'translateY(-50%)', pointerEvents:'none', color:t.textMute }}>
          <Icon name="chevronDown" size={14} />
        </div>
      </div>
      {hint && <span style={{fontSize:11,color:t.textMute}}>{hint}</span>}
    </label>
  );
};

const Toggle = ({ checked, onChange, label }) => {
  const t = useTheme();
  return (
    <label style={{ display:'inline-flex', alignItems:'center', gap:10, cursor:'pointer' }}>
      <div onClick={() => onChange(!checked)} style={{ width:36, height:20, borderRadius:10, background:checked?t.primary:t.borderStrong, position:'relative', transition:'all 0.2s', flexShrink:0 }}>
        <div style={{ position:'absolute', top:2, left:checked?18:2, width:16, height:16, borderRadius:'50%', background:'white', transition:'left 0.2s', boxShadow:'0 1px 3px rgba(0,0,0,0.25)' }} />
      </div>
      {label && <span style={{fontSize:13,color:t.text}}>{label}</span>}
    </label>
  );
};

const Checkbox = ({ checked, onChange, label }) => {
  const t = useTheme();
  return (
    <label style={{ display:'inline-flex', alignItems:'center', gap:8, cursor:'pointer', userSelect:'none' }}>
      <div onClick={() => onChange(!checked)} style={{ width:16, height:16, borderRadius:4, background:checked?t.primary:'transparent', border:`1px solid ${checked?t.primary:t.borderStrong}`, display:'flex', alignItems:'center', justifyContent:'center', transition:'all 0.15s', flexShrink:0 }}>
        {checked && <Icon name="check" size={11} color="white" strokeWidth={3} />}
      </div>
      {label && <span style={{fontSize:13,color:t.text}}>{label}</span>}
    </label>
  );
};

const Radio = ({ checked, onChange, label }) => {
  const t = useTheme();
  return (
    <label style={{ display:'inline-flex', alignItems:'center', gap:8, cursor:'pointer', userSelect:'none' }}>
      <div onClick={() => onChange(true)} style={{ width:16, height:16, borderRadius:'50%', background:'transparent', border:`1px solid ${checked?t.primary:t.borderStrong}`, display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0 }}>
        {checked && <div style={{ width:8, height:8, borderRadius:'50%', background:t.primary }} />}
      </div>
      {label && <span style={{fontSize:13,color:t.text}}>{label}</span>}
    </label>
  );
};

const Badge = ({ children, color, dot, style }) => {
  const t = useTheme();
  const c = color || t.textDim;
  return <span style={{ display:'inline-flex', alignItems:'center', gap:5, fontSize:11, fontWeight:500, padding:'3px 8px', borderRadius:6, background:`${c}1f`, color:c, border:`1px solid ${c}33`, ...style }}>
    {dot && <span style={{ width:6, height:6, borderRadius:'50%', background:c }} />}{children}
  </span>;
};

const StatusDot = ({ color, pulse }) => (
  <span style={{ position:'relative', display:'inline-flex', width:8, height:8 }}>
    {pulse && <span style={{ position:'absolute', inset:-2, borderRadius:'50%', background:color, opacity:0.4, animation:'pulse 2s ease-out infinite' }} />}
    <span style={{ width:8, height:8, borderRadius:'50%', background:color, position:'relative' }} />
  </span>
);

const SectionHeader = ({ title, subtitle, right }) => {
  const t = useTheme();
  return (
    <div style={{ display:'flex', justifyContent:'space-between', alignItems:'flex-end', marginBottom:20, gap:16, flexWrap:'wrap' }}>
      <div>
        <h1 style={{ fontSize:22, fontWeight:600, color:t.text, margin:0, letterSpacing:'-0.02em' }}>{title}</h1>
        {subtitle && <p style={{ fontSize:13, color:t.textDim, margin:'4px 0 0' }}>{subtitle}</p>}
      </div>
      {right}
    </div>
  );
};

// ============================================================================
// HELPERS
// ============================================================================
function describeCron(c) {
  if (!c) return '—';
  const map = {
    '30 7 * * 1-5': 'По будням в 07:30', '0 9 * * 0,6': 'По выходным в 09:00',
    '0 19 18,22 * *': '18-го и 22-го числа в 19:00', '0 8 * * *': 'Каждый день в 08:00',
    '0 19 * * *': 'Каждый день в 19:00', '0 19 25 * *': '25-го числа в 19:00',
    '30 9 * * 0,6': 'По выходным в 09:30', '30 7 * * 1-5': 'По будням в 07:30',
    '0 9 * * 6,0': 'По выходным в 09:00',
  };
  return map[c] || `cron: ${c}`;
}

function timeAgo(ts) {
  const diff = Math.floor(Date.now() / 1000) - ts;
  if (diff < 60) return `${diff} с назад`;
  if (diff < 3600) return `${Math.floor(diff/60)} мин назад`;
  if (diff < 86400) return `${Math.floor(diff/3600)} ч назад`;
  return `${Math.floor(diff/86400)} д назад`;
}

// ============================================================================
// TOAST
// ============================================================================
const ToastCtx = React.createContext({ toast: () => {} });
const useToast = () => React.useContext(ToastCtx).toast;

const ToastProvider = ({ children }) => {
  const [toasts, setToasts] = useState([]);
  const toast = useCallback((msg, kind = 'success') => {
    const id = Date.now() + Math.random();
    setToasts(ts => [...ts, { id, msg, kind }]);
    setTimeout(() => setToasts(ts => ts.filter(x => x.id !== id)), 3500);
  }, []);
  const t = useTheme();
  return (
    <ToastCtx.Provider value={{ toast }}>
      {children}
      <div style={{ position:'fixed', top:18, right:18, zIndex:1000, display:'flex', flexDirection:'column', gap:8, pointerEvents:'none' }}>
        {toasts.map(x => (
          <div key={x.id} style={{ display:'flex', alignItems:'center', gap:10, padding:'10px 14px', background:t.bgElevated, border:`1px solid ${x.kind==='success'?t.success:t.error}40`, borderLeft:`3px solid ${x.kind==='success'?t.success:t.error}`, borderRadius:8, boxShadow:'0 12px 32px -8px rgba(0,0,0,0.18)', color:t.text, fontSize:12.5, minWidth:280, animation:'slideIn 0.25s ease' }}>
            <div style={{ color: x.kind==='success'?t.success:t.error }}><Icon name={x.kind==='success'?'check':'x'} size={15} strokeWidth={2.5} /></div>
            {x.msg}
          </div>
        ))}
      </div>
    </ToastCtx.Provider>
  );
};

// ============================================================================
// LOGIN PAGE
// ============================================================================
const LoginPage = ({ onLogin }) => {
  const t = useTheme();
  const toast = useToast();
  const [username, setUsername] = useState('');
  const [password, setPassword] = useState('');
  const [showPwd, setShowPwd] = useState(false);
  const [loading, setLoading] = useState(false);

  const submit = async (e) => {
    e?.preventDefault();
    if (!username || !password) { toast('Введите логин и пароль', 'error'); return; }
    setLoading(true);
    try {
      const res = await fetch('/api/auth/login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({ username, password }),
      });
      const data = await res.json();
      if (!res.ok) { toast(data.error || 'Ошибка', 'error'); return; }
      onLogin(data);
    } catch (e) {
      toast('Ошибка соединения', 'error');
    } finally {
      setLoading(false);
    }
  };

  return (
    <div style={{ minHeight:'100vh', background:t.bg, display:'flex', alignItems:'center', justifyContent:'center', padding:20 }}>
      <div style={{ width:'100%', maxWidth:380, animation:'fadeIn 0.3s ease' }}>
        <div style={{ textAlign:'center', marginBottom:32 }}>
          <div style={{ width:56, height:56, borderRadius:16, background:t.primary, display:'inline-flex', alignItems:'center', justifyContent:'center', fontWeight:700, fontSize:20, color:'white', letterSpacing:'0.02em', boxShadow:`0 8px 24px -8px ${t.primary}80`, marginBottom:16 }}>ЗГ</div>
          <div style={{ fontSize:22, fontWeight:600, color:t.text, letterSpacing:'-0.02em' }}>Доска СНТ Змеиная горка</div>
          <div style={{ fontSize:13, color:t.textMute, marginTop:4 }}>СНТ Змеиная горка · Вход</div>
        </div>

        <Card style={{ padding:28 }}>
          <form onSubmit={submit} style={{ display:'flex', flexDirection:'column', gap:16 }}>
            <Input label="Логин" value={username} onChange={e => setUsername(e.target.value)} icon="shield" placeholder="root" autoComplete="username" autoFocus />
            <Input label="Пароль" value={password} onChange={e => setPassword(e.target.value)} type={showPwd?'text':'password'} icon="key" placeholder="••••••••"
              autoComplete="current-password"
              suffix={
                <button type="button" onClick={() => setShowPwd(!showPwd)} style={{ background:'transparent', border:'none', color:t.textMute, cursor:'pointer', padding:4 }}>
                  <Icon name={showPwd?'eyeOff':'eye'} size={14} />
                </button>
              }
            />
            <Button size="lg" onClick={submit} loading={loading} style={{ marginTop:4, justifyContent:'center' }}>Войти</Button>
          </form>
        </Card>

        <div style={{ textAlign:'center', marginTop:20, fontSize:11, color:t.textMute }}>Доска ЗГ v1.0 · Домашняя автоматизация</div>
      </div>
    </div>
  );
};

// ============================================================================
// SIDEBAR
// ============================================================================
const Sidebar = ({ page, setPage, collapsed, user, onLogout }) => {
  const t = useTheme();
  const items = [
    { id:'dashboard', label:'Обзор', icon:'dashboard' },
    { id:'weather', label:'Погода', icon:'weather' },
    { id:'reminders', label:'Напоминания', icon:'bell' },
    { id:'camera', label:'Камера', icon:'camera' },
    { id:'settings', label:'Настройки', icon:'settings' },
  ];
  return (
    <aside style={{ width:collapsed?68:230, background:t.bgElevated, borderRight:`1px solid ${t.border}`, display:'flex', flexDirection:'column', padding:'20px 12px', gap:4, transition:'width 0.2s ease', flexShrink:0 }}>
      <div style={{ display:'flex', alignItems:'center', gap:10, padding:'4px 10px 20px' }}>
        <div style={{ width:32, height:32, borderRadius:8, background:t.primary, display:'flex', alignItems:'center', justifyContent:'center', fontWeight:700, fontSize:13, color:'white', boxShadow:`0 4px 14px -4px ${t.primary}80`, flexShrink:0 }}>ЗГ</div>
        {!collapsed && <div><div style={{ fontSize:14, fontWeight:600, color:t.text, lineHeight:1.1 }}>Доска</div><div style={{ fontSize:10.5, color:t.textMute, letterSpacing:'0.04em', textTransform:'uppercase', marginTop:2 }}>СНТ Змеиная горка</div></div>}
      </div>

      {!collapsed && <div style={{ fontSize:10, fontWeight:600, color:t.textMute, letterSpacing:'0.08em', textTransform:'uppercase', padding:'12px 10px 6px' }}>Разделы</div>}

      {items.map(item => {
        const active = page === item.id;
        return (
          <button key={item.id} onClick={() => setPage(item.id)} style={{ display:'flex', alignItems:'center', gap:12, padding:'9px 10px', borderRadius:8, border:'none', background:active?t.primaryDim:'transparent', color:active?t.primary:t.textDim, fontSize:13, fontWeight:active?600:500, cursor:'pointer', fontFamily:'inherit', textAlign:'left', transition:'all 0.15s', position:'relative' }}
            onMouseEnter={e => { if(!active) { e.currentTarget.style.background=t.inputBg; e.currentTarget.style.color=t.text; } }}
            onMouseLeave={e => { if(!active) { e.currentTarget.style.background='transparent'; e.currentTarget.style.color=t.textDim; } }}
          >
            {active && <span style={{ position:'absolute', left:-12, top:8, bottom:8, width:3, background:t.primary, borderRadius:'0 3px 3px 0' }} />}
            <Icon name={item.icon} size={17} />
            {!collapsed && item.label}
          </button>
        );
      })}

      <div style={{ flex:1 }} />

      {!collapsed && user && (
        <div style={{ padding:'10px 10px', borderTop:`1px solid ${t.border}`, display:'flex', alignItems:'center', gap:8 }}>
          <div style={{ width:28, height:28, borderRadius:'50%', background:t.primaryDim, display:'flex', alignItems:'center', justifyContent:'center', color:t.primary, fontSize:12, fontWeight:600, flexShrink:0 }}>
            {user.username?.[0]?.toUpperCase()}
          </div>
          <div style={{ flex:1, minWidth:0 }}>
            <div style={{ fontSize:12, fontWeight:600, color:t.text, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{user.username}</div>
            <div style={{ fontSize:10.5, color:t.textMute }}>{user.role === 'admin' ? 'Администратор' : 'Пользователь'}</div>
          </div>
          <button onClick={onLogout} title="Выйти" style={{ background:'transparent', border:'none', color:t.textMute, cursor:'pointer', padding:4, borderRadius:6 }}
            onMouseEnter={e => e.currentTarget.style.color=t.error}
            onMouseLeave={e => e.currentTarget.style.color=t.textMute}
          ><Icon name="logOut" size={15} /></button>
        </div>
      )}
    </aside>
  );
};

// ============================================================================
// HEADER
// ============================================================================
const Header = ({ onMenuClick, mode, setMode }) => {
  const t = useTheme();
  const [now, setNow] = useState(new Date());
  useEffect(() => { const i = setInterval(() => setNow(new Date()), 1000); return () => clearInterval(i); }, []);
  const tz = 'Asia/Yekaterinburg';
  const time = now.toLocaleTimeString('ru-RU', { timeZone:tz, hour:'2-digit', minute:'2-digit', second:'2-digit' });
  const date = now.toLocaleDateString('ru-RU', { timeZone:tz, weekday:'short', day:'numeric', month:'short' });
  return (
    <header style={{ height:60, borderBottom:`1px solid ${t.border}`, background:t.bg, display:'flex', alignItems:'center', padding:'0 24px', gap:16, flexShrink:0 }}>
      <button onClick={onMenuClick} style={{ background:'transparent', border:'none', color:t.textDim, cursor:'pointer', padding:6, borderRadius:6 }}><Icon name="menu" size={18} /></button>
      <div style={{ flex:1 }} />
      <div style={{ display:'flex', alignItems:'center', gap:14 }}>
        <div style={{ width:1, height:22, background:t.border }} />
        <div style={{ display:'flex', alignItems:'center', gap:8 }}>
          <Icon name="clock" size={14} color={t.textMute} />
          <div style={{ display:'flex', flexDirection:'column', alignItems:'flex-end' }}>
            <div style={{ fontSize:13, fontWeight:600, color:t.text, fontVariantNumeric:'tabular-nums' }}>{time}</div>
            <div style={{ fontSize:10.5, color:t.textMute }}>{date} · Екатеринбург</div>
          </div>
        </div>
        <div style={{ width:1, height:22, background:t.border }} />
        <button onClick={() => setMode(mode==='light'?'dark':'light')} style={{ display:'inline-flex', alignItems:'center', gap:6, padding:'6px 10px', background:'transparent', border:`1px solid ${t.borderStrong}`, borderRadius:8, color:t.textDim, cursor:'pointer', fontFamily:'inherit', fontSize:12, transition:'all 0.15s' }}
          onMouseEnter={e => { e.currentTarget.style.background=t.inputBg; e.currentTarget.style.color=t.text; }}
          onMouseLeave={e => { e.currentTarget.style.background='transparent'; e.currentTarget.style.color=t.textDim; }}
        ><Icon name={mode==='light'?'moon':'sun'} size={14} /></button>
      </div>
    </header>
  );
};

// ============================================================================
// DASHBOARD
// ============================================================================
const MODULES = { weather:{label:'Погода',icon:'weather'}, reminder:{label:'Напоминания',icon:'bell'}, camera:{label:'Камера',icon:'camera'} };

const Sparkline = ({ data, color, height=32 }) => {
  const max = Math.max(...data, 1);
  const w = 100;
  const pts = data.map((v,i) => `${(i/(data.length-1))*w},${height-(v/max)*(height-4)-2}`).join(' ');
  return (
    <svg width="100%" height={height} viewBox={`0 0 ${w} ${height}`} preserveAspectRatio="none" style={{overflow:'visible'}}>
      <polyline points={`0,${height} ${pts} ${w},${height}`} fill={`${color}30`} stroke="none" />
      <polyline points={pts} fill="none" stroke={color} strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="round" />
    </svg>
  );
};

const StatCard = ({ label, value, sub, accent, icon, sparkline }) => {
  const t = useTheme();
  return (
    <Card style={{ padding:18, overflow:'hidden' }}>
      <div style={{ display:'flex', alignItems:'flex-start', justifyContent:'space-between', marginBottom:14 }}>
        <span style={{ fontSize:11.5, color:t.textDim, fontWeight:500 }}>{label}</span>
        {icon && <div style={{ width:28, height:28, borderRadius:7, background:`${accent}1a`, display:'flex', alignItems:'center', justifyContent:'center', color:accent }}><Icon name={icon} size={14} /></div>}
      </div>
      <div style={{ display:'flex', alignItems:'flex-end', justifyContent:'space-between', gap:12 }}>
        <div style={{ fontSize:28, fontWeight:600, color:t.text, letterSpacing:'-0.02em', lineHeight:1, fontVariantNumeric:'tabular-nums' }}>{value}</div>
        {sparkline && <div style={{ width:80, height:32 }}><Sparkline data={sparkline} color={accent} /></div>}
      </div>
      {sub && <div style={{ fontSize:11.5, color:t.textMute, marginTop:8 }}>{sub}</div>}
    </Card>
  );
};

const ActivityFeed = ({ items, filter, setFilter }) => {
  const t = useTheme();
  const mColors = { weather:t.weather, reminder:t.reminder, camera:t.camera };
  const filtered = filter==='all' ? items : items.filter(i => i.module===filter);
  return (
    <Card style={{ overflow:'hidden' }}>
      <div style={{ padding:'16px 18px', borderBottom:`1px solid ${t.border}`, display:'flex', alignItems:'center', justifyContent:'space-between', gap:12, flexWrap:'wrap' }}>
        <div><div style={{ fontSize:14, fontWeight:600, color:t.text }}>Лента событий</div><div style={{ fontSize:11.5, color:t.textMute, marginTop:2 }}>Журнал всех модулей</div></div>
        <div style={{ display:'flex', gap:4, padding:3, background:t.inputBg, borderRadius:8 }}>
          {['all','weather','reminder','camera'].map(f => (
            <button key={f} onClick={() => setFilter(f)} style={{ padding:'5px 10px', fontSize:11.5, fontWeight:500, borderRadius:6, border:'none', background:filter===f?t.card:'transparent', color:filter===f?t.text:t.textDim, cursor:'pointer', fontFamily:'inherit', boxShadow:filter===f?'0 1px 2px rgba(0,0,0,0.06)':'none' }}>
              {f==='all'?'Все':MODULES[f]?.label}
            </button>
          ))}
        </div>
      </div>
      <div style={{ maxHeight:360, overflowY:'auto' }}>
        {filtered.length === 0 && <div style={{ padding:'32px', textAlign:'center', color:t.textMute, fontSize:13 }}>Событий пока нет</div>}
        {filtered.map((item, i) => {
          const m = MODULES[item.module] || { label:item.module, icon:'activity' };
          const c = mColors[item.module] || t.textDim;
          return (
            <div key={item.id || i} style={{ display:'flex', alignItems:'center', gap:14, padding:'12px 18px', borderBottom:i<filtered.length-1?`1px solid ${t.border}`:'none', transition:'background 0.1s' }}
              onMouseEnter={e => e.currentTarget.style.background=t.inputBg}
              onMouseLeave={e => e.currentTarget.style.background='transparent'}
            >
              <div style={{ width:32, height:32, borderRadius:8, background:`${c}1a`, display:'flex', alignItems:'center', justifyContent:'center', color:c, flexShrink:0 }}><Icon name={m.icon} size={15} /></div>
              <div style={{ flex:1, minWidth:0 }}>
                <div style={{ fontSize:13, color:t.text, marginBottom:2 }}>{item.description}</div>
                <div style={{ fontSize:11, color:t.textMute, display:'flex', gap:8 }}><span>{m.label}</span><span>·</span><span>{timeAgo(item.created_at)}</span></div>
              </div>
              <Badge color={item.status==='success'?t.success:t.error} dot>{item.status==='success'?'Успех':'Ошибка'}</Badge>
            </div>
          );
        })}
      </div>
    </Card>
  );
};

const BarChart = ({ data }) => {
  const t = useTheme();
  const colors = [t.weather, t.reminder, t.camera];
  const [hover, setHover] = useState(null);
  const max = Math.max(...data.map(d => d[0]+d[1]+d[2]), 1);
  return (
    <Card style={{ padding:18 }}>
      <div style={{ display:'flex', alignItems:'flex-start', justifyContent:'space-between', marginBottom:18, gap:12, flexWrap:'wrap' }}>
        <div><div style={{ fontSize:14, fontWeight:600, color:t.text }}>События по дням</div><div style={{ fontSize:11.5, color:t.textMute, marginTop:2 }}>За последние 30 дней</div></div>
        <div style={{ display:'flex', gap:14, fontSize:11, color:t.textDim }}>
          {Object.entries(MODULES).map(([k,m],i) => <div key={k} style={{ display:'flex', alignItems:'center', gap:6 }}><div style={{ width:8, height:8, borderRadius:2, background:colors[i] }} />{m.label}</div>)}
        </div>
      </div>
      <div style={{ position:'relative', height:160, display:'flex', alignItems:'flex-end', gap:4 }}>
        {data.map((day, i) => {
          const total = day[0]+day[1]+day[2];
          const isHover = hover===i;
          return (
            <div key={i} onMouseEnter={() => setHover(i)} onMouseLeave={() => setHover(null)}
              style={{ flex:1, display:'flex', flexDirection:'column-reverse', height:'100%', cursor:'pointer', position:'relative', opacity:hover!==null&&!isHover?0.4:1, transition:'opacity 0.15s' }}>
              {day.map((v,j) => v > 0 && <div key={j} style={{ height:`${(v/max)*100}%`, background:colors[j], borderRadius:j===0?'0 0 2px 2px':0 }} />)}
              {isHover && <div style={{ position:'absolute', bottom:'100%', left:'50%', transform:'translateX(-50%)', marginBottom:6, background:t.bgElevated, border:`1px solid ${t.borderStrong}`, borderRadius:6, padding:'6px 10px', fontSize:11, whiteSpace:'nowrap', zIndex:10, color:t.text, boxShadow:'0 8px 24px rgba(0,0,0,0.18)' }}>
                <div style={{ fontWeight:600, marginBottom:4 }}>День {30-i} · {total} событ.</div>
                {Object.keys(MODULES).map((k,idx) => day[idx]>0&&<div key={k} style={{ display:'flex', alignItems:'center', gap:6, color:t.textDim }}><div style={{ width:6, height:6, borderRadius:1, background:colors[idx] }} />{MODULES[k].label}: {day[idx]}</div>)}
              </div>}
            </div>
          );
        })}
      </div>
      <div style={{ display:'flex', justifyContent:'space-between', marginTop:8, fontSize:10, color:t.textMute }}><span>30 дн. назад</span><span>15 дн.</span><span>Сегодня</span></div>
    </Card>
  );
};

const DashboardPage = () => {
  const t = useTheme();
  const toast = useToast();
  const [filter, setFilter] = useState('all');
  const [stats, setStats] = useState({ today:0, month:0, lastEvent:null });
  const [feed, setFeed] = useState([]);
  const [chart, setChart] = useState(Array(30).fill([0,0,0]));
  const [loading, setLoading] = useState(true);

  const load = async () => {
    try {
      const [s, f, c] = await Promise.all([apiGet('/dashboard/stats'), apiGet('/dashboard/feed?limit=50'), apiGet('/dashboard/chart')]);
      setStats(s); setFeed(f); setChart(c);
    } catch(e) { toast(e.message,'error'); } finally { setLoading(false); }
  };
  useEffect(() => { load(); const i = setInterval(load, 30000); return () => clearInterval(i); }, []);

  const lastModule = stats.lastEvent?.module;
  const lastDesc = stats.lastEvent?.description || '—';
  const lastAgo = stats.lastEvent ? timeAgo(stats.lastEvent.created_at) : '—';

  if (loading) return <div style={{ display:'flex', alignItems:'center', justifyContent:'center', height:200, color:t.textMute }}>Загрузка…</div>;
  return (
    <div>
      <SectionHeader title="Обзор" subtitle="Сводка по всем модулям автоматизации" right={<Button variant="ghost" icon="refresh" size="sm" onClick={load}>Обновить</Button>} />
      <div style={{ display:'grid', gridTemplateColumns:'repeat(4,1fr)', gap:14, marginBottom:16 }}>
        <StatCard label="Сообщений сегодня" value={stats.today} accent={t.primary} icon="send" sparkline={chart.slice(-12).map(d=>d[0]+d[1]+d[2])} />
        <StatCard label="Сообщений за месяц" value={stats.month} accent={t.camera} icon="activity" />
        <StatCard label="Последняя активность" value={lastAgo} sub={lastDesc.slice(0,40)} accent={t.reminder} icon="clock" />
        <StatCard label="Последний модуль" value={lastModule?MODULES[lastModule]?.label||lastModule:'—'} accent={t.weather} icon={lastModule?MODULES[lastModule]?.icon:'activity'} />
      </div>
      <div style={{ marginBottom:16 }}><ActivityFeed items={feed} filter={filter} setFilter={setFilter} /></div>
      <BarChart data={chart} />
    </div>
  );
};

// ============================================================================
// CHAT BUBBLE
// ============================================================================
const ChatBubble = ({ children, modKey='weather' }) => {
  const t = useTheme();
  const mColor = { weather:t.weather, reminder:t.reminder, camera:t.camera }[modKey] || t.primary;
  return (
    <div style={{ background:t.card, border:`1px solid ${t.border}`, borderRadius:'14px 14px 14px 4px', padding:'12px 14px', fontSize:13, color:t.text, lineHeight:1.65, fontFamily: modKey==='weather' ? '"JetBrains Mono","SF Mono",Menlo,monospace' : 'Inter,system-ui,sans-serif', whiteSpace:'pre-wrap', overflowWrap:'break-word', wordBreak:'break-word', boxShadow:'0 1px 2px rgba(0,0,0,0.04)' }}>
      <div style={{ display:'flex', alignItems:'center', gap:8, paddingBottom:8, marginBottom:8, borderBottom:`1px solid ${t.border}`, fontFamily:'Inter,sans-serif' }}>
        <div style={{ width:24, height:24, borderRadius:'50%', background:mColor, display:'flex', alignItems:'center', justifyContent:'center' }}><Icon name={MODULES[modKey]?.icon||'send'} size={12} color="white" /></div>
        <div><div style={{ fontSize:12, fontWeight:600, color:t.text }}>ZG Bot</div><div style={{ fontSize:10, color:t.textMute }}>через MAX</div></div>
      </div>
      {children}
    </div>
  );
};

// MAX Messenger mobile preview mockup
const MaxPhoneMockup = ({ text }) => {
  const timeStr = new Date().toLocaleTimeString('ru-RU', { hour:'2-digit', minute:'2-digit' });
  const hasText = text && text.trim().length > 0;
  return (
    <div style={{ width:244, margin:'0 auto' }}>
      {/* Phone frame */}
      <div style={{ background:'#18181B', borderRadius:40, padding:'12px 7px 16px', boxShadow:'0 0 0 2px #3F3F46, 0 24px 64px rgba(0,0,0,0.55)', position:'relative' }}>
        {/* Speaker notch */}
        <div style={{ width:64, height:16, background:'#27272A', borderRadius:8, margin:'0 auto 10px', display:'flex', alignItems:'center', justifyContent:'center', gap:6 }}>
          <div style={{ width:6, height:6, borderRadius:'50%', background:'#3F3F46' }} />
          <div style={{ width:28, height:4, borderRadius:2, background:'#3F3F46' }} />
        </div>
        {/* Screen */}
        <div style={{ background:'#0F0F14', borderRadius:28, overflow:'hidden' }}>
          {/* Status bar */}
          <div style={{ background:'#141420', padding:'7px 16px 5px', display:'flex', justifyContent:'space-between', alignItems:'center' }}>
            <span style={{ fontSize:9, fontWeight:700, color:'#E4E4E7', letterSpacing:0.3 }}>{timeStr}</span>
            <div style={{ display:'flex', gap:4, alignItems:'center' }}>
              <svg width="10" height="8" viewBox="0 0 10 8" fill="none"><rect x="0" y="3" width="2" height="5" rx="0.5" fill="#E4E4E7"/><rect x="2.5" y="2" width="2" height="6" rx="0.5" fill="#E4E4E7"/><rect x="5" y="1" width="2" height="7" rx="0.5" fill="#E4E4E7"/><rect x="7.5" y="0" width="2" height="8" rx="0.5" fill="#E4E4E7"/></svg>
              <svg width="14" height="8" viewBox="0 0 14 8" fill="none"><rect x="0.5" y="0.5" width="12" height="7" rx="1.5" stroke="#E4E4E7" strokeOpacity="0.4"/><rect x="12.5" y="2.5" width="1" height="3" rx="0.5" fill="#E4E4E7" fillOpacity="0.4"/><rect x="1.5" y="1.5" width="8" height="5" rx="0.8" fill="#E4E4E7"/></svg>
            </div>
          </div>
          {/* Chat header */}
          <div style={{ background:'#141420', padding:'6px 12px 9px', borderBottom:'1px solid rgba(255,255,255,0.05)', display:'flex', alignItems:'center', gap:8 }}>
            <div style={{ width:32, height:32, borderRadius:'50%', background:'linear-gradient(135deg,#3B82F6,#1D4ED8)', display:'flex', alignItems:'center', justifyContent:'center', fontSize:14, flexShrink:0, boxShadow:'0 2px 8px rgba(59,130,246,0.4)' }}>🤖</div>
            <div style={{ flex:1 }}>
              <div style={{ fontSize:11, fontWeight:600, color:'#F4F4F5' }}>ZG Bot</div>
              <div style={{ fontSize:8.5, color:'#52525B' }}>бот</div>
            </div>
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#52525B" strokeWidth="2"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg>
          </div>
          {/* Messages area */}
          <div style={{ background:'#0C0C18', minHeight:180, padding:'12px 8px 8px', backgroundImage:'radial-gradient(rgba(255,255,255,0.015) 1px, transparent 1px)', backgroundSize:'18px 18px' }}>
            {/* Date separator */}
            <div style={{ textAlign:'center', marginBottom:10 }}>
              <span style={{ fontSize:9, color:'#52525B', background:'rgba(255,255,255,0.04)', padding:'2px 10px', borderRadius:10 }}>Сегодня</span>
            </div>
            {/* Bot message bubble */}
            <div style={{ display:'flex', gap:6, alignItems:'flex-end' }}>
              <div style={{ width:22, height:22, borderRadius:'50%', background:'linear-gradient(135deg,#3B82F6,#1D4ED8)', flexShrink:0, display:'flex', alignItems:'center', justifyContent:'center', fontSize:10 }}>🤖</div>
              <div style={{ maxWidth:'84%' }}>
                <div style={{ fontSize:9.5, color:'#A1A1AA', marginBottom:3 }}>ZG Bot</div>
                <div style={{ background:'#1E293B', borderRadius:'12px 12px 12px 2px', padding:'8px 11px', color:'#E2E8F0', fontSize:10.5, lineHeight:1.6, whiteSpace:'pre-wrap', wordBreak:'break-word', boxShadow:'0 2px 8px rgba(0,0,0,0.3)' }}>
                  {hasText ? text : <span style={{ color:'#52525B', fontStyle:'italic' }}>Введите текст напоминания…</span>}
                </div>
                <div style={{ fontSize:8.5, color:'#3F3F46', marginTop:4, paddingLeft:2, display:'flex', alignItems:'center', gap:3 }}>
                  {timeStr}
                </div>
              </div>
            </div>
          </div>
          {/* Input bar */}
          <div style={{ background:'#141420', padding:'8px 10px', borderTop:'1px solid rgba(255,255,255,0.05)', display:'flex', alignItems:'center', gap:6 }}>
            <div style={{ flex:1, background:'rgba(255,255,255,0.05)', borderRadius:20, padding:'6px 12px' }}>
              <div style={{ fontSize:9.5, color:'#52525B' }}>Написать…</div>
            </div>
            <div style={{ width:28, height:28, borderRadius:'50%', background:'#3B82F6', display:'flex', alignItems:'center', justifyContent:'center' }}>
              <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="white" strokeWidth="2.5"><path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z"/></svg>
            </div>
          </div>
        </div>
      </div>
      <div style={{ textAlign:'center', fontSize:10, color:'rgba(90,90,100,0.7)', marginTop:8 }}>MAX Messenger · превью</div>
    </div>
  );
};

// ============================================================================
// WEATHER PAGE
// ============================================================================
const WeatherPage = () => {
  const t = useTheme();
  const toast = useToast();
  const [cfg, setCfg] = useState(null);
  const [showToken, setShowToken] = useState(false);
  const [testing, setTesting] = useState(false);
  const [sending, setSending] = useState(false);
  const [saving, setSaving] = useState(false);

  useEffect(() => { apiGet('/weather').then(setCfg).catch(e => toast(e.message,'error')); }, []);

  if (!cfg) return <div style={{ color:t.textMute, padding:40, textAlign:'center' }}>Загрузка…</div>;

  const up = patch => setCfg(c => ({ ...c, ...patch }));
  const upParams = patch => setCfg(c => ({ ...c, params: { ...c.params, ...patch } }));

  const testConn = async () => {
    setTesting(true);
    try {
      const r = await apiPost('/weather/test', { token: cfg.token, city: cfg.city });
      if (r.ok) toast(`API Gismeteo: соединение OK (${r.ms}мс)`, 'success');
      else toast(`Ошибка: ${r.error}`, 'error');
    } catch(e) { toast(e.message,'error'); } finally { setTesting(false); }
  };

  const save = async () => {
    setSaving(true);
    try { await apiPost('/weather', cfg); toast('Настройки погоды сохранены', 'success'); }
    catch(e) { toast(e.message,'error'); } finally { setSaving(false); }
  };

  const sendNow = async () => {
    setSending(true);
    try { await apiPost('/weather/send'); toast('Прогноз отправлен в чат', 'success'); }
    catch(e) { toast(e.message,'error'); } finally { setSending(false); }
  };

  const previewText = `⛅ 26 апреля, вс\nЕкатеринбург\n\n+12°C  ощущается +9°C\nМалооблачно\n\n💨 3 м/с — умеренный\n💧 Влажность: 55%\n📊 Давление: 748 мм рт.ст.\n☔ Осадки: 10%\n\n📅 Прогноз  10:00 – 23:00\nМалооблачно\n🌡 Макс: +15°C\n🌙 Вечером: +8°C\n☔ Осадки: 12%\n💨 Ветер: 5 м/с\n\n⏱ По часам\n🌤️ 09:00  +11°C  Малооблачно\n☀️ 12:00  +14°C  Ясно\n☀️ 15:00  +15°C  Ясно\n⛅ 18:00  +12°C  Переменная облачность\n🌥️ 21:00  +9°C  Облачно с прояснениями\n\n🌅 Рассвет: 05:58\n🌇 Закат: 21:22\n\n📡 Гисметео · обновлено 07:30`;

  return (
    <div>
      <SectionHeader title="Прогноз погоды" subtitle="Модуль Gismeteo — рассылка прогноза по расписанию"
        right={<div style={{ display:'flex', alignItems:'center', gap:12 }}>
          <Badge color={cfg.active?t.success:t.textMute} dot>{cfg.active?'Активен':'Отключён'}</Badge>
          <Toggle checked={cfg.active||false} onChange={v => up({ active:v })} />
        </div>} />

      <div style={{ display:'grid', gridTemplateColumns:'1fr 380px', gap:16, alignItems:'flex-start' }}>
        <Card style={{ padding:22 }}>
          <div style={{ display:'flex', flexDirection:'column', gap:18 }}>
            <Input label="API-токен Gismeteo" value={cfg.token||''} onChange={e => up({ token:e.target.value })} type={showToken?'text':'password'} icon="shield"
              suffix={<div style={{ display:'flex', gap:6, alignItems:'center' }}>
                <button onClick={() => setShowToken(!showToken)} style={{ background:'transparent', border:'none', color:t.textMute, cursor:'pointer', padding:4 }}><Icon name={showToken?'eyeOff':'eye'} size={14} /></button>
                <Button variant="subtle" size="sm" onClick={testConn} loading={testing}>{testing?'Проверка…':'Проверить'}</Button>
              </div>} />

            <Input label="Город (на латинице)" value={cfg.city||''} onChange={e => up({ city:e.target.value })} icon="mapPin" hint="Параметр name в API Gismeteo (Yekaterinburg, Moscow...)" />
            <Input label="Город (отображение в сообщении)" value={cfg.cityRu||''} onChange={e => up({ cityRu:e.target.value })} hint="Пример: Екатеринбург" />

            <div>
              <div style={{ fontSize:12, color:t.textDim, fontWeight:500, marginBottom:10 }}>Расписание (cron)</div>
              <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12 }}>
                <div>
                  <Input label="Будни" value={cfg.cron_wd||''} onChange={e => up({ cron_wd:e.target.value })} style={{ fontFamily:'"JetBrains Mono",monospace' }} />
                  <div style={{ fontSize:11, color:t.weather, marginTop:6, display:'flex', alignItems:'center', gap:6 }}><Icon name="clock" size={11} /> {describeCron(cfg.cron_wd)}</div>
                </div>
                <div>
                  <Input label="Выходные" value={cfg.cron_we||''} onChange={e => up({ cron_we:e.target.value })} style={{ fontFamily:'"JetBrains Mono",monospace' }} />
                  <div style={{ fontSize:11, color:t.weather, marginTop:6, display:'flex', alignItems:'center', gap:6 }}><Icon name="clock" size={11} /> {describeCron(cfg.cron_we)}</div>
                </div>
              </div>
            </div>

            <div>
              <div style={{ fontSize:12, color:t.textDim, fontWeight:500, marginBottom:10 }}>Тип прогноза</div>
              <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:8 }}>
                {[{v:'current',l:'Только текущая погода',d:'Снимок текущего момента'},{v:'hourly',l:'Почасовой (сегодня)',d:'Сегодня по часам'},{v:'daily',l:'Дневной (сегодня)',d:'Краткая сводка за день'},{v:'extended',l:'Расширенный',d:'Почасовой + дневной'}].map(opt => {
                  const isAct = cfg.forecast_type === opt.v;
                  return <div key={opt.v} onClick={() => up({ forecast_type:opt.v })} style={{ padding:12, borderRadius:8, border:`1px solid ${isAct?t.primary:t.border}`, background:isAct?t.primaryDim:t.inputBg, cursor:'pointer', transition:'all 0.15s' }}>
                    <div style={{ display:'flex', alignItems:'center', gap:8, marginBottom:4 }}><Radio checked={isAct} onChange={() => up({ forecast_type:opt.v })} /><span style={{ fontSize:12.5, fontWeight:500, color:t.text }}>{opt.l}</span></div>
                    <div style={{ fontSize:11, color:t.textMute, marginLeft:24 }}>{opt.d}</div>
                  </div>;
                })}
              </div>
            </div>

            <div>
              <div style={{ fontSize:12, color:t.textDim, fontWeight:500, marginBottom:10 }}>Параметры в сообщении</div>
              <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:8, padding:12, background:t.inputBg, borderRadius:8 }}>
                {[['temp','Температура'],['feels','Ощущается как'],['wind','Скорость ветра'],['humidity','Влажность'],['pressure','Давление'],['precip','Вероятность осадков'],['sun','Восход / закат'],['hourly','По часам (до 6 слотов)']].map(([k,l]) => (
                  <Checkbox key={k} checked={!!(cfg.params||{})[k]} onChange={v => upParams({ [k]:v })} label={l} />
                ))}
              </div>
            </div>

            <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', paddingTop:12, borderTop:`1px solid ${t.border}`, gap:12, flexWrap:'wrap' }}>
              <Button variant="ghost" icon="send" onClick={sendNow} loading={sending}>Отправить сейчас</Button>
              <Button onClick={save} loading={saving}>Сохранить</Button>
            </div>
          </div>
        </Card>

        <div style={{ position:'sticky', top:20 }}>
          <Card style={{ padding:18 }}>
            <div style={{ fontSize:12, color:t.textDim, fontWeight:600, letterSpacing:'0.04em', textTransform:'uppercase', marginBottom:14, display:'flex', alignItems:'center', gap:6 }}><Icon name="send" size={11} /> Превью сообщения</div>
            <div style={{ background:t.chatBg, borderRadius:12, padding:16, border:`1px solid ${t.border}` }}>
              <ChatBubble modKey="weather">{previewText}</ChatBubble>
              <div style={{ fontSize:10, color:t.textMute, marginTop:10, textAlign:'center' }}>Формат HTML · &lt;b&gt; &lt;i&gt; &lt;code&gt; &lt;u&gt;</div>
            </div>
          </Card>
        </div>
      </div>
    </div>
  );
};

// ============================================================================
// REMINDERS PAGE
// ============================================================================
const RemindersPage = () => {
  const t = useTheme();
  const toast = useToast();
  const [list, setList] = useState([]);
  const [active, setActive] = useState(true);
  const [adding, setAdding] = useState(false);
  const [editing, setEditing] = useState(null);
  const [draft, setDraft] = useState({ title:'', cron:'0 19 * * *', text:'' });
  const [saving, setSaving] = useState(false);
  const upDraft = p => setDraft(d => ({ ...d, ...p }));

  const load = async () => {
    const r = await apiGet('/reminders');
    setList(r.list); setActive(r.active);
  };
  useEffect(() => { load().catch(e => toast(e.message,'error')); }, []);

  const toggleModule = async v => {
    try { await apiPost('/reminders/toggle', { active:v }); setActive(v); toast(v?'Напоминания включены':'Напоминания отключены'); }
    catch(e) { toast(e.message,'error'); }
  };

  const startAdd = () => { setDraft({ title:'', cron:'0 19 * * *', text:'' }); setEditing(null); setAdding(true); };
  const startEdit = r => { setDraft({ title:r.title, cron:r.cron, text:r.text }); setEditing(r.id); setAdding(true); };
  const cancel = () => { setAdding(false); setEditing(null); };

  const save = async () => {
    if (!draft.title.trim()) { toast('Заголовок обязателен','error'); return; }
    if (!draft.text.trim()) { toast('Текст обязателен','error'); return; }
    setSaving(true);
    try {
      if (editing) { await apiPut(`/reminders/${editing}`, draft); toast('Напоминание обновлено'); }
      else { await apiPost('/reminders', draft); toast('Напоминание добавлено'); }
      await load(); cancel();
    } catch(e) { toast(e.message,'error'); } finally { setSaving(false); }
  };

  const remove = async id => {
    if (!confirm('Удалить напоминание?')) return;
    try { await apiDel(`/reminders/${id}`); toast('Удалено'); await load(); }
    catch(e) { toast(e.message,'error'); }
  };

  return (
    <div>
      <SectionHeader title="Напоминания по расписанию" subtitle={`${list.length} напоминаний, отправляются в чат MAX`}
        right={<div style={{ display:'flex', alignItems:'center', gap:12 }}>
          <Badge color={active?t.reminder:t.textMute} dot>{active?'Активны':'Отключены'}</Badge>
          <Toggle checked={active} onChange={toggleModule} />
        </div>} />

      <div style={{ display:'flex', flexDirection:'column', gap:12, marginBottom:12 }}>
        {list.map(r => (
          <Card key={r.id} style={{ padding:18 }}>
            <div style={{ display:'flex', alignItems:'flex-start', gap:14 }}>
              <div style={{ width:36, height:36, borderRadius:9, background:`${t.reminder}1a`, display:'flex', alignItems:'center', justifyContent:'center', color:t.reminder, flexShrink:0 }}><Icon name="bell" size={16} /></div>
              <div style={{ flex:1, minWidth:0 }}>
                <div style={{ display:'flex', alignItems:'center', gap:10, marginBottom:6, flexWrap:'wrap' }}>
                  <div style={{ fontSize:14, fontWeight:600, color:t.text }}>{r.title}</div>
                  <Badge color={t.reminder}><span style={{ fontFamily:'"JetBrains Mono",monospace', fontSize:10 }}>{r.cron}</span></Badge>
                </div>
                <div style={{ fontSize:11.5, color:t.reminder, marginBottom:8, display:'flex', alignItems:'center', gap:6 }}><Icon name="clock" size={11} /> {describeCron(r.cron)}</div>
                <div style={{ fontSize:12.5, color:t.textDim, lineHeight:1.5, display:'-webkit-box', WebkitBoxOrient:'vertical', WebkitLineClamp:2, overflow:'hidden' }}>{r.text}</div>
              </div>
              <div style={{ display:'flex', gap:6 }}>
                <Button variant="subtle" size="sm" icon="edit" onClick={() => startEdit(r)}>Изменить</Button>
                <Button variant="danger" size="sm" icon="trash" onClick={() => remove(r.id)}>Удалить</Button>
              </div>
            </div>
          </Card>
        ))}
      </div>

      {!adding && (
        <button onClick={startAdd} style={{ width:'100%', padding:16, border:`1px dashed ${t.borderStrong}`, borderRadius:12, background:'transparent', color:t.textDim, display:'flex', alignItems:'center', justifyContent:'center', gap:8, cursor:'pointer', fontFamily:'inherit', fontSize:13, transition:'all 0.15s' }}
          onMouseEnter={e => { e.currentTarget.style.borderColor=t.primary; e.currentTarget.style.color=t.primary; }}
          onMouseLeave={e => { e.currentTarget.style.borderColor=t.borderStrong; e.currentTarget.style.color=t.textDim; }}
        ><Icon name="plus" size={15} /> Добавить напоминание</button>
      )}

      {adding && (
        <Card style={{ padding:22, marginTop:4, border:`1px solid ${t.primary}`, boxShadow:`0 0 0 4px ${t.primaryDim}` }}>
          <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:18 }}>
            <div style={{ fontSize:14, fontWeight:600, color:t.text }}>{editing?'Редактирование':'Новое напоминание'}</div>
            <button onClick={cancel} style={{ background:'transparent', border:'none', color:t.textMute, cursor:'pointer', padding:4 }}><Icon name="x" size={16} /></button>
          </div>
          <div style={{ display:'grid', gridTemplateColumns:'1fr 360px', gap:20 }}>
            <div style={{ display:'flex', flexDirection:'column', gap:14 }}>
              <Input label="Заголовок" value={draft.title} onChange={e => upDraft({ title:e.target.value })} placeholder="например, «Уборка территории»" />
              <div>
                <Input label="Cron-выражение" value={draft.cron} onChange={e => upDraft({ cron:e.target.value })} style={{ fontFamily:'"JetBrains Mono",monospace' }} />
                <div style={{ fontSize:11.5, color:t.reminder, marginTop:6, display:'flex', alignItems:'center', gap:6 }}><Icon name="clock" size={11} /> Запускать: {describeCron(draft.cron)}</div>
              </div>
              <div>
                <div style={{ display:'flex', justifyContent:'space-between', marginBottom:4 }}>
                  <span style={{ fontSize:12, color:t.textDim, fontWeight:500 }}>Текст сообщения</span>
                  <span style={{ fontSize:11, color:draft.text.length>500?t.error:t.textMute, fontVariantNumeric:'tabular-nums' }}>{draft.text.length} / 500</span>
                </div>
                <Input textarea label="" value={draft.text} onChange={e => upDraft({ text:e.target.value })} rows={6} placeholder="Многострочный текст. Поддерживаются эмодзи 📨" />
              </div>
              <div style={{ display:'flex', gap:8, justifyContent:'flex-end' }}>
                <Button variant="ghost" onClick={cancel}>Отмена</Button>
                <Button onClick={save} loading={saving}>{editing?'Сохранить':'Добавить'}</Button>
              </div>
            </div>
            <Card style={{ padding:18 }}>
              <div style={{ fontSize:12, color:t.textDim, fontWeight:600, letterSpacing:'0.04em', textTransform:'uppercase', marginBottom:14, display:'flex', alignItems:'center', gap:6 }}><Icon name="send" size={11} /> Превью сообщения</div>
              <div style={{ background:t.chatBg, borderRadius:12, padding:16, border:`1px solid ${t.border}` }}>
                <ChatBubble modKey="reminder">{draft.text || <span style={{ color:t.textMute, fontStyle:'italic' }}>Текст напоминания появится здесь…</span>}</ChatBubble>
                <div style={{ fontSize:10, color:t.textMute, marginTop:10, textAlign:'center' }}>Поддерживаются эмодзи · текст отображается как есть</div>
              </div>
            </Card>
          </div>
        </Card>
      )}
    </div>
  );
};

// ============================================================================
// CAMERA PAGE
// ============================================================================
const CameraPage = () => {
  const t = useTheme();
  const toast = useToast();
  const [cfg, setCfg] = useState(null);
  const [showPwd, setShowPwd] = useState(false);
  const [kwInput, setKwInput] = useState('');
  const [testLoading, setTestLoading] = useState(false);
  const [testImgUrl, setTestImgUrl] = useState(null);
  const [testError, setTestError] = useState(null);
  const [saving, setSaving] = useState(false);
  const [snapshots, setSnapshots] = useState([]);

  const up = p => setCfg(c => ({ ...c, ...p }));

  const loadSnapshots = async () => {
    try { setSnapshots(await apiGet('/camera/snapshots')); } catch {}
  };

  useEffect(() => {
    apiGet('/camera').then(setCfg).catch(e => toast(e.message,'error'));
    loadSnapshots();
    const i = setInterval(loadSnapshots, 15000);
    return () => clearInterval(i);
  }, []);

  if (!cfg) return <div style={{ color:t.textMute, padding:40, textAlign:'center' }}>Загрузка…</div>;

  const addKw = () => { const v = kwInput.trim(); if (v && !cfg.keywords.includes(v)) { up({ keywords:[...(cfg.keywords||[]),v] }); setKwInput(''); } };
  const removeKw = k => up({ keywords:(cfg.keywords||[]).filter(x => x!==k) });

  const testSnap = async () => {
    setTestLoading(true); setTestImgUrl(null); setTestError(null);
    try {
      // Fetch via API to get real snapshot
      const r = await fetch('/api/camera/snapshot', { credentials:'same-origin' });
      if (!r.ok) { const d = await r.json(); throw new Error(d.error||'Ошибка'); }
      const blob = await r.blob();
      setTestImgUrl(URL.createObjectURL(blob));
      toast('Снимок получен', 'success');
    } catch(e) { setTestError(e.message); toast(e.message,'error'); } finally { setTestLoading(false); }
  };

  const save = async () => {
    // Commit any word still sitting in the input field (user skipped Enter)
    let saveCfg = cfg;
    const pending = kwInput.trim();
    if (pending && !(cfg.keywords||[]).includes(pending)) {
      saveCfg = { ...cfg, keywords: [...(cfg.keywords||[]), pending] };
      setCfg(saveCfg);
      setKwInput('');
    }
    setSaving(true);
    try { await apiPost('/camera', saveCfg); toast('Настройки камеры сохранены'); }
    catch(e) { toast(e.message,'error'); } finally { setSaving(false); }
  };

  return (
    <div>
      <SectionHeader title="Бот снимков с камеры" subtitle="Снимки по запросу — ключевые слова в чате MAX"
        right={<div style={{ display:'flex', alignItems:'center', gap:12 }}>
          <Badge color={cfg.active?t.camera:t.textMute} dot>{cfg.active?'Активен':'Отключён'}</Badge>
          <Toggle checked={cfg.active||false} onChange={v => up({ active:v })} />
        </div>} />

      <div style={{ display:'grid', gridTemplateColumns:'1fr 380px', gap:16, alignItems:'flex-start' }}>
        <Card style={{ padding:22 }}>
          <div style={{ display:'flex', flexDirection:'column', gap:18 }}>
            {/* Camera name badge */}
            <div style={{ display:'flex', alignItems:'center', gap:10, padding:'10px 14px', background:`${t.camera}10`, border:`1px solid ${t.camera}30`, borderRadius:10 }}>
              <div style={{ width:32, height:32, borderRadius:8, background:`${t.camera}20`, display:'flex', alignItems:'center', justifyContent:'center', color:t.camera }}><Icon name="camera" size={16} /></div>
              <div><div style={{ fontSize:13, fontWeight:600, color:t.text }}>{cfg.name || 'ТКО СНТ'}</div><div style={{ fontSize:11.5, color:t.textMute }}>Одна камера на объекте</div></div>
            </div>

            <Input label="URL снимка (ISAPI / MJPEG)" value={cfg.url||''} onChange={e => up({ url:e.target.value })} icon="camera" hint="HTTP-эндпоинт, возвращающий JPEG" style={{ fontFamily:'"JetBrains Mono",monospace', fontSize:11.5 }} />

            <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12 }}>
              <Input label="Логин" value={cfg.login||''} onChange={e => up({ login:e.target.value })} />
              <Input label="Пароль" value={cfg.password||''} onChange={e => up({ password:e.target.value })} type={showPwd?'text':'password'}
                suffix={<button onClick={() => setShowPwd(!showPwd)} style={{ background:'transparent', border:'none', color:t.textMute, cursor:'pointer', padding:4 }}><Icon name={showPwd?'eyeOff':'eye'} size={14} /></button>} />
            </div>

            <div>
              <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:8 }}>
                <span style={{ fontSize:12, color:t.textDim, fontWeight:500 }}>Ключевые слова</span>
                <span style={{ fontSize:11, color:t.textMute }}>{(cfg.keywords||[]).length} слов · регистр не важен</span>
              </div>
              <div style={{ display:'flex', flexWrap:'wrap', gap:6, alignItems:'center', padding:10, background:t.inputBg, border:`1px solid ${t.border}`, borderRadius:8, minHeight:44 }}>
                {(cfg.keywords||[]).map(k => (
                  <span key={k} style={{ display:'inline-flex', alignItems:'center', gap:6, padding:'4px 6px 4px 10px', background:`${t.camera}1f`, border:`1px solid ${t.camera}40`, color:t.camera, borderRadius:6, fontSize:12, fontWeight:500 }}>
                    {k}
                    <button onClick={() => removeKw(k)} style={{ background:`${t.camera}26`, border:'none', borderRadius:4, width:16, height:16, cursor:'pointer', display:'flex', alignItems:'center', justifyContent:'center', color:t.camera }}><Icon name="x" size={10} strokeWidth={2.5} /></button>
                  </span>
                ))}
                <input value={kwInput} onChange={e => setKwInput(e.target.value)} onKeyDown={e => { if(e.key==='Enter'){e.preventDefault();addKw();} }}
                  placeholder={(cfg.keywords||[]).length?'Добавить ещё…':'Введите слово и нажмите Enter'}
                  style={{ flex:1, minWidth:120, background:'transparent', border:'none', outline:'none', color:t.text, fontSize:13, fontFamily:'inherit' }} />
              </div>
            </div>

            <Select label="Задержка между снимками" hint="Защита от спама при частых запросах" value={String(cfg.cooldown||60)} onChange={e => up({ cooldown:parseInt(e.target.value) })}
              options={[{value:'0',label:'Без ограничений'},{value:'30',label:'30 секунд'},{value:'60',label:'1 минута'},{value:'300',label:'5 минут'}]} />

            {/* Test snapshot */}
            <div style={{ padding:14, background:`${t.camera}10`, border:`1px dashed ${t.camera}40`, borderRadius:10, display:'flex', alignItems:'center', gap:14 }}>
              <div style={{ flex:1 }}>
                <div style={{ fontSize:13, fontWeight:500, color:t.text, marginBottom:2 }}>Тестовый снимок</div>
                <div style={{ fontSize:11.5, color:t.textMute }}>Запрос к камере прямо сейчас (в превью)</div>
              </div>
              <Button variant="subtle" icon={testLoading?'refresh':'play'} onClick={testSnap} loading={testLoading}>{testLoading?'Получение…':'Сделать снимок'}</Button>
            </div>

            {(testLoading || testImgUrl || testError) && (
              <div style={{ background:t.inputBg, borderRadius:10, padding:14, border:`1px solid ${t.border}` }}>
                {testLoading && (
                  <div style={{ height:220, display:'flex', alignItems:'center', justifyContent:'center', flexDirection:'column', gap:8, color:t.textMute, fontSize:12 }}>
                    <div style={{ width:22, height:22, borderRadius:'50%', border:`2px solid ${t.border}`, borderTopColor:t.camera, animation:'spin 0.8s linear infinite' }} />
                    Подключение к камере…
                  </div>
                )}
                {!testLoading && testError && <div style={{ color:t.error, fontSize:13, textAlign:'center', padding:20 }}>❌ {testError}</div>}
                {!testLoading && testImgUrl && (
                  <div>
                    <img src={testImgUrl} alt="Снимок с камеры" style={{ width:'100%', borderRadius:8, display:'block' }} />
                    <div style={{ display:'flex', justifyContent:'space-between', marginTop:10, fontSize:11, color:t.textMute }}>
                      <span>📷 {cfg.name || 'ТКО СНТ'} · только что</span>
                      <span style={{ color:t.success, display:'flex', alignItems:'center', gap:4 }}><StatusDot color={t.success} /> 200 OK</span>
                    </div>
                  </div>
                )}
              </div>
            )}

            <div style={{ display:'flex', justifyContent:'flex-end', paddingTop:12, borderTop:`1px solid ${t.border}` }}>
              <Button onClick={save} loading={saving}>Сохранить</Button>
            </div>
          </div>
        </Card>

        <div style={{ display:'flex', flexDirection:'column', gap:12, position:'sticky', top:20 }}>
          <Card style={{ padding:18 }}>
            <div style={{ fontSize:13, fontWeight:600, color:t.text, marginBottom:4 }}>Последние 5 снимков</div>
            <div style={{ fontSize:11.5, color:t.textMute, marginBottom:14 }}>Из чата по ключевым словам</div>
            {snapshots.length === 0 && <div style={{ fontSize:12, color:t.textMute, textAlign:'center', padding:'20px 0' }}>Снимков пока нет</div>}
            <div style={{ display:'flex', flexDirection:'column', gap:10 }}>
              {snapshots.map((s, i) => (
                <div key={s.id||i} style={{ display:'flex', alignItems:'center', gap:10, padding:8, borderRadius:8, border:`1px solid ${t.border}`, background:t.inputBg, cursor:'pointer', transition:'border-color 0.15s' }}
                  onMouseEnter={e => e.currentTarget.style.borderColor=`${t.camera}80`}
                  onMouseLeave={e => e.currentTarget.style.borderColor=t.border}
                >
                  <div style={{ width:64, height:48, borderRadius:6, overflow:'hidden', flexShrink:0, background:t.cardSubtle, display:'flex', alignItems:'center', justifyContent:'center' }}>
                    <img src={`/snapshots/${s.filename}`} alt="snap" style={{ width:'100%', height:'100%', objectFit:'cover' }}
                      onError={e => { e.target.style.display='none'; e.target.nextSibling.style.display='flex'; }}
                    />
                    <div style={{ display:'none', width:'100%', height:'100%', alignItems:'center', justifyContent:'center', color:t.textMute }}><Icon name="image" size={18} /></div>
                  </div>
                  <div style={{ flex:1, minWidth:0 }}>
                    <div style={{ fontSize:12.5, color:t.text, fontWeight:500 }}>{cfg.name || 'ТКО СНТ'}</div>
                    <div style={{ fontSize:11, color:t.textMute, display:'flex', gap:6, alignItems:'center', marginTop:2 }}>
                      <span>{timeAgo(s.created_at)}</span><span>·</span>
                      <span style={{ padding:'1px 6px', borderRadius:3, background:`${t.camera}1f`, color:t.camera, fontFamily:'"JetBrains Mono",monospace', fontSize:10 }}>{s.keyword||'ручной'}</span>
                    </div>
                  </div>
                  <a href={`/snapshots/${s.filename}`} download style={{ color:t.textMute, textDecoration:'none' }}><Icon name="download" size={13} /></a>
                </div>
              ))}
            </div>
          </Card>

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

// ============================================================================
// SETTINGS PAGE
// ============================================================================
const SettingsPage = ({ user }) => {
  const t = useTheme();
  const toast = useToast();
  const [tab, setTab] = useState('global');
  const [s, setS] = useState({ max_token:'', max_chat_id:'', timezone:'Asia/Yekaterinburg', log_retention:'30' });
  const [showToken, setShowToken] = useState(false);
  const [validating, setValidating] = useState(false);
  const [savingGlobal, setSavingGlobal] = useState(false);
  const [registering, setRegistering] = useState(false);
  const [webhookUrl, setWebhookUrl] = useState('https://board.zmgorka.ru/webhook/max');
  const [pings, setPings] = useState({});
  const [pinging, setPinging] = useState({});

  const doPing = async (host) => {
    setPinging(p => ({ ...p, [host]: true }));
    try {
      const r = await apiGet(`/settings/ping?host=${encodeURIComponent(host)}`);
      setPings(p => ({ ...p, [host]: r }));
    } catch(e) {
      setPings(p => ({ ...p, [host]: { ok: false } }));
    } finally {
      setPinging(p => ({ ...p, [host]: false }));
    }
  };

  // Users
  const [users, setUsers] = useState([]);
  const [addingUser, setAddingUser] = useState(false);
  const [editingUser, setEditingUser] = useState(null);
  const [userDraft, setUserDraft] = useState({ username:'', password:'', role:'user' });
  const [savingUser, setSavingUser] = useState(false);
  const upUser = p => setUserDraft(d => ({...d,...p}));

  useEffect(() => {
    apiGet('/settings').then(setS).catch(e => toast(e.message,'error'));
    if (user?.role === 'admin') apiGet('/users').then(setUsers).catch(() => {});
  }, []);

  const saveGlobal = async () => {
    setSavingGlobal(true);
    try { await apiPost('/settings', s); toast('Настройки сохранены'); }
    catch(e) { toast(e.message,'error'); } finally { setSavingGlobal(false); }
  };

  const validate = async () => {
    setValidating(true);
    try {
      const r = await apiPost('/settings/validate-token', { token: s.max_token });
      if (r.ok) toast(`Токен MAX действителен — бот «${r.name}» доступен`, 'success');
      else toast(`Ошибка токена: ${r.error}`, 'error');
    } catch(e) { toast(e.message,'error'); } finally { setValidating(false); }
  };

  const registerWh = async () => {
    setRegistering(true);
    try {
      const r = await apiPost('/settings/register-webhook', { url: webhookUrl });
      if (r.ok) toast('Webhook зарегистрирован в MAX', 'success');
      else toast('Ошибка регистрации webhook', 'error');
    } catch(e) { toast(e.message,'error'); } finally { setRegistering(false); }
  };

  const saveUser = async () => {
    if (!userDraft.username.trim()) { toast('Введите логин','error'); return; }
    if (!editingUser && !userDraft.password) { toast('Введите пароль','error'); return; }
    setSavingUser(true);
    try {
      if (editingUser) { await apiPut(`/users/${editingUser}`, userDraft); toast('Пользователь обновлён'); }
      else { await apiPost('/users', userDraft); toast('Пользователь создан'); }
      setUsers(await apiGet('/users')); setAddingUser(false); setEditingUser(null);
    } catch(e) { toast(e.message,'error'); } finally { setSavingUser(false); }
  };

  const deleteUser = async id => {
    if (!confirm('Удалить пользователя?')) return;
    try { await apiDel(`/users/${id}`); setUsers(await apiGet('/users')); toast('Удалён'); }
    catch(e) { toast(e.message,'error'); }
  };

  const startEditUser = u => { setUserDraft({ username:u.username, password:'', role:u.role }); setEditingUser(u.id); setAddingUser(true); };

  const TABS = [{ id:'global', label:'Основные' }, { id:'users', label:'Пользователи' }, { id:'network', label:'Сеть' }];

  return (
    <div>
      <SectionHeader title="Глобальные настройки" subtitle="Учётные данные бота, пользователи, сеть" />

      <div style={{ display:'flex', gap:4, padding:3, background:t.inputBg, borderRadius:10, marginBottom:20, width:'fit-content' }}>
        {TABS.map(tb => <button key={tb.id} onClick={() => setTab(tb.id)} style={{ padding:'7px 16px', fontSize:12.5, fontWeight:500, borderRadius:8, border:'none', background:tab===tb.id?t.card:'transparent', color:tab===tb.id?t.text:t.textDim, cursor:'pointer', fontFamily:'inherit', boxShadow:tab===tb.id?'0 1px 2px rgba(0,0,0,0.06)':'none', transition:'all 0.15s' }}>{tb.label}</button>)}
      </div>

      {tab === 'global' && (
        <div style={{ display:'flex', flexDirection:'column', gap:14, maxWidth:760 }}>
          <Card style={{ padding:22 }}>
            <div style={{ fontSize:14, fontWeight:600, color:t.text, marginBottom:4 }}>Мессенджер MAX</div>
            <div style={{ fontSize:12, color:t.textMute, marginBottom:18 }}>Учётные данные бота для отправки сообщений</div>
            <div style={{ display:'flex', flexDirection:'column', gap:16 }}>
              <Input label="Токен бота MAX" value={s.max_token} onChange={e => setS({...s,max_token:e.target.value})} type={showToken?'text':'password'} icon="shield"
                suffix={<div style={{ display:'flex', gap:6, alignItems:'center' }}>
                  <button onClick={() => setShowToken(!showToken)} style={{ background:'transparent', border:'none', color:t.textMute, cursor:'pointer', padding:4 }}><Icon name={showToken?'eyeOff':'eye'} size={14} /></button>
                  <Button variant="subtle" size="sm" onClick={validate} loading={validating}>{validating?'Проверка…':'Проверить токен'}</Button>
                </div>} />
              <Input label="ID чата MAX" value={s.max_chat_id} onChange={e => setS({...s,max_chat_id:e.target.value})} hint="Чат, куда бот пишет и откуда принимает команды" />
            </div>
          </Card>

          <Card style={{ padding:22 }}>
            <div style={{ fontSize:14, fontWeight:600, color:t.text, marginBottom:18 }}>Сервер</div>
            <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:14 }}>
              <Select label="Часовой пояс" value={s.timezone} onChange={e => setS({...s,timezone:e.target.value})}
                options={[{value:'Asia/Yekaterinburg',label:'Asia/Yekaterinburg (UTC+5)'},{value:'Europe/Moscow',label:'Europe/Moscow (UTC+3)'},{value:'UTC',label:'UTC'}]} />
              <Select label="Хранение логов" value={s.log_retention} onChange={e => setS({...s,log_retention:e.target.value})}
                options={[{value:'7',label:'7 дней'},{value:'30',label:'30 дней'},{value:'90',label:'90 дней'}]} />
            </div>
          </Card>

          <div style={{ display:'flex', justifyContent:'flex-end', gap:8 }}>
            <Button onClick={saveGlobal} loading={savingGlobal}>Сохранить</Button>
          </div>
        </div>
      )}

      {tab === 'users' && user?.role === 'admin' && (
        <div style={{ maxWidth:760 }}>
          <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:14 }}>
            <div style={{ fontSize:13, color:t.textDim }}>Управление доступом</div>
            {!addingUser && <Button icon="plus" size="sm" onClick={() => { setUserDraft({username:'',password:'',role:'user'}); setEditingUser(null); setAddingUser(true); }}>Добавить</Button>}
          </div>

          {addingUser && (
            <Card style={{ padding:20, marginBottom:14, border:`1px solid ${t.primary}`, boxShadow:`0 0 0 3px ${t.primaryDim}` }}>
              <div style={{ display:'flex', justifyContent:'space-between', marginBottom:16 }}>
                <div style={{ fontSize:13, fontWeight:600, color:t.text }}>{editingUser?'Редактирование пользователя':'Новый пользователь'}</div>
                <button onClick={() => {setAddingUser(false);setEditingUser(null);}} style={{ background:'transparent', border:'none', color:t.textMute, cursor:'pointer' }}><Icon name="x" size={16} /></button>
              </div>
              <div style={{ display:'grid', gridTemplateColumns:'1fr 1fr 1fr', gap:12, marginBottom:16 }}>
                <Input label="Логин" value={userDraft.username} onChange={e => upUser({username:e.target.value})} disabled={!!editingUser} />
                <Input label={editingUser?'Новый пароль (пусто = не менять)':'Пароль'} value={userDraft.password} onChange={e => upUser({password:e.target.value})} type="password" />
                <Select label="Роль" value={userDraft.role} onChange={e => upUser({role:e.target.value})} options={[{value:'user',label:'Пользователь'},{value:'admin',label:'Администратор'}]} />
              </div>
              <div style={{ display:'flex', gap:8, justifyContent:'flex-end' }}>
                <Button variant="ghost" onClick={() => {setAddingUser(false);setEditingUser(null);}}>Отмена</Button>
                <Button onClick={saveUser} loading={savingUser}>{editingUser?'Сохранить':'Создать'}</Button>
              </div>
            </Card>
          )}

          <Card style={{ overflow:'hidden' }}>
            {users.map((u, i) => (
              <div key={u.id} style={{ display:'flex', alignItems:'center', gap:14, padding:'14px 18px', borderBottom:i<users.length-1?`1px solid ${t.border}`:'none' }}>
                <div style={{ width:34, height:34, borderRadius:'50%', background:t.primaryDim, display:'flex', alignItems:'center', justifyContent:'center', color:t.primary, fontWeight:700, fontSize:14, flexShrink:0 }}>{u.username[0].toUpperCase()}</div>
                <div style={{ flex:1 }}>
                  <div style={{ fontSize:13, fontWeight:600, color:t.text }}>{u.username}</div>
                  <div style={{ fontSize:11, color:t.textMute }}>{u.role==='admin'?'Администратор':'Пользователь'} · создан {timeAgo(u.created_at)}</div>
                </div>
                <div style={{ display:'flex', gap:6 }}>
                  <Button variant="subtle" size="sm" icon="edit" onClick={() => startEditUser(u)}>Изменить</Button>
                  {u.username !== 'root' && <Button variant="danger" size="sm" icon="trash" onClick={() => deleteUser(u.id)}>Удалить</Button>}
                </div>
              </div>
            ))}
          </Card>
        </div>
      )}

      {tab === 'network' && (
        <div style={{ maxWidth:760, display:'flex', flexDirection:'column', gap:14 }}>
          <Card style={{ padding:22, background:`linear-gradient(135deg,${t.success}0a,${t.primary}0a)`, border:`1px solid ${t.success}33` }}>
            <div style={{ display:'flex', gap:14 }}>
              <div style={{ width:38, height:38, borderRadius:9, background:`${t.success}1f`, display:'flex', alignItems:'center', justifyContent:'center', color:t.success, flexShrink:0 }}><Icon name="wifi" size={17} /></div>
              <div style={{ flex:1 }}>
                <div style={{ display:'flex', alignItems:'center', gap:10, marginBottom:4, flexWrap:'wrap' }}>
                  <div style={{ fontSize:13, fontWeight:600, color:t.text }}>Информация о сети</div>
                  <Badge color={t.success} dot>VPN-туннель 44.x ↔ 28.x</Badge>
                </div>
                <div style={{ fontSize:12.5, color:t.textDim, lineHeight:1.55 }}>
                  Запросы к API погоды маршрутизируются через VPN в сети <code style={{ background:t.inputBg, padding:'1px 5px', borderRadius:3, fontSize:11.5 }}>28.x</code> (шлюз <code style={{ background:t.inputBg, padding:'1px 5px', borderRadius:3, fontSize:11.5 }}>192.168.28.1</code>). Сервер находится в сети 44.x. Убедитесь, что VPN-туннель активен.
                </div>
                <div style={{ marginTop:10, fontSize:11.5, color:t.textDim }}>
                  Камера по адресу <code style={{ background:t.inputBg, padding:'1px 5px', borderRadius:3 }}>192.168.100.x</code> также доступна через VPN-маршрут.
                </div>
              </div>
            </div>
          </Card>

          <Card style={{ padding:22 }}>
            <div style={{ fontSize:14, fontWeight:600, color:t.text, marginBottom:4 }}>Проверка шлюзов</div>
            <div style={{ fontSize:12, color:t.textMute, marginBottom:16 }}>Ping до шлюзов для проверки VPN-туннеля</div>
            <div style={{ display:'flex', flexDirection:'column', gap:10 }}>
              {[
                { host:'192.168.28.1',  label:'Шлюз 28.x',  hint:'VPN-туннель / маршрут к Gismeteo' },
                { host:'192.168.100.1', label:'Шлюз 100.x', hint:'Маршрут до камеры ТКО СНТ' },
              ].map(({ host, label, hint }) => {
                const res = pings[host];
                const loading = pinging[host];
                return (
                  <div key={host} style={{ display:'flex', alignItems:'center', gap:12, padding:'11px 14px', background:t.inputBg, borderRadius:9, border:`1px solid ${res ? (res.ok ? `${t.success}50` : `${t.error}50`) : t.border}`, transition:'border-color 0.2s' }}>
                    <div style={{ flex:1, minWidth:0 }}>
                      <div style={{ fontSize:12.5, fontWeight:500, color:t.text }}>{label}</div>
                      <code style={{ fontSize:11, color:t.textMute }}>{host}</code>
                      <div style={{ fontSize:11, color:t.textMute, marginTop:1 }}>{hint}</div>
                    </div>
                    {res && (
                      <div style={{ fontSize:12, fontWeight:500, color:res.ok ? t.success : t.error, flexShrink:0 }}>
                        {res.ok ? `✅ ${res.ms} мс` : '❌ нет ответа'}
                      </div>
                    )}
                    <Button variant="subtle" size="sm" icon="refresh" onClick={() => doPing(host)} loading={loading}>Ping</Button>
                  </div>
                );
              })}
            </div>
          </Card>

          <Card style={{ padding:22 }}>
            <div style={{ fontSize:14, fontWeight:600, color:t.text, marginBottom:4 }}>Webhook MAX</div>
            <div style={{ fontSize:12, color:t.textMute, marginBottom:16 }}>Зарегистрировать URL для получения сообщений от MAX (нужно для модуля Камеры)</div>
            <div style={{ display:'flex', gap:10 }}>
              <div style={{ flex:1 }}>
                <Input label="URL webhook" value={webhookUrl} onChange={e => setWebhookUrl(e.target.value)} style={{ fontFamily:'"JetBrains Mono",monospace', fontSize:12 }} />
              </div>
              <div style={{ paddingTop:22 }}>
                <Button variant="ghost" onClick={registerWh} loading={registering} icon="wifi">Зарегистрировать</Button>
              </div>
            </div>
          </Card>
        </div>
      )}
    </div>
  );
};

// ============================================================================
// APP ROOT
// ============================================================================
function App() {
  const [mode, setMode] = useState(() => { try { return localStorage.getItem('zg-theme')||'light'; } catch { return 'light'; } });
  const [authUser, setAuthUser] = useState(null);
  const [authChecked, setAuthChecked] = useState(false);
  const [page, setPage] = useState('dashboard');
  const [collapsed, setCollapsed] = useState(false);

  useEffect(() => { try { localStorage.setItem('zg-theme', mode); } catch {} document.body.style.background = themes[mode].bg; }, [mode]);

  // Set global unauth handler
  useEffect(() => { window._onUnauth = () => { setAuthUser(null); setAuthChecked(true); }; }, []);

  useEffect(() => {
    fetch('/api/auth/me', { credentials:'same-origin' })
      .then(r => r.ok ? r.json() : null)
      .then(d => { setAuthUser(d); setAuthChecked(true); })
      .catch(() => setAuthChecked(true));
  }, []);

  const handleLogin = (data) => setAuthUser(data);
  const handleLogout = async () => {
    await fetch('/api/auth/logout', { method:'POST', credentials:'same-origin' });
    setAuthUser(null);
  };

  const t = themes[mode];

  if (!authChecked) return (
    <ThemeCtx.Provider value={t}>
      <div style={{ minHeight:'100vh', background:t.bg, display:'flex', alignItems:'center', justifyContent:'center' }}>
        <div style={{ width:22, height:22, borderRadius:'50%', border:`2px solid ${t.border}`, borderTopColor:t.primary, animation:'spin 0.8s linear infinite' }} />
      </div>
    </ThemeCtx.Provider>
  );

  return (
    <ThemeCtx.Provider value={t}>
      <ToastProvider>
        {!authUser ? (
          <LoginPage onLogin={handleLogin} />
        ) : (
          <div style={{ display:'flex', height:'100vh', background:t.bg, color:t.text, fontFamily:'Inter,-apple-system,system-ui,sans-serif', fontSize:14 }}>
            <Sidebar page={page} setPage={setPage} collapsed={collapsed} user={authUser} onLogout={handleLogout} />
            <div style={{ flex:1, display:'flex', flexDirection:'column', minWidth:0 }}>
              <Header onMenuClick={() => setCollapsed(!collapsed)} mode={mode} setMode={setMode} />
              <main style={{ flex:1, overflowY:'auto', padding:'24px 28px 80px' }}>
                <div style={{ maxWidth:1240, margin:'0 auto' }}>
                  {page === 'dashboard'  && <DashboardPage />}
                  {page === 'weather'    && <WeatherPage />}
                  {page === 'reminders'  && <RemindersPage />}
                  {page === 'camera'     && <CameraPage />}
                  {page === 'settings'   && <SettingsPage user={authUser} />}
                </div>
              </main>
            </div>
          </div>
        )}
      </ToastProvider>
    </ThemeCtx.Provider>
  );
}

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