/* ============================================================
   page-bot-behavior.jsx — Bot Behavior configuration panel
   BC4: categorized accordion + search with match highlighting
   BC6: presets (Strict / Standard / Relaxed + custom)
   BC7: diff banner with undo after save
   v2: full visual polish — icons, colors, hierarchy, sticky rail
   ============================================================ */

/* Re-bind shared primitives.
   NOTE: the shared ToastCtx exposes `push({msg, kind})`, but every call site in
   this file uses `toast({message, kind})`. _useToast adapts between the two so
   all toasts in this panel actually fire (previously `toast` was undefined). */
const _useToast = () => {
  const ctx = (typeof window.useToast === 'function' ? window.useToast() : null) || {};
  const push = typeof ctx.push === 'function' ? ctx.push : (() => {});
  return {
    toast: (o) => push({
      kind: (o && o.kind) || 'info',
      msg: (o && (o.message != null ? o.message : o.msg)) || '',
    }),
  };
};
const _StatusPill = window.StatusPill || (({ children, kind }) => React.createElement('span', { className: `pill pill-${kind}` }, children));

/* ── Category meta ─────────────────────────────────────────── */
const CATEGORY_META = {
  'Sign-in':     { emoji: '📥', color: '#059669', bg: 'rgba(5,150,105,.12)',  subtitle: 'How sign-ins are accepted, parsed, and acknowledged.' },
  'Strikes':     { emoji: '⚡', color: '#e11d48', bg: 'rgba(225,29,72,.12)',  subtitle: 'Enforcement thresholds — when warnings escalate to admin reports.' },
  'PTO':         { emoji: '🏖', color: '#0284c7', bg: 'rgba(2,132,199,.12)',  subtitle: 'Time-off detection, sick-day handling, and PTO confirmation flow.' },
  'Hours':       { emoji: '⏰', color: '#d97706', bg: 'rgba(217,119,6,.12)',  subtitle: 'Workday length, late grace windows, burnout caps, and short-day flags.' },
  'Reports':     { emoji: '📊', color: '#7c3aed', bg: 'rgba(124,58,237,.12)', subtitle: 'What goes into admin reports and how often they post.' },
  'Voice':       { emoji: '🎙', color: '#4f46e5', bg: 'rgba(79,70,229,.12)',  subtitle: 'Voice listening windows, wake phrases, and follow-up timing.' },
  'AI Prompts':  { emoji: '✨', color: '#db2777', bg: 'rgba(219,39,119,.12)', subtitle: 'System prompts, tone, and persona that shape the AI bot\'s replies.' },
  'Templates':   { emoji: '📄', color: '#475569', bg: 'rgba(71,85,105,.12)',  subtitle: 'Canned message templates the bot uses for acks, nudges, and DMs.' },
  'Timing':      { emoji: '⏰', color: '#d97706', bg: 'rgba(217,119,6,.12)',  subtitle: 'When the bot checks in, pings, or follows up.' },
  'Enforcement': { emoji: '⚖️', color: '#e11d48', bg: 'rgba(225,29,72,.12)',  subtitle: 'How strict the bot is when format or sign-in rules are missed.' },
  'Formatting':  { emoji: '📝', color: '#0284c7', bg: 'rgba(2,132,199,.12)',  subtitle: 'How messages are rendered — bullets, headers, emoji density.' },
  'Tone':        { emoji: '💬', color: '#db2777', bg: 'rgba(219,39,119,.12)', subtitle: 'How friendly, formal, or terse the bot sounds.' },
  'Escalation':  { emoji: '🚨', color: '#e11d48', bg: 'rgba(225,29,72,.12)',  subtitle: 'When and how the bot escalates to admins or on-call.' },
};

function getCatMeta(cat) {
  return CATEGORY_META[cat] || {
    emoji: '⚙',
    color: 'var(--accent)',
    bg: 'color-mix(in srgb, var(--accent) 12%, transparent)',
    subtitle: 'Settings in this category.',
  };
}

/* ── Helpers ─────────────────────────────────────────────── */

function highlight(text, query) {
  if (!query || !text) return text;
  const idx = text.toLowerCase().indexOf(query.toLowerCase());
  if (idx === -1) return text;
  return (
    <span>
      {text.slice(0, idx)}
      <mark style={{ background: 'var(--accent)', color: 'var(--bg)', borderRadius: 2, padding: '0 2px' }}>
        {text.slice(idx, idx + query.length)}
      </mark>
      {text.slice(idx + query.length)}
    </span>
  );
}

function matchesSetting(entry, q) {
  if (!q) return true;
  const lower = q.toLowerCase();
  return (
    entry.key.toLowerCase().includes(lower) ||
    (entry.label || '').toLowerCase().includes(lower) ||
    (entry.help || '').toLowerCase().includes(lower) ||
    (entry.category || '').toLowerCase().includes(lower)
  );
}

function coerceValue(value, type) {
  if (type === 'bool') return Boolean(value);
  if (type === 'int') return parseInt(value, 10) || 0;
  if (type === 'float') return parseFloat(value) || 0;
  if (type === 'string_list') {
    if (Array.isArray(value)) return value;
    return value ? value.split('\n').map(s => s.trim()).filter(Boolean) : [];
  }
  return value;
}

function displayValue(value, type) {
  if (type === 'string_list') {
    if (Array.isArray(value)) return value.join('\n');
    return value || '';
  }
  if (value === null || value === undefined) return '';
  return String(value);
}

/* ── Tag editor for string_list ────────────────────────────── */
function TagEditor({ value, onChange, disabled }) {
  const items = Array.isArray(value) ? value : (value ? String(value).split('\n').filter(Boolean) : []);
  const [draft, setDraft] = React.useState('');

  function addTag() {
    const t = draft.trim();
    if (!t || items.includes(t)) { setDraft(''); return; }
    onChange([...items, t]);
    setDraft('');
  }

  function removeTag(i) {
    onChange(items.filter((_, idx) => idx !== i));
  }

  return (
    <div>
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 5, marginBottom: 6 }}>
        {items.map((tag, i) => (
          <span
            key={i}
            style={{
              display: 'inline-flex', alignItems: 'center', gap: 4,
              background: 'color-mix(in srgb, var(--accent) 14%, var(--card))',
              border: '1px solid color-mix(in srgb, var(--accent) 30%, transparent)',
              borderRadius: 20, padding: '2px 8px 2px 10px',
              fontSize: 12, color: 'var(--text-1)',
            }}
          >
            {tag}
            {!disabled && (
              <button
                onClick={() => removeTag(i)}
                style={{
                  background: 'none', border: 'none', cursor: 'pointer',
                  color: 'var(--text-3)', padding: 0, lineHeight: 1,
                  fontSize: 13, marginLeft: 1,
                }}
                title={(window.appT||(s=>s))('Remove')}
              >
                ×
              </button>
            )}
          </span>
        ))}
        {items.length === 0 && (
          <span style={{ fontSize: 11, color: 'var(--text-3)', fontStyle: 'italic' }}>{(window.appT||(s=>s))('No items — add below')}</span>
        )}
      </div>
      {!disabled && (
        <div style={{ display: 'flex', gap: 6 }}>
          <input
            className="input input-sm"
            value={draft}
            onChange={e => setDraft(e.target.value)}
            onKeyDown={e => { if (e.key === 'Enter') { e.preventDefault(); addTag(); } }}
            placeholder={(window.appT||(s=>s))('Add item...')}
            style={{ flex: 1, fontSize: 12 }}
          />
          <button
            className="btn btn-ghost btn-xs"
            onClick={addTag}
            style={{ fontSize: 11, padding: '2px 10px', whiteSpace: 'nowrap' }}
          >
            + Add
          </button>
        </div>
      )}
    </div>
  );
}

/* ── Setting Input ─────────────────────────────────────────── */
function SettingInput({ entry, value, onChange, disabled }) {
  const { type, min, max } = entry;

  if (type === 'bool') {
    const checked = Boolean(value);
    return (
      <label style={{
        display: 'inline-flex', alignItems: 'center', gap: 8,
        cursor: disabled ? 'not-allowed' : 'pointer', userSelect: 'none',
      }}>
        {/* Pill toggle */}
        <span
          onClick={disabled ? undefined : () => onChange(!checked)}
          style={{
            position: 'relative', display: 'inline-block',
            width: 36, height: 20, flexShrink: 0,
            background: checked ? 'var(--accent)' : 'var(--border)',
            borderRadius: 10, transition: 'background .2s',
            cursor: disabled ? 'not-allowed' : 'pointer',
          }}
        >
          <span style={{
            position: 'absolute', top: 2, left: checked ? 18 : 2,
            width: 16, height: 16, background: 'white', borderRadius: '50%',
            transition: 'left .15s', boxShadow: '0 1px 3px rgba(0,0,0,.25)',
          }} />
        </span>
        <span style={{ fontSize: 13, color: checked ? 'var(--accent)' : 'var(--text-3)' }}>
          {checked ? (window.appT||(s=>s))('Enabled') : (window.appT||(s=>s))('Disabled')}
        </span>
      </label>
    );
  }

  if (type === 'int' || type === 'float') {
    return (
      <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
        <input
          type="number"
          className="input input-sm"
          value={value === undefined || value === null ? '' : value}
          min={min}
          max={max}
          step={type === 'float' ? 0.5 : 1}
          disabled={disabled}
          onChange={e => onChange(type === 'float' ? parseFloat(e.target.value) || 0 : parseInt(e.target.value, 10) || 0)}
          style={{ width: 90, textAlign: 'right' }}
        />
        {entry.unit && (
          <span style={{ fontSize: 12, color: 'var(--text-3)' }}>{entry.unit}</span>
        )}
      </div>
    );
  }

  if (type === 'string_list') {
    return <TagEditor value={value} onChange={onChange} disabled={disabled} />;
  }

  if (type === 'text') {
    const rows = Math.max(3, Math.min(8, (String(value || '').match(/\n/g) || []).length + 2));
    return (
      <textarea
        className="input"
        value={value || ''}
        rows={rows}
        disabled={disabled}
        onChange={e => onChange(e.target.value)}
        style={{ fontSize: 13, resize: 'vertical', minHeight: 72 }}
      />
    );
  }

  return (
    <input
      type="text"
      className="input input-sm"
      value={value || ''}
      disabled={disabled}
      onChange={e => onChange(e.target.value)}
      style={{ flex: 1 }}
    />
  );
}

/* ── Single Setting Row ─────────────────────────────────────── */
function SettingRow({ entry, currentValue, defaultValue, localValue, onChange, query }) {
  const isDirty    = localValue !== undefined;
  const isDefault  = !isDirty
    ? JSON.stringify(currentValue) === JSON.stringify(defaultValue)
    : JSON.stringify(localValue) === JSON.stringify(defaultValue);
  const displayVal = isDirty ? localValue : currentValue;

  const statusLabel = isDirty
    ? (window.appT||(s=>s))('Unsaved')
    : isDefault
      ? (window.appT||(s=>s))('Default')
      : (window.appT||(s=>s))('Customized');
  const statusColor = isDirty ? '#d97706' : isDefault ? 'var(--text-3)' : 'var(--accent)';

  return (
    <div
      className="setting-row"
      style={{
        display: 'grid',
        gridTemplateColumns: '2fr 2fr auto',
        gap: 12,
        padding: '12px 16px',
        borderBottom: '1px solid var(--border)',
        background: isDirty ? 'color-mix(in srgb, #d97706 5%, transparent)' : 'transparent',
        alignItems: 'start',
        transition: 'background 0.2s',
      }}
    >
      {/* Label column */}
      <div style={{ minWidth: 0, paddingTop: 2 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginBottom: 3 }}>
          <span style={{ fontWeight: 500, fontSize: 13 }}>
            {highlight(entry.label || entry.key, query)}
          </span>
          {entry.help && (
            <span
              title={entry.help}
              style={{
                cursor: 'help', color: 'var(--text-3)', fontSize: 11,
                width: 15, height: 15, border: '1px solid var(--border)',
                borderRadius: '50%', display: 'inline-flex', alignItems: 'center',
                justifyContent: 'center', flexShrink: 0,
              }}
            >?</span>
          )}
        </div>
        <div style={{ fontSize: 11, color: 'var(--text-3)', fontFamily: 'var(--font-mono)', marginBottom: 4 }}>
          {highlight(entry.key, query)}
        </div>
        {entry.help && (
          <div style={{ fontSize: 11, color: 'var(--text-3)', lineHeight: 1.4 }}>
            {highlight(entry.help, query)}
          </div>
        )}
      </div>

      {/* Control column */}
      <div>
        <SettingInput entry={entry} value={displayVal} onChange={val => onChange(entry.key, val)} />
      </div>

      {/* Status + actions column */}
      <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 4, paddingTop: 2, minWidth: 90 }}>
        <span style={{
          fontSize: 10, padding: '1px 6px', borderRadius: 4,
          border: `1px solid ${statusColor}`,
          color: statusColor, fontWeight: 600, whiteSpace: 'nowrap',
          opacity: (!isDirty && isDefault) ? 0.6 : 1,
        }}>
          {statusLabel}
        </span>
        {isDirty && (
          <button
            className="btn btn-ghost btn-xs"
            title={(window.appT||(s=>s))('Discard unsaved change')}
            onClick={() => onChange(entry.key, undefined)}
            style={{ fontSize: 10, padding: '1px 6px' }}
          >
            {(window.appT||(s=>s))('Discard')}
          </button>
        )}
        {!isDefault && !isDirty && (
          <button
            className="btn btn-ghost btn-xs"
            title={(window.appT||(s=>s))('Reset to default')}
            onClick={() => onChange(entry.key, defaultValue)}
            style={{ fontSize: 10, padding: '1px 6px', color: 'var(--text-3)' }}
          >
            {(window.appT||(s=>s))('Reset')}
          </button>
        )}
      </div>
    </div>
  );
}

/* ── Category Accordion ─────────────────────────────────────── */
function CategoryAccordion({ category, settings, currentValues, defaultValues, localEdits, onChange, query, forceOpen }) {
  const [open, setOpen] = React.useState(false);
  const visible     = settings.filter(s => matchesSetting(s, query));
  if (visible.length === 0) return null;

  const isOpen      = forceOpen || open;
  const dirtyCount  = visible.filter(s => localEdits[s.key] !== undefined).length;
  const customCount = visible.filter(s => {
    const cur = currentValues[s.key];
    const def = defaultValues[s.key];
    return JSON.stringify(cur) !== JSON.stringify(def);
  }).length;

  const meta = getCatMeta(category);

  return (
    <div style={{
      border: '1px solid var(--border)', borderRadius: 10,
      overflow: 'hidden', marginBottom: 10,
      boxShadow: isOpen ? '0 2px 8px rgba(0,0,0,.08)' : 'none',
      transition: 'box-shadow .2s',
    }}>
      <button
        className="btn btn-ghost"
        onClick={() => setOpen(o => !o)}
        style={{
          width: '100%', textAlign: 'left', padding: '12px 16px',
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          borderRadius: 0, background: isOpen ? 'color-mix(in srgb, var(--accent) 3%, var(--card))' : 'var(--card)',
          transition: 'background .2s',
        }}
      >
        <span style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          {/* Icon badge */}
          <span style={{
            width: 32, height: 32, borderRadius: '50%',
            background: meta.bg, border: `1.5px solid ${meta.color}30`,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            fontSize: 16, flexShrink: 0,
          }}>
            {meta.emoji}
          </span>
          {/* Text */}
          <span style={{ minWidth: 0 }}>
            <span style={{ fontWeight: 700, fontSize: 14, display: 'block', color: 'var(--text-1)' }}>
              {highlight(category, query)}
            </span>
            {meta.subtitle && (
              <span style={{ fontSize: 12, color: 'var(--text-2)', display: 'block', marginTop: 2, lineHeight: 1.35 }}>
                {(window.appT||(s=>s))(meta.subtitle)}
              </span>
            )}
            <span style={{ fontSize: 11, color: 'var(--text-3)', display: 'block', marginTop: 2 }}>
              {visible.length} {(window.appT||(s=>s))(visible.length !== 1 ? 'settings' : 'setting')}
              {customCount > 0 ? ` · ${customCount} ${(window.appT||(s=>s))('overridden')}` : ''}
            </span>
          </span>
        </span>
        <span style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
          {/* State chip */}
          {dirtyCount > 0 ? (
            <span style={{
              fontSize: 10, padding: '2px 8px', borderRadius: 4,
              background: 'rgba(217,119,6,.15)', color: '#d97706',
              border: '1px solid rgba(217,119,6,.3)', fontWeight: 700,
            }}>
              {dirtyCount} {(window.appT||(s=>s))('pending')}
            </span>
          ) : customCount > 0 ? (
            <span style={{
              fontSize: 10, padding: '2px 8px', borderRadius: 4,
              background: meta.bg, color: meta.color,
              border: `1px solid ${meta.color}40`, fontWeight: 600,
            }}>
              {customCount} {(window.appT||(s=>s))('customized')}
            </span>
          ) : (
            <span style={{
              fontSize: 10, padding: '2px 8px', borderRadius: 4,
              background: 'var(--card)', color: 'var(--text-3)',
              border: '1px solid var(--border)', fontWeight: 500,
            }}>
              {(window.appT||(s=>s))('All defaults')}
            </span>
          )}
          <span style={{ color: 'var(--text-3)', fontSize: 12, marginLeft: 2 }}>
            {isOpen ? '▲' : '▼'}
          </span>
        </span>
      </button>

      {isOpen && (
        <div>
          {/* Grid header */}
          <div style={{
            display: 'grid', gridTemplateColumns: '2fr 2fr auto',
            gap: 12, padding: '6px 16px',
            background: 'var(--bg)', borderTop: '1px solid var(--border)',
            borderBottom: '1px solid var(--border)',
          }}>
            <span style={{ fontSize: 10, fontWeight: 600, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: '.05em' }}>{(window.appT||(s=>s))('Setting')}</span>
            <span style={{ fontSize: 10, fontWeight: 600, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: '.05em' }}>{(window.appT||(s=>s))('Value')}</span>
            <span style={{ fontSize: 10, fontWeight: 600, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: '.05em', textAlign: 'right', minWidth: 90 }}>{(window.appT||(s=>s))('Status')}</span>
          </div>
          {visible.map(entry => (
            <SettingRow
              key={entry.key}
              entry={entry}
              currentValue={currentValues[entry.key]}
              defaultValue={defaultValues[entry.key]}
              localValue={localEdits[entry.key]}
              onChange={onChange}
              query={query}
            />
          ))}
        </div>
      )}
    </div>
  );
}

/* ── Presets Panel ──────────────────────────────────────────── */
function PresetsPanel({ onApply, onSave, saving }) {
  const { toast } = _useToast();
  const [presets, setPresets]        = React.useState([]);
  const [loadingPresets, setLoading] = React.useState(true);
  const [saveName, setSaveName]      = React.useState('');
  const [saveDesc, setSaveDesc]      = React.useState('');
  const [showSaveForm, setShowSave]  = React.useState(false);
  const [applying, setApplying]      = React.useState(null);

  React.useEffect(() => {
    fetch('/api/bot-behavior/presets', { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : { presets: [] })
      .then(d => setPresets(d.presets || []))
      .catch(() => setPresets([]))
      .finally(() => setLoading(false));
  }, []);

  const handleApply = async (preset) => {
    setApplying(preset.id);
    try {
      const r = await fetch(`/api/bot-behavior/presets/${preset.id}/apply`, {
        method: 'POST',
        credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ scope: 'global' }),
      });
      if (!r.ok) throw new Error(await r.text());
      const d = await r.json();
      toast({ kind: 'ok', message: `${(window.appT||(s=>s))('Applied')} "${preset.name}" — ${d.count} ${(window.appT||(s=>s))('settings updated')}` });
      onApply();
    } catch (e) {
      toast({ kind: 'err', message: `${(window.appT||(s=>s))('Failed to apply preset:')} ${e.message}` });
    } finally {
      setApplying(null);
    }
  };

  const handleSave = async () => {
    if (!saveName.trim()) { toast({ kind: 'warn', message: (window.appT||(s=>s))('Enter a preset name') }); return; }
    try {
      const r = await fetch('/api/bot-behavior/presets', {
        method: 'POST',
        credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ name: saveName.trim(), description: saveDesc.trim() }),
      });
      if (!r.ok) throw new Error(await r.text());
      const d = await r.json();
      toast({ kind: 'ok', message: `${(window.appT||(s=>s))('Preset')} "${d.name}" ${(window.appT||(s=>s))('saved')}` });
      setSaveName(''); setSaveDesc(''); setShowSave(false);
      fetch('/api/bot-behavior/presets', { credentials: 'same-origin' })
        .then(r => r.ok ? r.json() : { presets: [] })
        .then(d => setPresets(d.presets || []))
        .catch(() => {});
    } catch (e) {
      toast({ kind: 'err', message: `${(window.appT||(s=>s))('Save failed:')} ${e.message}` });
    }
  };

  const builtins = presets.filter(p => p.type === 'builtin');
  const customs  = presets.filter(p => p.type === 'custom');

  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
      {/* Built-ins */}
      <div style={{ fontSize: 10, fontWeight: 700, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: '.07em', marginBottom: 2 }}>
        {(window.appT||(s=>s))('Built-in presets')}
      </div>
      {loadingPresets ? (
        <div style={{ color: 'var(--text-3)', fontSize: 12, padding: '8px 0' }}>{(window.appT||(s=>s))('Loading...')}</div>
      ) : builtins.length === 0 ? (
        <div style={{ color: 'var(--text-3)', fontSize: 12, fontStyle: 'italic' }}>{(window.appT||(s=>s))('None available')}</div>
      ) : builtins.map(p => (
        <div
          key={p.id}
          style={{
            borderRadius: 8, padding: '10px 12px',
            background: 'var(--bg)', border: '1px solid var(--border)',
            display: 'flex', flexDirection: 'column', gap: 6,
            transition: 'box-shadow .15s, transform .15s',
          }}
          onMouseEnter={e => { e.currentTarget.style.boxShadow = '0 2px 8px rgba(0,0,0,.12)'; e.currentTarget.style.transform = 'translateY(-1px)'; }}
          onMouseLeave={e => { e.currentTarget.style.boxShadow = 'none'; e.currentTarget.style.transform = 'none'; }}
        >
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <span style={{ fontWeight: 600, fontSize: 13 }}>{p.name}</span>
            <button
              className="btn btn-primary btn-sm"
              onClick={() => handleApply(p)}
              disabled={applying === p.id}
              style={{ fontSize: 11, padding: '4px 12px', flexShrink: 0 }}
            >
              {applying === p.id ? (window.appT||(s=>s))('Applying...') : (window.appT||(s=>s))('Apply')}
            </button>
          </div>
          {p.description && (
            <div style={{ fontSize: 11, color: 'var(--text-3)', lineHeight: 1.4 }}>{p.description}</div>
          )}
        </div>
      ))}

      {/* Custom presets */}
      {customs.length > 0 && (
        <>
          <div style={{ fontSize: 10, fontWeight: 700, color: 'var(--text-3)', textTransform: 'uppercase', letterSpacing: '.07em', marginTop: 8, marginBottom: 2 }}>
            {(window.appT||(s=>s))('Custom presets')}
          </div>
          {customs.map(p => (
            <div
              key={p.id}
              style={{
                borderRadius: 8, padding: '10px 12px',
                background: 'var(--bg)', border: '1px solid var(--border)',
                display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: 8,
              }}
            >
              <div style={{ minWidth: 0 }}>
                <div style={{ fontWeight: 500, fontSize: 13 }}>{p.name}</div>
                {p.description && <div style={{ fontSize: 11, color: 'var(--text-3)' }}>{p.description}</div>}
              </div>
              <button
                className="btn btn-sm btn-primary"
                onClick={() => handleApply(p)}
                disabled={applying === p.id}
                style={{ fontSize: 11, padding: '4px 12px', flexShrink: 0 }}
              >
                {applying === p.id ? '...' : (window.appT||(s=>s))('Apply')}
              </button>
            </div>
          ))}
        </>
      )}

      {/* Save current as preset */}
      <div style={{ marginTop: 4 }}>
        {!showSaveForm ? (
          <button
            className="btn btn-ghost btn-sm"
            onClick={() => setShowSave(true)}
            style={{
              width: '100%', fontSize: 12,
              border: '1.5px dashed var(--border)',
              borderRadius: 8, padding: '10px 0',
              color: 'var(--text-2)',
            }}
          >
            {(window.appT||(s=>s))('+ Save current state as preset')}
          </button>
        ) : (
          <div style={{
            border: '1px solid var(--border)', borderRadius: 8,
            padding: 12, display: 'flex', flexDirection: 'column', gap: 7,
            background: 'var(--bg)',
          }}>
            <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--text-1)', marginBottom: 2 }}>{(window.appT||(s=>s))('Save preset')}</div>
            <input
              className="input input-sm"
              placeholder={(window.appT||(s=>s))('Preset name')}
              value={saveName}
              onChange={e => setSaveName(e.target.value)}
              autoFocus
            />
            <input
              className="input input-sm"
              placeholder={(window.appT||(s=>s))('Description (optional)')}
              value={saveDesc}
              onChange={e => setSaveDesc(e.target.value)}
            />
            <div style={{ display: 'flex', gap: 6 }}>
              <button className="btn btn-sm btn-primary" onClick={handleSave} style={{ flex: 1, fontSize: 12 }}>{(window.appT||(s=>s))('Save')}</button>
              <button className="btn btn-sm btn-ghost" onClick={() => { setShowSave(false); setSaveName(''); setSaveDesc(''); }} style={{ fontSize: 12 }}>{(window.appT||(s=>s))('Cancel')}</button>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

/* ── Diff Banner ────────────────────────────────────────────── */
function DiffBanner({ changes, lastUndoToken, onUndo, onDismiss }) {
  if (!changes || changes.length === 0) return null;
  return (
    <div style={{
      background: 'color-mix(in srgb, var(--accent) 10%, var(--card))',
      border: '1px solid var(--accent)',
      borderRadius: 8, padding: '10px 14px',
      display: 'flex', alignItems: 'center', justifyContent: 'space-between',
      gap: 12, marginBottom: 16, fontSize: 13,
    }}>
      <span>
        <strong>{changes.length}</strong> {(window.appT||(s=>s))(changes.length !== 1 ? 'settings saved successfully.' : 'setting saved successfully.')}
        {lastUndoToken && (
          <button
            className="btn btn-ghost btn-xs"
            onClick={onUndo}
            style={{ marginLeft: 10, fontSize: 11, textDecoration: 'underline', color: 'var(--accent)' }}
          >
            {(window.appT||(s=>s))('Undo last change')}
          </button>
        )}
      </span>
      <button className="btn btn-ghost btn-xs" onClick={onDismiss}>{(window.appT||(s=>s))('Dismiss')}</button>
    </div>
  );
}

/* ── Main Page ──────────────────────────────────────────────── */
function BotBehaviorPage() {
  const { toast } = _useToast();
  const [registry, setRegistry]       = React.useState([]);
  const [categories, setCategories]   = React.useState([]);
  const [loading, setLoading]         = React.useState(true);
  const [saving, setSaving]           = React.useState(false);
  const [query, setQuery]             = React.useState('');
  const [localEdits, setLocalEdits]   = React.useState({});
  const [lastSaved, setLastSaved]     = React.useState([]);
  const [lastUndoToken, setUndoToken] = React.useState(null);
  const [showDiff, setShowDiff]       = React.useState(false);
  const [pageTab, setPageTab]         = React.useState('settings');  // Q12: settings | triggers
  const searchRef = React.useRef(null);

  // Auto-focus search on mount
  React.useEffect(() => { if (searchRef.current) searchRef.current.focus(); }, []);

  const fetchSettings = React.useCallback(() => {
    setLoading(true);
    fetch('/api/bot-behavior/settings', { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : Promise.reject(r.status))
      .then(d => {
        setRegistry(d.settings || []);
        setCategories(d.categories || []);
      })
      .catch(err => toast({ kind: 'err', message: `${(window.appT||(s=>s))('Failed to load settings:')} ${err}` }))
      .finally(() => setLoading(false));
  }, []);

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

  const currentValues = React.useMemo(() => {
    const map = {};
    registry.forEach(s => { map[s.key] = s.current_value; });
    return map;
  }, [registry]);

  const defaultValues = React.useMemo(() => {
    const map = {};
    registry.forEach(s => { map[s.key] = s.default; });
    return map;
  }, [registry]);

  const handleChange = React.useCallback((key, value) => {
    setLocalEdits(prev => {
      if (value === undefined) {
        const next = { ...prev };
        delete next[key];
        return next;
      }
      return { ...prev, [key]: value };
    });
  }, []);

  const dirtyKeys = Object.keys(localEdits);
  const hasDirty  = dirtyKeys.length > 0;

  // Compute global stats
  const totalCustomized = React.useMemo(() => {
    return registry.filter(s => {
      return JSON.stringify(s.current_value) !== JSON.stringify(s.default);
    }).length;
  }, [registry]);

  const handleSave = async () => {
    if (!hasDirty) return;
    setSaving(true);
    const saved = [];
    let lastToken = null;
    try {
      for (const key of dirtyKeys) {
        const r = await fetch(`/api/bot-behavior/settings/${encodeURIComponent(key)}`, {
          method: 'PUT',
          credentials: 'same-origin',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ value: localEdits[key], scope: 'global' }),
        });
        if (!r.ok) {
          toast({ kind: 'warn', message: `${(window.appT||(s=>s))('Failed to save')} ${key}: ${await r.text()}` });
          continue;
        }
        saved.push(key);
      }
      if (saved.length > 0) {
        try {
          const ar = await fetch('/api/bot-behavior/audit?limit=1', { credentials: 'same-origin' });
          if (ar.ok) {
            const ad = await ar.json();
            lastToken = (ad.audit || [])[0]?.undo_token || null;
          }
        } catch (_) {}
        setLastSaved(saved);
        setUndoToken(lastToken);
        setShowDiff(true);
        setLocalEdits({});
        fetchSettings();
        toast({ kind: 'ok', message: `${(window.appT||(s=>s))('Saved')} ${saved.length} ${(window.appT||(s=>s))(saved.length !== 1 ? 'settings' : 'setting')}` });
      }
    } finally {
      setSaving(false);
    }
  };

  const handleDiscard = () => { setLocalEdits({}); };

  const handleUndo = async () => {
    if (!lastUndoToken) return;
    try {
      const r = await fetch(`/api/bot-behavior/audit/undo/${lastUndoToken}`, {
        method: 'POST', credentials: 'same-origin',
      });
      if (!r.ok) throw new Error(await r.text());
      toast({ kind: 'ok', message: (window.appT||(s=>s))('Change reverted') });
      setUndoToken(null);
      setShowDiff(false);
      fetchSettings();
    } catch (e) {
      toast({ kind: 'err', message: `${(window.appT||(s=>s))('Undo failed:')} ${e.message}` });
    }
  };

  const forceOpenAll = query.trim().length > 0;

  const settingsByCategory = React.useMemo(() => {
    const map = {};
    (registry || []).forEach(s => {
      if (!map[s.category]) map[s.category] = [];
      map[s.category].push(s);
    });
    return map;
  }, [registry]);

  const hasSearchResults = query.trim()
    ? (registry || []).some(s => matchesSetting(s, query))
    : true;

  return (
    <div className="page-root" style={{ maxWidth: 1100, margin: '0 auto', padding: '20px 16px 100px' }}>

      {/* ── Header ── */}
      <div style={{
        display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between',
        marginBottom: 20, gap: 16, flexWrap: 'wrap',
      }}>
        <div>
          <h1 style={{ fontSize: 22, fontWeight: 700, margin: 0, display: 'flex', alignItems: 'center', gap: 8 }}>
            <span style={{
              width: 36, height: 36, borderRadius: 9,
              background: 'color-mix(in srgb, var(--accent) 15%, transparent)',
              display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
              flexShrink: 0,
            }}>
              {/* Inline sliders glyph for the header */}
              <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
                <path d="M4 6h16"/><path d="M4 12h16"/><path d="M4 18h16"/>
                <circle cx="8" cy="6" r="2" fill="var(--card)" stroke="var(--accent)" strokeWidth="1.8"/>
                <circle cx="16" cy="12" r="2" fill="var(--card)" stroke="var(--accent)" strokeWidth="1.8"/>
                <circle cx="10" cy="18" r="2" fill="var(--card)" stroke="var(--accent)" strokeWidth="1.8"/>
              </svg>
            </span>
            {(window.appT||(s=>s))('Bot Behavior')}
          </h1>
          <p style={{ margin: '4px 0 0', color: 'var(--text-2)', fontSize: 13 }}>
            {(window.appT||(s=>s))('Configure how the bot interacts with your team. Changes apply within 60 seconds.')}
          </p>
        </div>

        {/* Stat strip */}
        {!loading && (
          <div style={{
            display: 'flex', alignItems: 'center', gap: 16, flexShrink: 0,
            background: 'var(--card)', border: '1px solid var(--border)',
            borderRadius: 8, padding: '8px 14px', fontSize: 12,
          }}>
            <div style={{ textAlign: 'center' }}>
              <div style={{ fontWeight: 700, fontSize: 18, color: totalCustomized > 0 ? 'var(--accent)' : 'var(--text-2)' }}>
                {totalCustomized}
              </div>
              <div style={{ color: 'var(--text-3)', marginTop: 1 }}>{(window.appT||(s=>s))('customized')}</div>
            </div>
            <div style={{ width: 1, height: 28, background: 'var(--border)' }} />
            <div style={{ textAlign: 'center' }}>
              <div style={{ fontWeight: 700, fontSize: 18, color: 'var(--text-1)' }}>
                {registry.length}
              </div>
              <div style={{ color: 'var(--text-3)', marginTop: 1 }}>{(window.appT||(s=>s))('total settings')}</div>
            </div>
            <div style={{ width: 1, height: 28, background: 'var(--border)' }} />
            <div style={{ textAlign: 'center' }}>
              <div style={{ fontWeight: 700, fontSize: 18, color: 'var(--text-1)' }}>
                {categories.length}
              </div>
              <div style={{ color: 'var(--text-3)', marginTop: 1 }}>{(window.appT||(s=>s))('categories')}</div>
            </div>
          </div>
        )}
      </div>

      {/* Q12: top-level tab bar — Settings | Triggers */}
      <div style={{ display: 'flex', gap: 4, marginBottom: 16, borderBottom: '1px solid var(--line)' }}>
        <button
          onClick={() => setPageTab('settings')}
          className={pageTab === 'settings' ? 'btn btn-pri' : 'btn btn-ghost'}
          style={{ fontSize: 13, padding: '8px 16px', borderRadius: '6px 6px 0 0' }}>
          {(window.appT||(s=>s))('Settings')}
        </button>
        <button
          onClick={() => setPageTab('triggers')}
          className={pageTab === 'triggers' ? 'btn btn-pri' : 'btn btn-ghost'}
          style={{ fontSize: 13, padding: '8px 16px', borderRadius: '6px 6px 0 0' }}>
          {(window.appT||(s=>s))('Triggers')}
        </button>
        <button
          onClick={() => setPageTab('automations')}
          className={pageTab === 'automations' ? 'btn btn-pri' : 'btn btn-ghost'}
          style={{ fontSize: 13, padding: '8px 16px', borderRadius: '6px 6px 0 0' }}>
          {(window.appT||(s=>s))('Automations')}
        </button>
        <button
          onClick={() => setPageTab('botlogic')}
          className={pageTab === 'botlogic' ? 'btn btn-pri' : 'btn btn-ghost'}
          style={{ fontSize: 13, padding: '8px 16px', borderRadius: '6px 6px 0 0' }}>
          {(window.appT||(s=>s))('Bot logic')}
        </button>
        <button
          onClick={() => setPageTab('troubleshoot')}
          className={pageTab === 'troubleshoot' ? 'btn btn-pri' : 'btn btn-ghost'}
          style={{ fontSize: 13, padding: '8px 16px', borderRadius: '6px 6px 0 0' }}>
          {(window.appT||(s=>s))('🛠 Troubleshoot')}
        </button>
      </div>

      {pageTab === 'triggers' && <TriggersPanel />}
      {pageTab === 'automations' && <AutomationsPanel />}
      {pageTab === 'troubleshoot' && <TroubleshootPanel />}
      {pageTab === 'botlogic' && (
        <div className="card" style={{ marginBottom: 16 }}>
          <SectionHeader title={(window.appT||(s=>s))('🤖 Bot logic')}
            tip={(window.appT||(s=>s))('Tunable thresholds — late_buffer_minutes, inactivity_hours, strike_cap, dm_cooldown, burnout_hours_per_day. Restart-free; reads BotState on every check.')}
            sub={(window.appT||(s=>s))('Strike cap, thresholds, keyword lists — change without redeploying')} />
          <div style={{ padding: 16 }}>
            {window.BotLogicSection ? <window.BotLogicSection /> : <div className="muted">{(window.appT||(s=>s))('Loading…')}</div>}
          </div>
        </div>
      )}

      {pageTab === 'settings' && (
      <>
      {/* ── How it works intro ── */}
      <div style={{
        background: 'var(--bg-elev-2, var(--card))',
        border: '1px solid var(--line, var(--border))',
        borderRadius: 'var(--r-md, 10px)',
        padding: '14px 16px',
        marginBottom: 16,
      }}>
        <div style={{
          fontWeight: 700, fontSize: 13, color: 'var(--text-1)',
          marginBottom: 6, display: 'flex', alignItems: 'center', gap: 6,
        }}>
          <span>{(window.appT||(s=>s))('How it works')}</span>
        </div>
        <div style={{ fontSize: 12.5, color: 'var(--text-2)', lineHeight: 1.55 }}>
          {(window.appT||(s=>s))('These settings control how the AI bot speaks, when it interrupts, and how strict it is. Changes are saved per-category and apply within ~60 seconds. Use')} <strong>{(window.appT||(s=>s))('Presets')}</strong> {(window.appT||(s=>s))('on the right for one-click switches between strict, friendly, or silent profiles — or hit')} <strong>{(window.appT||(s=>s))('Save current state as preset')}</strong> {(window.appT||(s=>s))('to bottle your own mix.')}
        </div>
      </div>

      {/* ── Diff banner ── */}
      {showDiff && (
        <DiffBanner
          changes={lastSaved}
          lastUndoToken={lastUndoToken}
          onUndo={handleUndo}
          onDismiss={() => setShowDiff(false)}
        />
      )}

      {/* ── Search bar ── */}
      <div style={{ marginBottom: 18, position: 'relative' }}>
        <span style={{
          position: 'absolute', left: 12, top: '50%', transform: 'translateY(-50%)',
          color: 'var(--text-3)', pointerEvents: 'none',
          display: 'flex', alignItems: 'center',
        }}>
          <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
            <circle cx="10.5" cy="10.5" r="6.5"/><path d="m20 20-4.4-4.4"/>
          </svg>
        </span>
        <input
          ref={searchRef}
          type="search"
          placeholder={(window.appT||(s=>s))('Search settings by name, key, or description...')}
          value={query}
          onChange={e => setQuery(e.target.value)}
          style={{
            width: '100%', maxWidth: 460, height: 44, fontSize: 14,
            padding: '0 36px 0 36px',
            border: '1.5px solid var(--border)', borderRadius: 8,
            background: 'var(--card)', color: 'var(--text-1)',
            outline: 'none', boxSizing: 'border-box',
            transition: 'border-color .15s, box-shadow .15s',
          }}
          onFocus={e => { e.target.style.borderColor = 'var(--accent)'; e.target.style.boxShadow = '0 0 0 3px color-mix(in srgb, var(--accent) 20%, transparent)'; }}
          onBlur={e => { e.target.style.borderColor = 'var(--border)'; e.target.style.boxShadow = 'none'; }}
        />
        {query && (
          <button
            onClick={() => setQuery('')}
            style={{
              position: 'absolute', right: query ? 'calc(100% - 448px)' : 12,
              top: '50%', transform: 'translateY(-50%)',
              background: 'none', border: 'none', cursor: 'pointer',
              color: 'var(--text-3)', padding: 4, display: 'flex', alignItems: 'center',
            }}
            title={(window.appT||(s=>s))('Clear search')}
          >
            <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round">
              <path d="M6 6 18 18M18 6 6 18"/>
            </svg>
          </button>
        )}
      </div>

      {loading ? (
        <div style={{ color: 'var(--text-3)', padding: 48, textAlign: 'center', fontSize: 13 }}>
          {(window.appT||(s=>s))('Loading settings...')}
        </div>
      ) : (
        <div style={{ display: 'grid', gridTemplateColumns: '1fr 290px', gap: 20, alignItems: 'start' }}>

          {/* ── Accordion list ── */}
          <div>
            {!hasSearchResults && (
              <div style={{ color: 'var(--text-3)', padding: 32, textAlign: 'center', fontSize: 13, border: '1px dashed var(--border)', borderRadius: 10 }}>
                {(window.appT||(s=>s))('No settings match')} "{query}"
              </div>
            )}
            {categories.map(cat => (
              <CategoryAccordion
                key={cat}
                category={cat}
                settings={settingsByCategory[cat] || []}
                currentValues={currentValues}
                defaultValues={defaultValues}
                localEdits={localEdits}
                onChange={handleChange}
                query={query}
                forceOpen={forceOpenAll}
              />
            ))}
          </div>

          {/* ── Right rail — sticky presets ── */}
          <div style={{ position: 'sticky', top: 20 }}>
            <div style={{
              border: '1px solid var(--border)', borderRadius: 10,
              padding: 16, background: 'var(--card)',
              boxShadow: '0 2px 8px rgba(0,0,0,.07)',
            }}>
              <div style={{
                fontSize: 10, fontWeight: 700, color: 'var(--text-3)',
                textTransform: 'uppercase', letterSpacing: '.1em',
                marginBottom: 14,
              }}>
                {(window.appT||(s=>s))('Presets')}
              </div>
              <PresetsPanel
                onApply={fetchSettings}
                saving={saving}
              />
            </div>
          </div>
        </div>
      )}

      {/* ── Sticky save bar ── */}
      {hasDirty && (
        <div style={{
          position: 'fixed', bottom: 0, left: 0, right: 0,
          background: 'var(--card)', borderTop: '1.5px solid var(--accent)',
          padding: '12px 24px',
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
          zIndex: 100, boxShadow: '0 -6px 24px rgba(0,0,0,.18)',
          animation: 'slideUpIn .2s ease',
        }}>
          <span style={{ fontSize: 13, color: 'var(--text-2)' }}>
            <strong style={{ color: 'var(--accent)', fontSize: 15 }}>{dirtyKeys.length}</strong>
            {' '}{(window.appT||(s=>s))(dirtyKeys.length !== 1 ? 'changes pending' : 'change pending')}
          </span>
          <div style={{ display: 'flex', gap: 8 }}>
            <button
              className="btn btn-ghost btn-sm"
              onClick={handleDiscard}
              disabled={saving}
              style={{ color: 'var(--text-2)' }}
            >
              {(window.appT||(s=>s))('Cancel')}
            </button>
            <button
              className="btn btn-primary btn-sm"
              onClick={handleSave}
              disabled={saving}
              style={{ minWidth: 110 }}
            >
              {saving ? (window.appT||(s=>s))('Saving...') : `${(window.appT||(s=>s))('Save')} ${dirtyKeys.length} ${(window.appT||(s=>s))(dirtyKeys.length !== 1 ? 'changes' : 'change')}`}
            </button>
          </div>
        </div>
      )}
      </>
      )}
    </div>
  );
}

/* ============================================================
   TriggersPanel — Q12 admin-defined message triggers
   ============================================================ */
const TRIGGER_KINDS = [
  { key: 'keyword_reply', label: 'Keyword replies',  hint: 'When a message contains keywords, bot replies with canned text.' },
  { key: 'routing',       label: 'Routing',          hint: 'When keywords match, forward the message to a specialist user or room.' },
  { key: 'rule',          label: 'Rules',            hint: 'Event-driven: e.g. when a user reaches N strikes, DM admins.' },
  { key: 'command',       label: 'Slash commands',   hint: 'When a message starts with !cmd, run a defined action.' },
];

function TriggersPanel() {
  const { toast } = _useToast();
  const [kind, setKind]       = React.useState('keyword_reply');
  const [items, setItems]     = React.useState([]);
  const [loading, setLoading] = React.useState(false);
  const [editing, setEditing] = React.useState(null);  // form state for new/edit
  const [testText, setTT]     = React.useState('');
  const [testResult, setTR]   = React.useState(null);

  const load = React.useCallback(async () => {
    setLoading(true);
    try {
      const r = await fetch(`/api/triggers?kind=${kind}`, { credentials: 'same-origin' });
      if (r.ok) { const d = await r.json(); setItems(d.items || []); }
    } catch (_) {}
    finally { setLoading(false); }
  }, [kind]);

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

  const newDraft = () => {
    if (kind === 'keyword_reply') {
      return { kind, name: '', enabled: true, priority: 100,
               match: { keywords: [], match: 'any', case_sensitive: false, scope: 'both' },
               action: { reply: '' } };
    }
    if (kind === 'routing') {
      return { kind, name: '', enabled: true, priority: 100,
               match: { keywords: [], match: 'any', case_sensitive: false, scope: 'both' },
               action: { route_to: '', ack: '' } };
    }
    if (kind === 'rule') {
      return { kind, name: '', enabled: true, priority: 100,
               match: { event: 'strike', threshold: 3, window_days: 30 },
               action: { dm_admins: true, post_room: '' } };
    }
    return { kind, name: '', enabled: true, priority: 100,
             match: { command: '!' }, action: { reply: '' } };
  };

  const save = async () => {
    if (!editing) return;
    const isNew = !editing.id;
    try {
      const r = await fetch(isNew ? '/api/triggers' : `/api/triggers/${editing.id}`, {
        method: isNew ? 'POST' : 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify(editing),
      });
      if (!r.ok) throw new Error(`${r.status}`);
      toast({ kind: 'ok', message: isNew ? (window.appT||(s=>s))('Trigger created') : (window.appT||(s=>s))('Trigger saved') });
      setEditing(null);
      await load();
    } catch (e) { toast({ kind: 'err', message: (window.appT||(s=>s))('Save failed:') + ' ' + e.message }); }
  };

  const remove = async (id) => {
    if (!window.confirm((window.appT||(s=>s))('Delete this trigger?'))) return;
    try {
      const r = await fetch(`/api/triggers/${id}`, { method: 'DELETE', credentials: 'same-origin' });
      if (!r.ok) throw new Error(`${r.status}`);
      toast({ kind: 'ok', message: (window.appT||(s=>s))('Deleted') });
      await load();
    } catch (e) { toast({ kind: 'err', message: (window.appT||(s=>s))('Delete failed:') + ' ' + e.message }); }
  };

  const toggle = async (it) => {
    try {
      const r = await fetch(`/api/triggers/${it.id}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({ enabled: !it.enabled }),
      });
      if (!r.ok) throw new Error(`${r.status}`);
      await load();
    } catch (e) { toast({ kind: 'err', message: (window.appT||(s=>s))('Toggle failed:') + ' ' + e.message }); }
  };

  const runTest = async () => {
    setTR(null);
    if (!testText.trim()) return;
    try {
      const r = await fetch('/api/triggers/test', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        credentials: 'same-origin',
        body: JSON.stringify({ text: testText, scope: 'dm' }),
      });
      if (r.ok) setTR(await r.json());
    } catch (_) {}
  };

  return (
    <div style={{ marginTop: 16 }}>
      {/* Sub-tab bar */}
      <div style={{ display: 'flex', gap: 4, marginBottom: 12, borderBottom: '1px solid var(--line)' }}>
        {TRIGGER_KINDS.map(k => (
          <button key={k.key}
            onClick={() => { setKind(k.key); setEditing(null); }}
            className={kind === k.key ? 'btn btn-pri' : 'btn btn-ghost'}
            style={{ fontSize: 12, padding: '6px 12px', borderRadius: '6px 6px 0 0' }}>
            {(window.appT||(s=>s))(k.label)}
          </button>
        ))}
      </div>

      <div className="muted" style={{ fontSize: 12, marginBottom: 10 }}>
        {(window.appT||(s=>s))(TRIGGER_KINDS.find(k => k.key === kind)?.hint || '')}
      </div>

      <div style={{ display: 'flex', gap: 8, marginBottom: 12 }}>
        <button className="btn btn-pri" onClick={() => setEditing(newDraft())}>{(window.appT||(s=>s))('+ New trigger')}</button>
        <button className="btn btn-ghost" onClick={load} disabled={loading}>
          {loading ? (window.appT||(s=>s))('Loading…') : (window.appT||(s=>s))('Refresh')}
        </button>
      </div>

      {/* Tester */}
      <div className="card" style={{ padding: 10, marginBottom: 12 }}>
        <div style={{ fontWeight: 600, fontSize: 12, marginBottom: 4 }}>{(window.appT||(s=>s))('Test against text')}</div>
        <div style={{ display: 'flex', gap: 8 }}>
          <input className="inp" value={testText} onChange={e => setTT(e.target.value)}
            placeholder={(window.appT||(s=>s))('Type a message and see which triggers fire…')}
            style={{ flex: 1, height: 30, fontSize: 12 }} />
          <button className="btn btn-ghost" onClick={runTest}>{(window.appT||(s=>s))('Test')}</button>
        </div>
        {testResult && (
          <div style={{ marginTop: 8, fontSize: 11 }}>
            {testResult.matches.length === 0
              ? <span className="muted">{(window.appT||(s=>s))('No triggers matched.')}</span>
              : <span style={{ color: 'var(--ok)' }}>{(window.appT||(s=>s))('Matched:')} {testResult.matches.map(m => m.name).join(', ')}</span>}
          </div>
        )}
      </div>

      {/* Items list */}
      <table className="t" style={{ fontSize: 12 }}>
        <thead>
          <tr><th>{(window.appT||(s=>s))('Name')}</th><th>{(window.appT||(s=>s))('On')}</th><th>{(window.appT||(s=>s))('Prio')}</th><th>{(window.appT||(s=>s))('Match')}</th><th>{(window.appT||(s=>s))('Action')}</th><th>{(window.appT||(s=>s))('Fires')}</th><th></th></tr>
        </thead>
        <tbody>
          {items.length === 0 && !loading && (
            <tr><td colSpan={7} className="muted" style={{ padding: 16, textAlign: 'center' }}>
              {(window.appT||(s=>s))('No')} {kind.replace('_',' ')} {(window.appT||(s=>s))('triggers yet. Click')} <strong>{(window.appT||(s=>s))('+ New trigger')}</strong>.
            </td></tr>
          )}
          {items.map(it => (
            <tr key={it.id}>
              <td style={{ fontWeight: 600 }}>{it.name}</td>
              <td>
                <input type="checkbox" checked={!!it.enabled} onChange={() => toggle(it)} />
              </td>
              <td className="mono">{it.priority}</td>
              <td className="mono muted" style={{ fontSize: 11, maxWidth: 240, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                {JSON.stringify(it.match)}
              </td>
              <td className="mono muted" style={{ fontSize: 11, maxWidth: 240, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                {JSON.stringify(it.action)}
              </td>
              <td className="mono" style={{ fontSize: 11 }}>{it.fire_count || 0}</td>
              <td>
                <button className="btn btn-ghost" style={{ padding: '1px 6px', fontSize: 11 }}
                  onClick={() => setEditing({ ...it })}>{(window.appT||(s=>s))('Edit')}</button>
                <button className="btn btn-ghost" style={{ padding: '1px 6px', fontSize: 11, color: 'var(--err)' }}
                  onClick={() => remove(it.id)}>{(window.appT||(s=>s))('Delete')}</button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>

      {/* Edit modal-ish panel */}
      {editing && (
        <div className="card" style={{ marginTop: 16, padding: 14, borderColor: 'var(--brand, var(--accent))' }}>
          <div style={{ fontWeight: 700, fontSize: 14, marginBottom: 10 }}>
            {editing.id ? (window.appT||(s=>s))('Edit') : (window.appT||(s=>s))('New')} {kind.replace('_', ' ')} {(window.appT||(s=>s))('trigger')}
          </div>

          <div style={{ display: 'grid', gridTemplateColumns: '120px 1fr', gap: 8, alignItems: 'center', fontSize: 12 }}>
            <label>{(window.appT||(s=>s))('Name')}</label>
            <input className="inp" value={editing.name}
              onChange={e => setEditing({ ...editing, name: e.target.value })} />

            <label>{(window.appT||(s=>s))('Priority')}</label>
            <input className="inp" type="number" value={editing.priority}
              onChange={e => setEditing({ ...editing, priority: Number(e.target.value) || 100 })}
              style={{ width: 100 }} />

            <label>{(window.appT||(s=>s))('Enabled')}</label>
            <input type="checkbox" checked={!!editing.enabled}
              onChange={e => setEditing({ ...editing, enabled: e.target.checked })} />

            {(kind === 'keyword_reply' || kind === 'routing') && (
              <>
                <label>{(window.appT||(s=>s))('Keywords')}</label>
                <input className="inp" placeholder={(window.appT||(s=>s))('comma,separated,keywords')}
                  value={(editing.match.keywords || []).join(',')}
                  onChange={e => setEditing({ ...editing, match: { ...editing.match,
                    keywords: e.target.value.split(',').map(s => s.trim()).filter(Boolean) }})} />

                <label>{(window.appT||(s=>s))('Match mode')}</label>
                <select className="sel" value={editing.match.match || 'any'}
                  onChange={e => setEditing({ ...editing, match: { ...editing.match, match: e.target.value }})}>
                  <option value="any">{(window.appT||(s=>s))('Any keyword')}</option>
                  <option value="all">{(window.appT||(s=>s))('All keywords')}</option>
                  <option value="exact">{(window.appT||(s=>s))('Exact match')}</option>
                </select>

                <label>{(window.appT||(s=>s))('Case-sensitive')}</label>
                <input type="checkbox" checked={!!editing.match.case_sensitive}
                  onChange={e => setEditing({ ...editing, match: { ...editing.match, case_sensitive: e.target.checked }})} />

                <label>{(window.appT||(s=>s))('Scope')}</label>
                <select className="sel" value={editing.match.scope || 'both'}
                  onChange={e => setEditing({ ...editing, match: { ...editing.match, scope: e.target.value }})}>
                  <option value="both">{(window.appT||(s=>s))('DM + room')}</option>
                  <option value="dm">{(window.appT||(s=>s))('DM only')}</option>
                  <option value="room">{(window.appT||(s=>s))('Room only')}</option>
                </select>
              </>
            )}

            {kind === 'command' && (
              <>
                <label>{(window.appT||(s=>s))('Command')}</label>
                <input className="inp" placeholder="!example"
                  value={editing.match.command || ''}
                  onChange={e => setEditing({ ...editing, match: { ...editing.match, command: e.target.value }})} />
              </>
            )}

            {kind === 'rule' && (
              <>
                <label>{(window.appT||(s=>s))('Event')}</label>
                <select className="sel" value={editing.match.event || 'strike'}
                  onChange={e => setEditing({ ...editing, match: { ...editing.match, event: e.target.value }})}>
                  <option value="strike">{(window.appT||(s=>s))('Format strike')}</option>
                  <option value="late">{(window.appT||(s=>s))('Late sign-in')}</option>
                  <option value="absent">{(window.appT||(s=>s))('Absent')}</option>
                  <option value="blocker">{(window.appT||(s=>s))('Blocker reported')}</option>
                </select>
                <label>{(window.appT||(s=>s))('Threshold')}</label>
                <input className="inp" type="number" value={editing.match.threshold || 1}
                  onChange={e => setEditing({ ...editing, match: { ...editing.match, threshold: Number(e.target.value) || 1 }})} />
                <label>{(window.appT||(s=>s))('Window (days)')}</label>
                <input className="inp" type="number" value={editing.match.window_days || 30}
                  onChange={e => setEditing({ ...editing, match: { ...editing.match, window_days: Number(e.target.value) || 30 }})} />
                <div className="muted" style={{ gridColumn: '1 / -1', fontSize: 11, marginTop: 4 }}>
                  {(window.appT||(s=>s))("Note: rule firing is configured here but evaluated by the scheduler's event hooks — not from inbound text.")}
                </div>
              </>
            )}

            {(kind === 'keyword_reply' || kind === 'command') && (
              <>
                <label>{(window.appT||(s=>s))('Reply text')}</label>
                <textarea className="inp" rows={3} value={editing.action.reply || ''}
                  onChange={e => setEditing({ ...editing, action: { ...editing.action, reply: e.target.value }})}
                  style={{ resize: 'vertical' }} />
              </>
            )}

            {kind === 'routing' && (
              <>
                <label>{(window.appT||(s=>s))('Route to')}</label>
                <input className="inp" placeholder="@user:server or !room:server"
                  value={editing.action.route_to || ''}
                  onChange={e => setEditing({ ...editing, action: { ...editing.action, route_to: e.target.value }})} />
                <label>{(window.appT||(s=>s))('Ack to sender')}</label>
                <input className="inp" placeholder={(window.appT||(s=>s))('Optional reply back to the original sender')}
                  value={editing.action.ack || ''}
                  onChange={e => setEditing({ ...editing, action: { ...editing.action, ack: e.target.value }})} />
              </>
            )}

            {kind === 'rule' && (
              <>
                <label>{(window.appT||(s=>s))('DM admins')}</label>
                <input type="checkbox" checked={!!editing.action.dm_admins}
                  onChange={e => setEditing({ ...editing, action: { ...editing.action, dm_admins: e.target.checked }})} />
                <label>{(window.appT||(s=>s))('Post to room')}</label>
                <input className="inp" placeholder={(window.appT||(s=>s))('Optional !room:server')}
                  value={editing.action.post_room || ''}
                  onChange={e => setEditing({ ...editing, action: { ...editing.action, post_room: e.target.value }})} />
              </>
            )}
          </div>

          <div style={{ display: 'flex', gap: 8, marginTop: 14 }}>
            <button className="btn btn-pri" onClick={save}>{(window.appT||(s=>s))('Save')}</button>
            <button className="btn btn-ghost" onClick={() => setEditing(null)}>{(window.appT||(s=>s))('Cancel')}</button>
          </div>
        </div>
      )}
    </div>
  );
}

/* ============================================================
   AutomationsPanel — admin on/off + run-now for every bot job
   ============================================================ */
// Per-user recipient editor for the summaries automation. Lets the admin pick
// All employees / Poor performers / Good standing / specific users.
function SummaryRecipientsEditor() {
  const { toast } = _useToast();
  const [mode, setMode]   = React.useState('all');
  const [ids, setIds]     = React.useState([]);
  const [avail, setAvail] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [saving, setSaving]   = React.useState(false);

  React.useEffect(() => {
    fetch('/api/automations/summaries/recipients', { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : null)
      .then(d => {
        if (!d) return;
        setMode(d.mode || 'all');
        setIds(d.user_ids || []);
        setAvail(d.available || []);
      })
      .catch(() => {})
      .finally(() => setLoading(false));
  }, []);

  const toggleId = (id) => setIds(prev =>
    prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id]);
  const selectAll = () => setIds(avail.map(u => u.id));
  const clearAll  = () => setIds([]);

  const save = async () => {
    setSaving(true);
    try {
      const body = { mode, user_ids: mode === 'selected' ? ids : [] };
      const r = await fetch('/api/automations/summaries/recipients', {
        method: 'PUT', credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
      });
      const d = await r.json();
      if (!r.ok || d.ok === false) throw new Error(d.error || r.status);
      toast({ kind: 'ok', message: (window.appT||(s=>s))('Summary recipients saved') });
    } catch (e) { toast({ kind: 'err', message: (window.appT||(s=>s))('Save failed:') + ' ' + e.message }); }
    finally { setSaving(false); }
  };

  if (loading) return <div className="muted" style={{ fontSize: 11, padding: 8 }}>{(window.appT||(s=>s))('Loading recipients…')}</div>;

  const T = (window.appT||(s=>s));
  const opts = [
    ['all',      T('All employees'), T('Everyone active — new hires auto-included.')],
    ['poor',     T('Poor performers only'), T('Low attendance, no progress, or strikes this period.')],
    ['good',     T('Good standing only'), T('Everyone not flagged as a poor performer.')],
    ['selected', T('Specific users'), T('Only the employees you check below.')],
  ];

  return (
    <div style={{ padding: '10px 4px', display: 'flex', flexDirection: 'column', gap: 10 }}>
      <div style={{ fontWeight: 600, fontSize: 12 }}>{(window.appT||(s=>s))('Who receives the per-user summary report?')}</div>
      <div style={{ display: 'flex', flexWrap: 'wrap', gap: 14 }}>
        {opts.map(([val, label, hint]) => (
          <label key={val} title={hint}
            style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12, cursor: 'pointer' }}>
            <input type="radio" name="rcpt-mode" checked={mode === val}
              onChange={() => setMode(val)} />
            {label}
          </label>
        ))}
      </div>
      {mode === 'selected' && (
        <div style={{ border: '1px solid var(--border)', borderRadius: 8, padding: 10 }}>
          <div style={{ display: 'flex', gap: 8, marginBottom: 8 }}>
            <button className="btn btn-ghost" style={{ fontSize: 11, padding: '2px 8px' }} onClick={selectAll}>{(window.appT||(s=>s))('Select all')}</button>
            <button className="btn btn-ghost" style={{ fontSize: 11, padding: '2px 8px' }} onClick={clearAll}>{(window.appT||(s=>s))('Clear')}</button>
            <span className="muted" style={{ fontSize: 11, alignSelf: 'center' }}>{ids.length} {(window.appT||(s=>s))('of')} {avail.length} {(window.appT||(s=>s))('selected')}</span>
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))', gap: 6 }}>
            {avail.map(u => (
              <label key={u.id} style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 12, cursor: 'pointer' }}>
                <input type="checkbox" checked={ids.includes(u.id)} onChange={() => toggleId(u.id)} />
                {u.name}
              </label>
            ))}
          </div>
        </div>
      )}
      <div>
        <button className="btn btn-primary" style={{ fontSize: 12 }} onClick={save} disabled={saving}>
          {saving ? (window.appT||(s=>s))('Saving…') : (window.appT||(s=>s))('Save recipients')}
        </button>
      </div>
    </div>
  );
}

function AutomationsPanel() {
  const { toast } = _useToast();
  const [items, setItems]   = React.useState([]);
  const [loading, setLoading] = React.useState(false);
  const [busy, setBusy]     = React.useState(null);
  const [lastRun, setLastRun] = React.useState({});   // key -> result string
  const [showRcpt, setShowRcpt] = React.useState(false);

  const load = React.useCallback(() => {
    setLoading(true);
    fetch('/api/automations', { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : { items: [] })
      .then(d => setItems(d.items || []))
      .catch(() => {})
      .finally(() => setLoading(false));
  }, []);
  React.useEffect(() => { load(); }, [load]);

  // Optimistic toggle: flip local state immediately, persist, revert on failure.
  const toggle = async (key, enabled) => {
    setItems(prev => prev.map(a => a.key === key ? { ...a, enabled } : a));
    setBusy(key);
    try {
      const r = await fetch(`/api/automations/${key}`, {
        method: 'PATCH', credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ enabled }),
      });
      if (!r.ok) throw new Error(`${r.status}`);
      toast({ kind: 'ok', message: `${key} ${enabled ? (window.appT||(s=>s))('enabled') : (window.appT||(s=>s))('disabled')}` });
    } catch (e) {
      setItems(prev => prev.map(a => a.key === key ? { ...a, enabled: !enabled } : a));
      toast({ kind: 'err', message: (window.appT||(s=>s))('Toggle failed:') + ' ' + e.message });
    }
    finally { setBusy(null); }
  };

  const runNow = async (key) => {
    setBusy(key);
    setLastRun(prev => ({ ...prev, [key]: (window.appT||(s=>s))('running…') }));
    try {
      const r = await fetch(`/api/automations/${key}/run-now`, {
        method: 'POST', credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({}),
      });
      const d = await r.json();
      if (!r.ok || d.ok === false) throw new Error(d.error || r.status);
      const when = new Date().toLocaleTimeString();
      const detail = (d.sent != null)
        ? `${(window.appT||(s=>s))('sent')} ${d.sent}, ${(window.appT||(s=>s))('skipped')} ${d.skipped || 0}${d.admin_rollup_sent != null ? `, ${(window.appT||(s=>s))('admin')} ${d.admin_rollup_sent}` : ''}`
        : (window.appT||(s=>s))('done');
      setLastRun(prev => ({ ...prev, [key]: `✓ ${detail} · ${when}` }));
      toast({ kind: 'ok', message: `${(window.appT||(s=>s))('Ran now —')} ${detail}` });
      await load();
    } catch (e) {
      setLastRun(prev => ({ ...prev, [key]: '✗ ' + (window.appT||(s=>s))('failed:') + ' ' + e.message }));
      toast({ kind: 'err', message: (window.appT||(s=>s))('Run failed:') + ' ' + e.message });
    }
    finally { setBusy(null); }
  };

  return (
    <div style={{ marginTop: 16 }}>
      <div className="muted" style={{ fontSize: 12.5, marginBottom: 12 }}>
        {(window.appT||(s=>s))('Every background automation the bot runs.')} <strong>{(window.appT||(s=>s))('Toggle ON')}</strong> {(window.appT||(s=>s))('and it runs on its own schedule continuously;')} <strong>{(window.appT||(s=>s))('toggle OFF')}</strong> {(window.appT||(s=>s))('and it stops. “Run now” triggers a one-off immediately (useful for testing).')}
      </div>
      <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 12 }}>
        <button className="btn btn-ghost" onClick={load} disabled={loading}>
          {loading ? (window.appT||(s=>s))('Loading…') : (window.appT||(s=>s))('Refresh')}
        </button>
      </div>
      <table className="t" style={{ fontSize: 12.5, width: '100%' }}>
        <thead><tr>
          <th>{(window.appT||(s=>s))('Automation')}</th><th>{(window.appT||(s=>s))('Enabled')}</th><th>{(window.appT||(s=>s))('Next run')}</th><th></th>
        </tr></thead>
        <tbody>
          {items.length === 0 && !loading && (
            <tr><td colSpan={4} className="muted" style={{ padding: 16, textAlign: 'center' }}>{(window.appT||(s=>s))('No automations.')}</td></tr>
          )}
          {items.map(a => (
            <React.Fragment key={a.key}>
            <tr>
              <td>
                <div style={{ fontWeight: 600 }}>{(window.appT||(s=>s))(a.label)}</div>
                <div className="muted" style={{ fontSize: 11 }}>{(window.appT||(s=>s))(a.desc)}</div>
                {lastRun[a.key] && (
                  <div style={{ fontSize: 11, marginTop: 3,
                    color: lastRun[a.key].startsWith('✗') ? 'var(--err)' : 'var(--ok)' }}>
                    {lastRun[a.key]}
                  </div>
                )}
              </td>
              <td>
                <label style={{ display: 'flex', alignItems: 'center', gap: 6, cursor: busy === a.key ? 'wait' : 'pointer' }}>
                  <input type="checkbox" checked={!!a.enabled} disabled={busy === a.key}
                    onChange={e => toggle(a.key, e.target.checked)} />
                  <span style={{ fontSize: 11, color: a.enabled ? 'var(--ok)' : 'var(--text-3)' }}>
                    {a.enabled ? (window.appT||(s=>s))('ON') : (window.appT||(s=>s))('OFF')}
                  </span>
                </label>
              </td>
              <td className="mono muted" style={{ fontSize: 11 }}>
                {a.next_run_at ? new Date(a.next_run_at).toLocaleString() : '—'}
              </td>
              <td style={{ whiteSpace: 'nowrap' }}>
                {a.key === 'summaries' && (
                  <button className="btn btn-ghost" style={{ padding: '2px 10px', fontSize: 11, marginRight: 6 }}
                    onClick={() => setShowRcpt(v => !v)}>
                    {showRcpt ? (window.appT||(s=>s))('Hide recipients') : (window.appT||(s=>s))('Recipients')}
                  </button>
                )}
                <button className="btn btn-ghost" style={{ padding: '2px 10px', fontSize: 11 }}
                  disabled={busy === a.key} onClick={() => runNow(a.key)}>
                  {busy === a.key ? '…' : (window.appT||(s=>s))('Run now')}
                </button>
              </td>
            </tr>
            {a.key === 'summaries' && showRcpt && (
              <tr>
                <td colSpan={4} style={{ background: 'var(--surface-2, rgba(255,255,255,0.02))' }}>
                  <SummaryRecipientsEditor />
                </td>
              </tr>
            )}
            </React.Fragment>
          ))}
        </tbody>
      </table>
    </div>
  );
}

/* ============================================================
   TroubleshootPanel — on-demand fixes + per-room bot health so the
   admin can self-diagnose/repair without asking an operator.
   ============================================================ */
function TroubleshootPanel() {
  const { toast } = _useToast();
  const T = (window.appT||(s=>s));
  const [health, setHealth] = React.useState(null);
  const [loadingH, setLoadingH] = React.useState(false);
  const [busy, setBusy] = React.useState(null);
  const [results, setResults] = React.useState({});  // action key -> message

  const loadHealth = React.useCallback(() => {
    setLoadingH(true);
    fetch('/api/admin/troubleshoot/room-health', { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : null)
      .then(d => setHealth(d))
      .catch(() => {})
      .finally(() => setLoadingH(false));
  }, []);
  React.useEffect(() => { loadHealth(); }, [loadHealth]);

  // Inline room edit for troubleshooting: change a room's TYPE (which controls
  // what the bot listens for) or rename it, right from the health table.
  const [savingRoom, setSavingRoom] = React.useState(null);
  const saveRoom = async (id, patch, okMsg) => {
    setSavingRoom(id);
    try {
      const r = await fetch(`/api/rooms/${id}`, {
        method: 'PUT', credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(patch),
      });
      const d = await r.json().catch(() => ({}));
      if (!r.ok || d.ok === false) throw new Error(d.detail || d.error || r.status);
      toast({ kind: 'ok', message: okMsg || T('Room updated') });
      await loadHealth();
    } catch (e) {
      toast({ kind: 'err', message: T('Update failed:') + ' ' + e.message });
    } finally { setSavingRoom(null); }
  };
  const renameRoom = (r) => {
    const next = window.prompt(T('Rename room') + ':', r.name || '');
    if (next == null) return;
    const v = next.trim();
    if (!v || v === r.name) return;
    saveRoom(r.id, { display_name: v }, T('Renamed'));
  };
  const ROOM_TYPES = ['standup', 'general', 'pto', 'support', 'technical', 'admin', 'announcements', 'chat'];

  const runAction = async (key, url, describe) => {
    setBusy(key);
    setResults(prev => ({ ...prev, [key]: T('running…') }));
    try {
      const r = await fetch(url, {
        method: 'POST', credentials: 'same-origin',
        headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}),
      });
      const d = await r.json();
      if (!r.ok || d.ok === false) throw new Error(d.detail || d.error || r.status);
      const msg = describe(d);
      const when = new Date().toLocaleTimeString();
      setResults(prev => ({ ...prev, [key]: `✓ ${msg} · ${when}` }));
      toast({ kind: 'ok', message: msg });
      loadHealth();
    } catch (e) {
      setResults(prev => ({ ...prev, [key]: '✗ ' + T('failed:') + ' ' + e.message }));
      toast({ kind: 'err', message: T('Action failed:') + ' ' + e.message });
    } finally { setBusy(null); }
  };

  const actions = [
    {
      key: 'recompute',
      title: T('Recompute today’s attendance'),
      desc: T('Rebuild hours, late flags and sign-off status from the live sign-in/out events. Use when the dashboard numbers look stale or wrong.'),
      url: '/api/admin/troubleshoot/recompute-today',
      btn: T('Recompute now'),
      describe: d => `${T('Recomputed')} ${d.rows} — ${d.signed_in} ${T('in')}, ${d.signed_off} ${T('off')}, ${d.total_hours}h`,
    },
  ];

  const fmtAgo = (iso) => {
    if (!iso) return '—';
    try {
      const mins = Math.round((Date.now() - new Date(iso).getTime()) / 60000);
      if (mins < 1) return T('just now');
      if (mins < 60) return mins + 'm ' + T('ago');
      const h = Math.round(mins / 60);
      if (h < 24) return h + 'h ' + T('ago');
      return Math.round(h / 24) + 'd ' + T('ago');
    } catch (_) { return '—'; }
  };

  return (
    <div style={{ marginTop: 16 }}>
      <div className="muted" style={{ fontSize: 12.5, marginBottom: 14 }}>
        {T('On-demand fixes you can run yourself. Each one is the same logic the bot automates — exposed here so you can repair data or check the bot without asking an operator.')}
        {' '}{T('Automation on/off + “Run now” for every job lives in the')} <strong>{T('Automations')}</strong> {T('tab.')}
      </div>

      {/* On-demand action cards */}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(280px, 1fr))', gap: 12, marginBottom: 20 }}>
        {actions.map(a => (
          <div key={a.key} className="card" style={{ padding: 14 }}>
            <div style={{ fontWeight: 700, fontSize: 13.5 }}>{a.title}</div>
            <div className="muted" style={{ fontSize: 12, margin: '6px 0 12px' }}>{a.desc}</div>
            <button className="btn btn-primary" style={{ fontSize: 12 }}
              disabled={busy === a.key}
              onClick={() => runAction(a.key, a.url, a.describe)}>
              {busy === a.key ? T('Working…') : a.btn}
            </button>
            {results[a.key] && (
              <div style={{ fontSize: 11.5, marginTop: 8,
                color: results[a.key].startsWith('✗') ? 'var(--err)' : 'var(--ok)' }}>
                {results[a.key]}
              </div>
            )}
          </div>
        ))}
      </div>

      {/* Per-room bot health */}
      <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}>
        <div style={{ fontWeight: 700, fontSize: 14 }}>{T('Per-room bot health')}</div>
        {health && (
          <span style={{ fontSize: 11.5, color: health.is_ready ? 'var(--ok)' : 'var(--err)' }}>
            {health.is_ready ? T('● bot online') : T('● bot not ready')}
          </span>
        )}
        {health && health.unhealthy > 0 && (
          <span style={{ fontSize: 11.5, color: 'var(--err)' }}>· {health.unhealthy} {T('room(s) need attention')}</span>
        )}
        <button className="btn btn-ghost" style={{ marginLeft: 'auto', fontSize: 11.5 }}
          onClick={loadHealth} disabled={loadingH}>
          {loadingH ? T('Loading…') : T('Refresh')}
        </button>
      </div>
      <div className="muted" style={{ fontSize: 11.5, marginBottom: 8 }}>
        {T('“Listening” = the bot processes messages here (sign-ins, PTO, or Q&A). “Joined” = the bot account is actually a member. Attendance rooms need both ✓; announcement/admin rooms only need Joined.')}
        {' '}{T('You can change a room’s TYPE or rename it inline — changing the type changes what the bot listens for (standup/pto/general/support/admin all listen).')}
      </div>
      <table className="t" style={{ fontSize: 12.5, width: '100%' }}>
        <thead><tr>
          <th>{T('Room')}</th><th>{T('Type (editable)')}</th><th>{T('Listening')}</th><th>{T('Joined')}</th>
          <th>{T('Msgs today')}</th><th>{T('Last human')}</th><th></th>
        </tr></thead>
        <tbody>
          {(!health || !health.rooms) && (
            <tr><td colSpan={7} className="muted" style={{ padding: 16, textAlign: 'center' }}>
              {loadingH ? T('Loading…') : T('No room data.')}</td></tr>
          )}
          {health && health.rooms && health.rooms.map(r => (
            <tr key={r.id} style={!r.healthy ? { background: 'rgba(255,80,80,0.06)' } : null}>
              <td style={{ fontWeight: 600 }}>{r.name}{r.team ? <span className="muted" style={{ fontWeight: 400 }}> · {r.team}</span> : null}</td>
              <td>
                <select className="sel" value={r.type || ''} disabled={savingRoom === r.id}
                  style={{ fontSize: 11, padding: '1px 4px' }}
                  onChange={e => saveRoom(r.id, { room_type: e.target.value }, T('Type changed to') + ' ' + e.target.value)}>
                  {(ROOM_TYPES.includes(r.type) ? ROOM_TYPES : [r.type, ...ROOM_TYPES]).map(t =>
                    <option key={t} value={t}>{t || '—'}</option>)}
                </select>
              </td>
              <td style={{ color: r.monitored ? 'var(--ok)' : 'var(--err)', fontWeight: 700 }}>{r.monitored ? '✓' : '✗'}</td>
              <td style={{ color: r.joined ? 'var(--ok)' : 'var(--err)', fontWeight: 700 }}>{r.joined ? '✓' : '✗'}</td>
              <td className="mono">{r.messages_today}</td>
              <td className="mono muted" style={{ fontSize: 11 }}>{fmtAgo(r.last_human_at)}</td>
              <td style={{ whiteSpace: 'nowrap' }}>
                <button className="btn btn-ghost" style={{ padding: '1px 8px', fontSize: 11 }}
                  disabled={savingRoom === r.id} onClick={() => renameRoom(r)}>
                  {savingRoom === r.id ? '…' : T('Rename')}
                </button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

window.AutomationsPanel = AutomationsPanel;
window.TriggersPanel = TriggersPanel;
window.TroubleshootPanel = TroubleshootPanel;
window.BotBehaviorPage = BotBehaviorPage;
