// AI Asistent — chat + conversation history + admin tabs
// Single-page React component wired to /api/ai/*.

const AI_SECTIONS = [
  { id: 'chat',       label: 'Chat',            ico: <Ico.ai/>,        section: 'work'  },
  { id: 'history',    label: 'História',        ico: <Ico.dashboard/>, section: 'work'  },
  { id: 'memories',   label: 'Pamäť',           ico: <Ico.sparkles/>,  section: 'work'  },
  { id: 'training',   label: 'Znalosti',        ico: <Ico.datasheet/>, section: 'work'  },
  { id: 'approvals',  label: 'Na schválenie',   ico: <Ico.shield/>,    section: 'admin' },
  { id: 'schedules',  label: 'Plánované úlohy', ico: <Ico.construction/>, section: 'admin' },
  { id: 'skills',     label: 'Skills',          ico: <Ico.check/>,     section: 'admin' },
  { id: 'functions',  label: 'Funkcie',         ico: <Ico.modules/>,   section: 'admin' },
  { id: 'stats',      label: 'Štatistiky',      ico: <Ico.grid/>,      section: 'admin' },
  { id: 'settings',   label: 'Nastavenia',      ico: <Ico.settings/>,  section: 'admin' },
];

// Shared API helpers
const AI = {
  async _j(url, opts) {
    const r = await fetch(url, { credentials: 'same-origin', headers: { 'Content-Type': 'application/json' }, ...opts });
    const j = await r.json().catch(() => ({}));
    return { ok: r.ok, status: r.status, body: j };
  },
  get(url)             { return this._j(url); },
  post(url, body)      { return this._j(url, { method: 'POST', body: JSON.stringify(body || {}) }); },
  patch(url, body)     { return this._j(url, { method: 'PATCH', body: JSON.stringify(body || {}) }); },
  del(url)             { return this._j(url, { method: 'DELETE' }); },
};

// ────────────── Attachment helpers (share between AiChat + FAB + SkillBuilder) ──────────────
const AI_IMAGE_TYPES    = new Set(['image/jpeg','image/png','image/webp','image/gif']);
const AI_TEXT_TYPES_RE  = /^(text\/(plain|csv|markdown|html)|application\/(json|xml))/;
const AI_PDF_TYPE       = 'application/pdf';
const AI_MAX_FILE_BYTES = 10 * 1024 * 1024; // 10 MB
const AI_MAX_FILES      = 6;

function readFileAsBase64(file) {
  return new Promise((resolve, reject) => {
    const r = new FileReader();
    r.onload = () => resolve(String(r.result).split(',')[1] || '');
    r.onerror = () => reject(new Error('read_failed'));
    r.readAsDataURL(file);
  });
}
function readFileAsText(file) {
  return new Promise((resolve, reject) => {
    const r = new FileReader();
    r.onload = () => resolve(String(r.result || ''));
    r.onerror = () => reject(new Error('read_failed'));
    r.readAsText(file);
  });
}

// Builds Anthropic-compatible content blocks from a list of browser File objects.
// Returns { blocks, uiFiles } — blocks go to the API, uiFiles are for previewing in our own message bubble.
async function filesToAnthropicBlocks(files) {
  const blocks = [];
  const uiFiles = [];
  for (const f of files) {
    if (f.size > AI_MAX_FILE_BYTES) throw new Error(`Súbor ${f.name} je väčší ako 10 MB.`);
    if (AI_IMAGE_TYPES.has(f.type)) {
      const data = await readFileAsBase64(f);
      blocks.push({ type: 'image', source: { type: 'base64', media_type: f.type, data } });
      uiFiles.push({ name: f.name, type: f.type, size: f.size, data_url: `data:${f.type};base64,${data}` });
    } else if (f.type === AI_PDF_TYPE) {
      const data = await readFileAsBase64(f);
      blocks.push({ type: 'document', source: { type: 'base64', media_type: 'application/pdf', data }, title: f.name });
      uiFiles.push({ name: f.name, type: f.type, size: f.size, data_url: null });
    } else if (AI_TEXT_TYPES_RE.test(f.type) || /\.(txt|csv|md|json|xml|log)$/i.test(f.name)) {
      const txt = await readFileAsText(f);
      blocks.push({ type: 'text', text: `[Súbor ${f.name}]\n${txt.slice(0, 100_000)}` });
      uiFiles.push({ name: f.name, type: f.type || 'text/plain', size: f.size, data_url: null });
    } else {
      throw new Error(`Typ ${f.type || f.name.split('.').pop()} nie je podporovaný.`);
    }
  }
  return { blocks, uiFiles };
}

// Given raw user text + optional file blocks, builds final `content` for the Anthropic message.
function buildUserContent(text, fileBlocks) {
  if (!fileBlocks || fileBlocks.length === 0) return text;
  const arr = [...fileBlocks];
  if (text && text.trim()) arr.push({ type: 'text', text });
  return arr;
}

// Pretty size
function fmtBytes(n) {
  if (n < 1024) return n + ' B';
  if (n < 1024 * 1024) return (n / 1024).toFixed(0) + ' kB';
  return (n / 1024 / 1024).toFixed(1) + ' MB';
}

function AiAssistant() {
  const [section, setSection] = React.useState('chat');
  const [toast, setToast] = React.useState(null);
  const [pendingCount, setPendingCount] = React.useState(0);
  const canEdit = window.hasPerm ? window.hasPerm('ai.edit') : false;
  const notify = (kind, text) => setToast({ kind, text });

  // Poll approval queue count so the Admin sees incoming requests in near real-time.
  React.useEffect(() => {
    if (!canEdit) return;
    const tick = () => AI.get('/api/ai/approvals/count').then(r => r.ok && setPendingCount(r.body.count || 0));
    tick();
    const id = setInterval(tick, 30_000);
    return () => clearInterval(id);
  }, [canEdit]);
  // Refresh count when leaving the approvals tab (might have just approved/rejected)
  React.useEffect(() => {
    if (section === 'approvals' && canEdit) {
      AI.get('/api/ai/approvals/count').then(r => r.ok && setPendingCount(r.body.count || 0));
    }
  }, [section, canEdit]);

  return (
    <AppShell active="ai" crumbs={['AI Asistent', AI_SECTIONS.find(s => s.id === section)?.label || '']}>
      {toast && <Toast kind={toast.kind} onClose={() => setToast(null)}>{toast.text}</Toast>}

      <div className="ai-layout">
        {/* Sub-sidebar */}
        <div className="card ai-subnav">
          <nav className="ai-subnav-list">
            <div className="ai-subnav-section">Práca</div>
            {AI_SECTIONS.filter(s => s.section === 'work').map(s => (
              <a key={s.id}
                 onClick={() => setSection(s.id)}
                 className={`ai-subnav-item${section === s.id ? ' is-active' : ''}`}>
                {s.ico}<span>{s.label}</span>
              </a>
            ))}
            {canEdit && <>
              <div className="ai-subnav-section">Administrácia</div>
              {AI_SECTIONS.filter(s => s.section === 'admin').map(s => (
                <a key={s.id}
                   onClick={() => setSection(s.id)}
                   className={`ai-subnav-item${section === s.id ? ' is-active' : ''}`}>
                  {s.ico}<span>{s.label}</span>
                  {s.id === 'approvals' && pendingCount > 0 && (
                    <span className="ai-subnav-badge">{pendingCount}</span>
                  )}
                </a>
              ))}
            </>}
          </nav>
        </div>

        {/* Content */}
        <div className="ai-content">
          {section === 'chat'      && <AiChat notify={notify}/>}
          {section === 'history'   && <AiHistory notify={notify} onOpen={(id) => { sessionStorage.setItem('ai_open_convo', id); setSection('chat'); }}/>}
          {section === 'memories'  && <AiMemories notify={notify}/>}
          {section === 'approvals' && <AiApprovals notify={notify} onReviewed={() => AI.get('/api/ai/approvals/count').then(r => r.ok && setPendingCount(r.body.count || 0))}/>}
          {section === 'schedules' && <AiSchedules notify={notify}/>}
          {section === 'skills'    && <AiSkills notify={notify}/>}
          {section === 'functions' && <AiFunctions notify={notify}/>}
          {section === 'training'  && <AiTraining notify={notify}/>}
          {section === 'stats'     && <AiStats/>}
          {section === 'settings'  && <AiSettings notify={notify}/>}
        </div>
      </div>
    </AppShell>
  );
}

// ═════════ CHAT ═════════════════════════════════════════════════════════════
function AiChat({ notify }) {
  const [messages, setMessages] = React.useState([]);
  const [input, setInput]     = React.useState('');
  const [files, setFiles]     = React.useState([]);          // pending attachments
  const [dragging, setDragging] = React.useState(false);
  const [sending, setSending] = React.useState(false);
  const [sendingStartedAt, setSendingStartedAt] = React.useState(null);
  const [model, setModel]     = React.useState('auto');
  const [lastInfo, setLastInfo] = React.useState(null);
  const [convoId, setConvoId] = React.useState(null);
  const [models, setModels]   = React.useState([]);
  const [quickBtns, setQuickBtns] = React.useState([]);
  const [context, setContext] = React.useState(null); // { skills, tools_count, integrations }
  const scrollRef = React.useRef(null);

  React.useEffect(() => {
    AI.get('/api/ai/models').then(r => r.ok && setModels(r.body.models || []));
    AI.get('/api/ai/quick-buttons').then(r => r.ok && setQuickBtns(r.body.buttons || []));
    AI.get('/api/ai/context').then(r => r.ok && setContext(r.body));
    // Load conversation if requested (from History)
    const open = sessionStorage.getItem('ai_open_convo');
    if (open) {
      sessionStorage.removeItem('ai_open_convo');
      AI.get(`/api/ai/conversations/${open}`).then(r => {
        if (r.ok) { setMessages(r.body.conversation.messages || []); setConvoId(r.body.conversation.id); setModel(r.body.conversation.model || 'auto'); }
      });
    }
  }, []);
  React.useEffect(() => {
    if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
  }, [messages, sending]);

  async function send(text) {
    const t = (text ?? input).trim();
    const hasFiles = files.length > 0;
    if ((!t && !hasFiles) || sending) return;

    // Convert attached files → Anthropic blocks
    let blocks = [], uiFiles = [];
    if (hasFiles) {
      try { ({ blocks, uiFiles } = await filesToAnthropicBlocks(files)); }
      catch (e) { notify && notify('error', e.message); return; }
    }
    const userMsg = {
      role: 'user',
      content: buildUserContent(t, blocks),
      _ui_files: uiFiles,  // for our own bubble rendering
    };
    const next = [...messages, userMsg];
    setMessages(next); setInput(''); setFiles([]); setSending(true); setSendingStartedAt(Date.now());

    // Strip _ui_files before POST (server doesn't need it)
    const wire = next.map(m => ({ role: m.role, content: m.content }));
    const r = await AI.post('/api/ai/chat', { messages: wire, model, conversation_id: convoId });
    setSending(false); setSendingStartedAt(null);
    if (r.ok && r.body.ok) {
      const b = r.body;
      setMessages([...next, {
        role: 'assistant', content: b.text,
        _meta: {
          model: b.model, auto_picked: b.auto_picked,
          tools: b.tools_used || [], iterations: b.iterations,
          skills_loaded: b.skills_loaded || [],
          navigate_to: b.navigate_to || null,
          input_tokens: b.usage?.input_tokens, output_tokens: b.usage?.output_tokens,
          cache_tokens: b.usage?.cache_tokens, cost_usd: b.usage?.cost_usd,
          duration_ms: b.duration_ms,
        },
      }]);
      setConvoId(b.conversation_id);
      // If the AI triggered a navigation tool, redirect after a short delay.
      if (b.navigate_to && b.navigate_to.href) {
        setTimeout(() => { window.location.hash = b.navigate_to.href.replace(/^#/, ''); }, 900);
      }
      setLastInfo({
        model: b.model, auto_picked: b.auto_picked,
        tools: b.tools_used, iter: b.iterations,
        skills: b.skills_loaded?.length || 0,
        in: b.usage?.input_tokens, out: b.usage?.output_tokens,
        cache: b.usage?.cache_tokens, cost: b.usage?.cost_usd,
        duration: b.duration_ms,
      });
    } else {
      const msg = r.body.message || r.body.error || 'Chyba pri volaní AI.';
      setMessages([...next, { role: 'assistant', content: `⚠️ ${msg}`, _error: true }]);
    }
  }

  function newChat() { setMessages([]); setConvoId(null); setLastInfo(null); }

  const suggestions = quickBtns.length ? quickBtns : [
    { id:'s1', label: 'Zhrň mi dnešné objednávky',              prompt: 'Zhrň mi dnešné objednávky cez woo_get_orders a daj krátky prehľad stavov.' },
    { id:'s2', label: 'Vypredané produkty',                      prompt: 'Ktoré produkty sú aktuálne vypredané? Použi woo_get_products.' },
    { id:'s3', label: 'Tento týždeň — faktúry SF',               prompt: 'Koľko faktúr bolo vystavených tento mesiac? Použi sf_get_invoices.' },
    { id:'s4', label: 'Aktuálny výkon FV inštalácií',            prompt: 'Zisti aktuálny stav mojich FV inštalácií cez goodwe_list_plants a upozorni na alarmy.' },
    { id:'s5', label: 'Zaplánuj denný prehľad o 8:00',           prompt: 'Vytvor plánovanú úlohu ktorá mi každé ráno o 8:00 pošle prehľad — počet nových objednávok, alarmy z GoodWe a TOP 5 produktov s nízkou zásobou.' },
    { id:'s6', label: 'Zapamätaj si: IČO firmy 12345678',        prompt: 'Zapamätaj si, že IČO našej firmy je 12345678 a pri vytváraní faktúr ho používaj.' },
  ];

  return (
    <div className="card ai-chat">
      <div className="card-head ai-chat-head">
        <h3>{convoId ? `Konverzácia #${convoId}` : 'Nová konverzácia'}</h3>
        <div className="ai-chat-head-actions">
          <select className="input ai-model-select" value={model} onChange={e => setModel(e.target.value)}>
            {models.map(m => <option key={m.id} value={m.id}>{m.label}</option>)}
          </select>
          <button className="btn btn-sm" onClick={newChat}><Ico.plus/>Nový</button>
        </div>
      </div>

      {/* Context strip — what's available in this chat */}
      {context && (
        <div className="ai-context-strip">
          <span className="ai-chip ai-chip-model" title={model === 'auto' ? 'Auto — vyberie sa podľa otázky' : model}>
            <Ico.sparkles/>{model === 'auto' ? 'Auto' : (modelChip(model)?.short || model)}
          </span>
          {context.skills.length > 0 ? (
            <span className="ai-chip ai-chip-skill" title={context.skills.map(s => `• ${s.name}${s.description ? ' — ' + s.description : ''}`).join('\n')}>
              ✦ {context.skills.length === 1 ? context.skills[0].name : `${context.skills.length} skillov`}
            </span>
          ) : (
            <span className="ai-chip" style={{opacity:0.6}}>✦ bez skillov</span>
          )}
          <span className="ai-chip ai-chip-tool" title="Dostupné nástroje (tool-use)">🔧 {context.tools_count} nástrojov</span>
          <span className="ai-chip" title={'Integrácie:\n' + Object.entries(context.integrations).map(([k, v]) => `${v ? '✓' : '✗'} ${k}`).join('\n')}>
            {Object.values(context.integrations).filter(Boolean).length}/{Object.keys(context.integrations).length} integrácií
          </span>
          {convoId && <span className="ai-chip ai-chip-convo-id">#{convoId}</span>}
        </div>
      )}

      <div ref={scrollRef} className="ai-chat-scroll">
        {messages.length === 0 ? (
          <div className="ai-chat-empty">
            <div className="ai-chat-empty-badge">
              <Ico.sparkles style={{width:28, height:28}}/>
            </div>
            <h2 style={{margin:0}}>Ako vám dnes pomôžem?</h2>
            <p className="muted" style={{margin:'6px 0 20px'}}>
              Asistent má prístup k objednávkam, produktom, zákazníkom, faktúram (SF), úlohám (ClickUp), FV monitoringu (GoodWe) a pamäti.
            </p>
            <div className="ai-suggestions-grid">
              {suggestions.map(s => (
                <button key={s.id} className="card ai-suggestion" onClick={() => send(s.prompt)}>
                  <div className="ai-suggestion-label">{s.label}</div>
                  <div className="ai-suggestion-prompt small muted">{s.prompt}</div>
                </button>
              ))}
            </div>
          </div>
        ) : (
          <div className="ai-chat-messages">
            {messages.map((m, i) => <AiMessage key={i} m={m}/>)}
            {sending && (() => {
              // Pick a progress checklist based on the last user message — so admin
              // sees real-looking phases instead of a generic timer.
              const lastUser = [...messages].reverse().find(m => m.role === 'user');
              const lastText = typeof lastUser?.content === 'string'
                ? lastUser.content
                : Array.isArray(lastUser?.content)
                  ? lastUser.content.filter(b => b.type === 'text').map(b => b.text).join(' ')
                  : '';
              const steps = (window.pickTypingSteps ? window.pickTypingSteps(lastText) : null);
              return (
                <div className="hstack" style={{alignItems:'flex-start', gap:12}}>
                  <div className="ai-avatar ai-avatar-bot"><Ico.sparkles/></div>
                  <TypingIndicator label="Asistent pracuje…" startedAt={sendingStartedAt} steps={steps} rotateMs={3000}/>
                </div>
              );
            })()}
          </div>
        )}
      </div>

      <div className="ai-chat-input-wrap">
        {lastInfo && (
          <div className="ai-meta ai-chat-last-meta">
            {(() => { const mc = modelChip(lastInfo.model); return mc && <span className="ai-chip ai-chip-model"><Ico.sparkles/>{mc.short}{lastInfo.auto_picked && <span className="ai-chip-tag">auto</span>}</span>; })()}
            {lastInfo.skills > 0 && <span className="ai-chip ai-chip-skill">✦ {lastInfo.skills} skillov</span>}
            {lastInfo.tools?.length > 0 && <span className="ai-chip ai-chip-tool" title={lastInfo.tools.join(', ')}>🔧 {lastInfo.tools.length} nástrojov</span>}
            {lastInfo.iter > 1 && <span className="ai-chip">↻ {lastInfo.iter} krokov</span>}
            <span className="ai-chip ai-chip-tok">{((lastInfo.in||0)+(lastInfo.out||0)).toLocaleString('sk-SK')} tok{lastInfo.cache > 0 && <span className="ai-chip-tag">💾 {lastInfo.cache.toLocaleString('sk-SK')}</span>}</span>
            {lastInfo.duration && <span className="ai-chip">⏱ {(lastInfo.duration/1000).toFixed(2)} s</span>}
            {lastInfo.cost > 0 && <span className="ai-chip">💵 ${lastInfo.cost.toFixed(4)}</span>}
          </div>
        )}
        <div
          className={`ai-chat-input${dragging ? ' is-dragging' : ''}`}
          onDragOver={e => { e.preventDefault(); setDragging(true); }}
          onDragLeave={e => { e.preventDefault(); setDragging(false); }}
          onDrop={e => {
            e.preventDefault(); setDragging(false);
            const dropped = Array.from(e.dataTransfer?.files || []);
            if (dropped.length) setFiles(prev => [...prev, ...dropped].slice(0, AI_MAX_FILES));
          }}
        >
          {files.length > 0 && (
            <AttachmentPreviewRow files={files} onRemove={i => setFiles(prev => prev.filter((_, j) => j !== i))}/>
          )}
          <textarea
            className="ai-chat-textarea"
            placeholder={dragging ? 'Pustite súbor(y) sem…' : 'Napíšte správu… (Enter = odoslať, Shift+Enter = nový riadok)'}
            value={input}
            onChange={e => setInput(e.target.value)}
            onKeyDown={e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } }}
            onPaste={e => {
              const pasted = Array.from(e.clipboardData?.files || []);
              if (pasted.length) { e.preventDefault(); setFiles(prev => [...prev, ...pasted].slice(0, AI_MAX_FILES)); }
            }}
            disabled={sending}
          />
          <div className="hstack ai-chat-input-bar">
            <label className="ai-attach-btn" title="Pripojiť súbor (obrázok, PDF, text)">
              <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 17.99 8.84l-8.59 8.57a2 2 0 1 1-2.83-2.83l8.49-8.49"/></svg>
              <input type="file" multiple hidden
                accept="image/jpeg,image/png,image/webp,image/gif,application/pdf,text/plain,text/csv,text/markdown,application/json"
                onChange={e => {
                  const picked = Array.from(e.target.files || []);
                  if (picked.length) setFiles(prev => [...prev, ...picked].slice(0, AI_MAX_FILES));
                  e.target.value = '';
                }}/>
            </label>
            <span className="small muted" style={{marginLeft:6}}>Model: <b>{model === 'auto' ? 'Auto' : model}</b></span>
            <button className="btn btn-primary btn-sm ai-send-btn" onClick={() => send()} disabled={sending || (!input.trim() && files.length === 0)}>
              <Ico.plus style={{transform:'rotate(45deg)'}}/> <span>Odoslať</span>
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

// Render Markdown → sanitized HTML using marked + DOMPurify (both loaded via CDN).
// Falls back to plain-text rendering if either library isn't available yet.
function renderMarkdown(src) {
  const s = typeof src === 'string' ? src : JSON.stringify(src);
  if (!window.marked || !window.DOMPurify) return null;
  try {
    window.marked.setOptions({ breaks: true, gfm: true });
    const html = window.marked.parse(s || '');
    return window.DOMPurify.sanitize(html, { ADD_ATTR: ['target'] });
  } catch {
    return null;
  }
}

// ────────────── Attachment UI (preview chips + in-message gallery) ──────────────
function AttachmentPreviewRow({ files, onRemove }) {
  return (
    <div className="ai-attach-row">
      {files.map((f, i) => {
        const isImg = AI_IMAGE_TYPES.has(f.type);
        const url = isImg ? URL.createObjectURL(f) : null;
        return (
          <div key={i} className="ai-attach-chip" title={`${f.name} · ${fmtBytes(f.size)}`}>
            {isImg
              ? <img src={url} alt="" className="ai-attach-chip-thumb"/>
              : <span className="ai-attach-chip-icon">{f.type === 'application/pdf' ? '📄' : '📝'}</span>}
            <span className="ai-attach-chip-name">{f.name}</span>
            <button type="button" className="ai-attach-chip-x" onClick={() => onRemove(i)} aria-label="Odstrániť">✕</button>
          </div>
        );
      })}
    </div>
  );
}

function InlineAttachments({ files }) {
  if (!files || files.length === 0) return null;
  return (
    <div className="ai-msg-files">
      {files.map((f, i) => (
        f.data_url && f.type?.startsWith('image/') ? (
          <a key={i} href={f.data_url} target="_blank" rel="noreferrer" className="ai-msg-file ai-msg-file-img" title={f.name}>
            <img src={f.data_url} alt={f.name}/>
          </a>
        ) : (
          <div key={i} className="ai-msg-file ai-msg-file-doc">
            <span className="ai-msg-file-icon">{f.type === 'application/pdf' ? '📄' : '📝'}</span>
            <div style={{minWidth:0, flex:1}}>
              <div className="ai-msg-file-name">{f.name}</div>
              <div className="small muted">{fmtBytes(f.size)}</div>
            </div>
          </div>
        )
      ))}
    </div>
  );
}

// Short friendly model label. Anthropic model IDs are verbose → show a compact tier name + full id on hover.
function modelChip(full) {
  if (!full) return null;
  let short = 'Claude';
  if (/haiku/i.test(full))  short = 'Haiku';
  else if (/sonnet/i.test(full)) short = 'Sonnet';
  else if (/opus/i.test(full))  short = 'Opus';
  return { short, full };
}

// Extract plain-text portion from either a string or an Anthropic content array.
function plainTextOfContent(c) {
  if (typeof c === 'string') return c;
  if (Array.isArray(c)) return c.filter(b => b.type === 'text').map(b => b.text).join('\n');
  return '';
}

function AiMessage({ m }) {
  const isUser = m.role === 'user';
  const text = plainTextOfContent(m.content);
  const html = !isUser && !m._error ? renderMarkdown(text) : null;
  const meta = m._meta || {};
  const mc = !isUser ? modelChip(meta.model) : null;
  const hasBubble = !!text || (isUser && !m._ui_files?.length);

  return (
    <div className="hstack" style={{alignItems:'flex-start', gap:12, marginBottom:14}}>
      <div className={`ai-avatar ${isUser ? 'ai-avatar-user' : 'ai-avatar-bot'}`}>
        {isUser ? 'JA' : <Ico.sparkles/>}
      </div>
      <div style={{flex:1, minWidth:0}}>
        <div className="small muted" style={{marginBottom:3}}>
          {isUser ? 'Vy' : 'Asistent'}
          {isUser && m._ui_files?.length > 0 && <span className="small muted"> · {m._ui_files.length} príloh</span>}
        </div>
        {isUser && <InlineAttachments files={m._ui_files}/>}
        {hasBubble && (
        <div className={isUser ? 'ai-bubble ai-bubble-user' : (m._error ? 'ai-bubble ai-bubble-err' : 'ai-bubble ai-md')}>
          {html
            ? <div dangerouslySetInnerHTML={{ __html: html }}/>
            : <div style={{whiteSpace:'pre-wrap'}}>{text}</div>}
        </div>
        )}

        {/* Navigation action from AI-invoked erp_open_* tool */}
        {!isUser && meta.navigate_to && (
          <a href={meta.navigate_to.href} className="ai-nav-action">
            <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
              <path d="M7 17 17 7"/><path d="M7 7h10v10"/>
            </svg>
            {meta.navigate_to.label || 'Otvoriť'}
          </a>
        )}

        {/* Rich meta strip — only for assistant messages */}
        {!isUser && (meta.model || meta.tools?.length || meta.skills_loaded?.length || meta.input_tokens) && (
          <div className="ai-meta">
            {mc && (
              <span className="ai-chip ai-chip-model" title={`Model: ${mc.full}${meta.auto_picked ? ' (vybratý automaticky)' : ''}`}>
                <Ico.sparkles/>{mc.short}
                {meta.auto_picked && <span className="ai-chip-tag">auto</span>}
              </span>
            )}
            {meta.skills_loaded?.length > 0 && (
              <span className="ai-chip ai-chip-skill" title={meta.skills_loaded.map(s => s.name).join(', ')}>
                ✦ {meta.skills_loaded.length === 1 ? meta.skills_loaded[0].name : `${meta.skills_loaded.length} skillov`}
              </span>
            )}
            {meta.tools?.length > 0 && (
              <span className="ai-chip ai-chip-tool" title={meta.tools.join(', ')}>
                🔧 {meta.tools.length === 1 ? meta.tools[0] : `${meta.tools.length}× nástroj`}
              </span>
            )}
            {meta.iterations > 1 && (
              <span className="ai-chip" title="Počet iterácií v tool-use loope">↻ {meta.iterations} krokov</span>
            )}
            {(meta.input_tokens || meta.output_tokens) != null && (
              <span className="ai-chip ai-chip-tok" title={`Vstup ${meta.input_tokens} + výstup ${meta.output_tokens}${meta.cache_tokens ? ` + cache ${meta.cache_tokens}` : ''}`}>
                {((meta.input_tokens || 0) + (meta.output_tokens || 0)).toLocaleString('sk-SK')} tok
                {meta.cache_tokens > 0 && <span className="ai-chip-tag">💾 {meta.cache_tokens.toLocaleString('sk-SK')}</span>}
              </span>
            )}
            {meta.duration_ms && (
              <span className="ai-chip" title="Čas odpovede">⏱ {(meta.duration_ms / 1000).toFixed(2)} s</span>
            )}
            {meta.cost_usd != null && meta.cost_usd > 0 && (
              <span className="ai-chip" title="Odhad ceny (USD)">💵 ${meta.cost_usd.toFixed(4)}</span>
            )}
          </div>
        )}

        {/* Tool call details (expandable) */}
        {!isUser && meta.tools?.length > 0 && (
          <details className="ai-tools-trace">
            <summary className="small muted">Zoznam volaných nástrojov</summary>
            <ol style={{margin:'6px 0 0 20px', fontSize:11.5}}>
              {meta.tools.map((t, i) => <li key={i}><code>{t}</code></li>)}
            </ol>
          </details>
        )}
      </div>
    </div>
  );
}

// ═════════ HISTORY ══════════════════════════════════════════════════════════
function AiHistory({ notify, onOpen }) {
  const [list, setList] = React.useState([]);
  const [loading, setLoading] = React.useState(true);

  async function reload() {
    setLoading(true);
    const r = await AI.get('/api/ai/conversations');
    setLoading(false);
    if (r.ok) setList(r.body.conversations || []);
  }
  React.useEffect(() => { reload(); }, []);

  async function remove(id) {
    if (!confirm('Zmazať konverzáciu?')) return;
    const r = await AI.del('/api/ai/conversations/' + id);
    if (r.ok) { reload(); notify('success', 'Zmazané.'); }
    else notify('error', 'Chyba.');
  }

  return (
    <div className="card">
      <div className="card-head">
        <h3>História konverzácií ({list.length})</h3>
        <div style={{marginLeft:'auto'}}><button className="btn btn-sm" onClick={reload}><Ico.refresh/>Obnoviť</button></div>
      </div>
      {loading ? <div className="muted" style={{padding:20}}>Načítavam…</div> :
       list.length === 0 ? <div className="muted" style={{padding:30, textAlign:'center'}}>Zatiaľ žiadne konverzácie.</div> :
       <div className="ai-table-wrap"><table className="tbl">
         <thead><tr><th>ID</th><th>Názov</th><th>Model</th><th>Upravené</th><th></th></tr></thead>
         <tbody>
           {list.map(c => (
             <tr key={c.id}>
               <td className="mono small">#{c.id}</td>
               <td><a style={{color:'var(--navy-700)', fontWeight:500, cursor:'pointer'}} onClick={() => onOpen(c.id)}>{c.title || '(bez názvu)'}</a></td>
               <td className="mono small">{c.model || '—'}</td>
               <td className="small muted">{new Date(c.updated_at * 1000).toLocaleString('sk-SK')}</td>
               <td style={{textAlign:'right'}}><button className="btn btn-xs btn-danger" onClick={() => remove(c.id)}>Zmazať</button></td>
             </tr>
           ))}
         </tbody>
       </table></div>}
    </div>
  );
}

// ═════════ MEMORIES ═════════════════════════════════════════════════════════
function AiMemories({ notify }) {
  const [list, setList] = React.useState([]);
  const [content, setContent] = React.useState('');
  const [tags, setTags] = React.useState('');
  const isAdmin = (window.currentSession?.user?.role === 'admin');

  async function reload() {
    const r = await AI.get('/api/ai/memories');
    if (r.ok) setList(r.body.memories || []);
  }
  React.useEffect(() => { reload(); }, []);

  async function add() {
    if (!content.trim()) return;
    const r = await AI.post('/api/ai/memories', { content: content.trim(), tags: tags.trim() || null });
    if (r.ok) {
      setContent(''); setTags(''); reload();
      notify('success', r.body.status === 'pending'
        ? 'Odoslané administrátorovi na schválenie.'
        : 'Uložené do globálnej pamäte.');
    }
  }
  async function remove(id) {
    const r = await AI.del('/api/ai/memories/' + id);
    if (r.ok) reload();
  }

  const statusChip = (m) => {
    if (!m.status || m.status === 'approved') return <span className="chip green dot">Schválené</span>;
    if (m.status === 'pending')                return <span className="chip amber dot">Čaká na schválenie</span>;
    return <span className="chip red dot">Zamietnuté</span>;
  };

  return (
    <div className="vstack" style={{gap:14}}>
      <div className="card">
        <div className="card-head"><h3>Pridať spomienku {!isAdmin && <span className="chip amber" style={{padding:'1px 8px'}}>pôjde na schválenie</span>}</h3></div>
        <div className="card-body vstack" style={{gap:10, padding:14}}>
          {!isAdmin && (
            <div className="small muted" style={{background:'var(--amber-100)', padding:'8px 10px', borderRadius:6, border:'1px solid rgba(243,167,18,0.2)'}}>
              ℹ️ Keďže nie ste administrátor, pridanie do globálnej pamäte musí schváliť admin. Ostatní používatelia ho uvidia až po schválení.
            </div>
          )}
          <div className="field">
            <label>Čo si má asistent zapamätať</label>
            <textarea className="input" rows={3} value={content} onChange={e => setContent(e.target.value)} placeholder="Napr. „Náš IČO je 12345678, pri faktúrach používaj štandardnú splatnosť 14 dní."/>
          </div>
          <div className="field">
            <label>Tagy (voliteľné, oddelené čiarkou)</label>
            <input className="input" value={tags} onChange={e => setTags(e.target.value)} placeholder="napr. firma, fakturácia"/>
          </div>
          <button className="btn btn-primary btn-sm" style={{alignSelf:'flex-start'}} onClick={add} disabled={!content.trim()}>
            <Ico.plus/>{isAdmin ? 'Pridať do pamäte' : 'Odoslať na schválenie'}
          </button>
        </div>
      </div>

      <div className="card">
        <div className="card-head"><h3>Uložené spomienky ({list.length})</h3></div>
        {list.length === 0 ? <div className="muted" style={{padding:20, textAlign:'center'}}>Pamäť je prázdna.</div> :
         <div className="ai-table-wrap"><table className="tbl">
           <thead><tr><th>Obsah</th><th>Stav</th><th>Pridané</th><th></th></tr></thead>
           <tbody>
             {list.map(m => (
               <tr key={m.id} style={m.status === 'pending' ? {background:'rgba(246,191,71,0.08)'} : m.status === 'rejected' ? {opacity:0.5} : {}}>
                 <td>
                   <div>{m.content}</div>
                   {m.tags && <div className="small muted" style={{marginTop:3}}>{m.tags.split(',').map((t, i) => <span key={i} className="chip blue" style={{marginRight:4, padding:'0 6px'}}>{t.trim()}</span>)}</div>}
                 </td>
                 <td>{statusChip(m)}</td>
                 <td className="small muted" style={{whiteSpace:'nowrap'}}>{new Date(m.created_at * 1000).toLocaleDateString('sk-SK')}</td>
                 <td style={{textAlign:'right'}}>{(isAdmin || m.requested_by === window.currentSession?.user?.id) && <button className="btn btn-xs btn-danger" onClick={() => remove(m.id)}>Zmazať</button>}</td>
               </tr>
             ))}
           </tbody>
         </table></div>}
      </div>
    </div>
  );
}

// ═════════ APPROVALS (admin only) ═══════════════════════════════════════════
function AiApprovals({ notify, onReviewed }) {
  const [list, setList] = React.useState([]);
  const [loading, setLoading] = React.useState(true);
  const [busyId, setBusyId] = React.useState(null);

  async function reload() {
    setLoading(true);
    const r = await AI.get('/api/ai/approvals');
    setLoading(false);
    if (r.ok) setList(r.body.items || []);
  }
  React.useEffect(() => { reload(); }, []);

  async function act(item, action) {
    const key = `${item.kind}-${item.id}`;
    setBusyId(key);
    const r = await AI.post(`/api/ai/approvals/${item.kind}/${item.id}/${action}`);
    setBusyId(null);
    if (r.ok) {
      notify('success', action === 'approve' ? 'Schválené.' : 'Zamietnuté.');
      reload();
      onReviewed && onReviewed();
    } else notify('error', r.body.error || 'Chyba.');
  }

  return (
    <div className="card">
      <div className="card-head">
        <h3>Na schválenie {list.length > 0 && <span className="chip amber" style={{padding:'1px 8px'}}>{list.length}</span>}</h3>
        <div style={{marginLeft:'auto'}}>
          <button className="btn btn-sm" onClick={reload}><Ico.refresh/>Obnoviť</button>
        </div>
      </div>

      {loading ? <div className="muted" style={{padding:30, textAlign:'center'}}>Načítavam…</div> :
       list.length === 0 ? (
         <div style={{padding:40, textAlign:'center'}}>
           <div style={{fontSize:32, opacity:0.4}}>✓</div>
           <div className="muted" style={{marginTop:6}}>Žiadne požiadavky na schválenie.</div>
           <div className="small muted" style={{marginTop:4}}>Keď používateľ bez admin roly pridá spomienku alebo znalosť, objaví sa tu.</div>
         </div>
       ) : (
         <div className="vstack" style={{gap:0}}>
           {list.map(item => {
             const key = `${item.kind}-${item.id}`;
             const busy = busyId === key;
             return (
               <div key={key} className="ai-approval-item">
                 <div className="ai-approval-head">
                   <span className={`chip ${item.kind === 'memory' ? 'blue' : 'green'} ai-approval-kind`}>
                     {item.kind === 'memory' ? '✦ Pamäť' : '📖 Znalosť'}
                   </span>
                   <div className="small muted ai-approval-meta">
                     {item.requested_by_name ? <>od <b>{item.requested_by_name}</b> ({item.requested_by_email})</> : <>od neznámeho používateľa</>}
                     {' · '}{new Date(item.created_at * 1000).toLocaleString('sk-SK')}
                   </div>
                   <div className="ai-approval-actions">
                     <button className="btn btn-xs btn-danger" onClick={() => act(item, 'reject')} disabled={busy}><Ico.x/>Zamietnuť</button>
                     <button className="btn btn-xs ai-btn-approve" onClick={() => act(item, 'approve')} disabled={busy}>
                       <Ico.check/>{busy ? 'Pracujem…' : 'Schváliť'}
                     </button>
                   </div>
                 </div>
                 {item.kind === 'training' && <div style={{fontWeight:600, marginBottom:4}}>{item.title}</div>}
                 <div className="ai-approval-content">{item.content}</div>
                 {item.tags && <div className="small muted" style={{marginTop:4}}>Tagy: {item.tags}</div>}
               </div>
             );
           })}
         </div>
       )}
    </div>
  );
}

// ═════════ SCHEDULES ════════════════════════════════════════════════════════
function AiSchedules({ notify }) {
  const [list, setList] = React.useState([]);
  const [edit, setEdit] = React.useState(null); // null | 'new' | row

  async function reload() {
    const r = await AI.get('/api/ai/schedules');
    if (r.ok) setList(r.body.schedules || []);
  }
  React.useEffect(() => { reload(); }, []);

  async function save(s) {
    const r = s.id ? await AI.patch('/api/ai/schedules/' + s.id, s) : await AI.post('/api/ai/schedules', s);
    if (r.ok) { setEdit(null); reload(); notify('success', 'Uložené.'); }
    else notify('error', r.body.error || 'Chyba.');
  }
  async function toggle(s) { await AI.patch('/api/ai/schedules/' + s.id, { active: !s.active }); reload(); }
  async function remove(s) { if (!confirm('Zmazať úlohu?')) return; await AI.del('/api/ai/schedules/' + s.id); reload(); }
  async function runNow(s) {
    notify('info', 'Spúšťam…');
    const r = await AI.post('/api/ai/schedules/' + s.id + '/run');
    reload();
    notify(r.body.ok ? 'success' : 'error', r.body.ok ? 'Úloha úspešne vykonaná.' : 'Chyba: ' + (r.body.result || r.body.error));
  }

  return (
    <div className="card">
      <div className="card-head">
        <h3>Plánované úlohy ({list.length})</h3>
        <div style={{marginLeft:'auto'}} className="hstack">
          <button className="btn btn-sm" onClick={reload}><Ico.refresh/>Obnoviť</button>
          <button className="btn btn-primary btn-sm" onClick={() => setEdit({})}><Ico.plus/>Nová úloha</button>
        </div>
      </div>

      {edit && <ScheduleEditor schedule={edit} onClose={() => setEdit(null)} onSave={save}/>}

      {list.length === 0 ? <div className="muted" style={{padding:30, textAlign:'center'}}>Žiadne úlohy. Vytvorte prvú.</div> :
       <div className="ai-table-wrap"><table className="tbl">
         <thead><tr><th>Názov</th><th>Cron</th><th>Typ</th><th>Posledný beh</th><th>Stav</th><th></th></tr></thead>
         <tbody>
           {list.map(s => (
             <tr key={s.id}>
               <td><b>{s.name}</b><div className="small muted">{s.prompt?.slice(0,60) || '—'}</div></td>
               <td className="mono small">{s.cron_expr}</td>
               <td><span className="chip gray" style={{padding:'0 6px'}}>{s.action_type}</span></td>
               <td className="small muted">
                 {s.last_run ? new Date(s.last_run * 1000).toLocaleString('sk-SK') : '—'}
                 {s.last_success != null && ' ' + (s.last_success ? '✓' : '✗')}
               </td>
               <td><span className={`chip ${s.active ? 'green' : 'gray'} dot`}>{s.active ? 'Aktívna' : 'Vypnutá'}</span></td>
               <td style={{textAlign:'right', whiteSpace:'nowrap'}}>
                 <button className="btn btn-xs" onClick={() => runNow(s)}>Spustiť</button>{' '}
                 <button className="btn btn-xs" onClick={() => toggle(s)}>{s.active ? 'Vyp.' : 'Zap.'}</button>{' '}
                 <button className="btn btn-xs" onClick={() => setEdit(s)}>Upraviť</button>{' '}
                 <button className="btn btn-xs btn-danger" onClick={() => remove(s)}>Zmazať</button>
               </td>
             </tr>
           ))}
         </tbody>
       </table></div>}
    </div>
  );
}

function ScheduleEditor({ schedule, onClose, onSave }) {
  const [s, setS] = React.useState({
    name: '', cron_expr: '0 8 * * *', action_type: 'ai_message', prompt: '',
    active: true, email_to: '', email_subject: '', email_body: '',
    ai_generate_body: false, send_result_by_email: false,
    ...schedule,
  });
  const F = (k) => ({ value: s[k] ?? '', onChange: e => setS(p => ({ ...p, [k]: e.target.value })) });
  const C = (k) => ({ checked: !!s[k], onChange: e => setS(p => ({ ...p, [k]: e.target.checked })) });

  return (
    <Modal title={s.id ? 'Upraviť úlohu' : 'Nová plánovaná úloha'} width={640} onClose={onClose}
           footer={<>
             <button className="btn" onClick={onClose}>Zavrieť</button>
             <button className="btn btn-primary btn-sm" onClick={() => onSave(s)} disabled={!s.name || !s.cron_expr}>Uložiť</button>
           </>}>
      <div className="vstack" style={{gap:10}}>
        <div className="field"><label>Názov</label><input className="input" {...F('name')} placeholder="Ranný prehľad"/></div>
        <div className="ai-form-row">
          <div className="field" style={{flex:1, minWidth:0}}>
            <label>Cron výraz</label>
            <input className="input mono" {...F('cron_expr')}/>
            <div className="small muted">Napr. <code>0 8 * * *</code> = denne o 8:00 · <code>*/15 * * * *</code> = každých 15 min · <code>0 9 * * 1</code> = pondelok 9:00</div>
          </div>
          <div className="field ai-form-col-narrow">
            <label>Typ akcie</label>
            <select className="input" {...F('action_type')}>
              <option value="ai_message">AI správa</option>
              <option value="send_email">Odoslať e-mail</option>
              <option value="notification">Notifikácia</option>
            </select>
          </div>
        </div>
        <div className="field">
          <label>Prompt pre AI (čo má spraviť)</label>
          <textarea className="input" rows={4} {...F('prompt')} placeholder="Daj mi prehľad dnešných objednávok a alarmov z GoodWe…"/>
        </div>
        {s.action_type === 'send_email' && <>
          <div className="ai-form-row">
            <div className="field" style={{flex:1, minWidth:0}}><label>Komu</label><input className="input" {...F('email_to')} placeholder="admin@firma.sk"/></div>
            <div className="field" style={{flex:1, minWidth:0}}><label>Predmet</label><input className="input" {...F('email_subject')}/></div>
          </div>
          <label className="checkbox"><input type="checkbox" {...C('ai_generate_body')}/><span>Telo emailu vygeneruje AI z promptu</span></label>
          {!s.ai_generate_body && (
            <div className="field"><label>Text emailu</label><textarea className="input" rows={4} {...F('email_body')}/></div>
          )}
        </>}
        <label className="checkbox"><input type="checkbox" {...C('send_result_by_email')}/><span>Pošli výsledok e-mailom po vykonaní</span></label>
        <label className="checkbox"><input type="checkbox" {...C('active')}/><span>Aktívna</span></label>
      </div>
    </Modal>
  );
}

// ═════════ SKILLS ═══════════════════════════════════════════════════════════
function AiSkills({ notify }) {
  const [list, setList] = React.useState([]);
  const [edit, setEdit] = React.useState(null);   // null | skill row (has content loaded)
  const [builder, setBuilder] = React.useState(null); // null | { editingSkill? }
  const [uploading, setUploading] = React.useState(false);

  async function reload() {
    const r = await AI.get('/api/ai/skills');
    if (r.ok) setList(r.body.skills || []);
  }
  React.useEffect(() => { reload(); }, []);

  async function openEdit(s) {
    if (!s.id) { setEdit({}); return; }
    const r = await AI.get('/api/ai/skills/' + s.id);
    if (r.ok) setEdit(r.body.skill);
    else notify('error', 'Nepodarilo sa načítať skill.');
  }

  async function save(s) {
    const body = {
      name: s.name, description: s.description, prompt: s.prompt, content: s.content,
      active: !!s.active, restricted: !!s.restricted,
    };
    const r = s.id ? await AI.patch('/api/ai/skills/' + s.id, body) : await AI.post('/api/ai/skills', body);
    if (r.ok) { setEdit(null); reload(); notify('success', 'Skill uložený.'); }
    else notify('error', r.body.error || 'Chyba.');
  }
  async function remove(s) { if (!confirm(`Zmazať skill "${s.name}"?`)) return; await AI.del('/api/ai/skills/' + s.id); reload(); }

  async function upload(file) {
    if (!file) return;
    setUploading(true);
    const fd = new FormData(); fd.append('file', file);
    const r = await fetch('/api/ai/skills/upload', { method: 'POST', credentials: 'same-origin', body: fd });
    const j = await r.json().catch(() => ({}));
    setUploading(false);
    if (r.ok && j.ok) { notify('success', `Skill "${j.name}" ${j.action === 'updated' ? 'aktualizovaný' : 'nahraný'}.`); reload(); }
    else notify('error', j.message || j.error || 'Upload zlyhal.');
  }

  return (
    <>
      <div className="card">
        <div className="card-head">
          <h3>Skills ({list.length})</h3>
          <div style={{marginLeft:'auto'}} className="hstack">
            <button className="btn btn-sm" onClick={reload}><Ico.refresh/>Obnoviť</button>
            <label className="btn btn-sm" style={{cursor:'pointer'}}>
              <Ico.upload/>{uploading ? 'Nahrávam…' : 'Nahrať .skill'}
              <input type="file" accept=".skill,.md,.txt,text/plain,text/markdown" hidden onChange={e => upload(e.target.files?.[0])}/>
            </label>
            <button className="btn btn-sm" onClick={() => setBuilder({})}><Ico.sparkles/>Skill Builder</button>
            <button className="btn btn-primary btn-sm" onClick={() => openEdit({})}><Ico.plus/>Nový ručne</button>
          </div>
        </div>

        {list.length === 0 ? <div className="muted" style={{padding:30, textAlign:'center'}}>Žiadne skilly. Nahrajte .skill súbor alebo spustite Skill Builder.</div> :
         <div className="ai-table-wrap"><table className="tbl">
           <thead><tr><th>Názov</th><th>Popis</th><th>Obsah</th><th>Aktívny</th><th>Obmedzenie</th><th></th></tr></thead>
           <tbody>
             {list.map(s => (
               <tr key={s.id}>
                 <td className="mono small">{s.name}</td>
                 <td style={{maxWidth:420, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap'}} title={s.description || ''}>{s.description || '—'}</td>
                 <td>{s.has_content ? <span className="chip green" style={{padding:'0 6px'}}>SKILL.md</span> : <span className="chip gray" style={{padding:'0 6px'}}>len prompt</span>}</td>
                 <td>{s.active ? '✓' : '—'}</td>
                 <td>{s.restricted ? <span className="chip amber" style={{padding:'0 6px'}}>per-user</span> : <span className="small muted">všetci</span>}</td>
                 <td style={{textAlign:'right', whiteSpace:'nowrap'}}>
                   <button className="btn btn-xs" onClick={() => setBuilder({ editingSkillId: s.id })}><Ico.sparkles/>Upraviť s AI</button>{' '}
                   <button className="btn btn-xs" onClick={() => openEdit(s)}>Upraviť</button>{' '}
                   <button className="btn btn-xs btn-danger" onClick={() => remove(s)}>Zmazať</button>
                 </td>
               </tr>
             ))}
           </tbody>
         </table></div>}
      </div>

      {edit && <SkillEditorModal item={edit} onClose={() => setEdit(null)} onSave={save}/>}
      {builder && <SkillBuilderModal editingSkillId={builder.editingSkillId} onClose={() => setBuilder(null)} onSaved={() => { setBuilder(null); reload(); notify('success','Skill uložený.'); }}/>}
    </>
  );
}

function SkillEditorModal({ item, onClose, onSave }) {
  const [s, setS] = React.useState({
    name: item.name || '', description: item.description || '', prompt: item.prompt || '',
    content: item.content || '', active: item.active !== undefined ? !!item.active : true,
    restricted: !!item.restricted, ...(item.id ? { id: item.id } : {}),
  });
  const F = (k) => ({ value: s[k] ?? '', onChange: e => setS(p => ({ ...p, [k]: e.target.value })) });
  const C = (k) => ({ checked: !!s[k], onChange: e => setS(p => ({ ...p, [k]: e.target.checked })) });
  return (
    <Modal title={s.id ? 'Upraviť skill' : 'Nový skill'} width={760} onClose={onClose}
           footer={<><button className="btn" onClick={onClose}>Zavrieť</button>
                    <button className="btn btn-primary btn-sm" onClick={() => onSave(s)} disabled={!s.name}>Uložiť</button></>}>
      <div className="vstack" style={{gap:10}}>
        <div className="ai-form-row">
          <div className="field" style={{flex:1, minWidth:0}}><label>Názov (kebab-case)</label><input className="input mono" {...F('name')} placeholder="moj-skill"/></div>
          <div className="field ai-form-col-checks"><label>&nbsp;</label>
            <div className="hstack" style={{gap:14, marginTop:4, flexWrap:'wrap'}}>
              <label className="checkbox"><input type="checkbox" {...C('active')}/><span>Aktívny</span></label>
              <label className="checkbox"><input type="checkbox" {...C('restricted')}/><span>Per-user</span></label>
            </div>
          </div>
        </div>
        <div className="field">
          <label>Popis / trigger (kedy AI skill použije)</label>
          <textarea className="input" rows={2} {...F('description')} placeholder="Použi tento skill keď používateľ spomenie…"/>
        </div>
        <div className="field">
          <label>Pokyn (krátky prompt, voliteľný, preskakuje sa ak je Content vyplnený)</label>
          <textarea className="input" rows={3} {...F('prompt')}/>
        </div>
        <div className="field">
          <label>Content — plný SKILL.md (vrátane frontmatteru, Markdown)</label>
          <textarea className="input mono" rows={14} {...F('content')} placeholder={`---\nname: moj-skill\ndescription: …\n---\n\n# Nadpis\n\n…`}/>
          <div className="small muted">Ak je vyplnený, použije sa namiesto krátkeho promptu.</div>
        </div>
      </div>
    </Modal>
  );
}

function SkillBuilderModal({ editingSkillId, onClose, onSaved }) {
  const [messages, setMessages] = React.useState([]);
  const [input, setInput] = React.useState('');
  const [sending, setSending] = React.useState(false);
  const [sendingStartedAt, setSendingStartedAt] = React.useState(null);
  const [proposal, setProposal] = React.useState(null);
  const [saving, setSaving] = React.useState(false);
  const scrollRef = React.useRef(null);

  React.useEffect(() => {
    // Seed greeting
    const hint = editingSkillId ? 'Chceme upraviť existujúci skill. Povedz mi, čo má fungovať inak.' : 'Poďme spolu navrhnúť nový skill. Daj mi krátky popis: kedy ho má AI použiť a čo má spraviť?';
    setMessages([{ role: 'assistant', content: hint }]);
  }, [editingSkillId]);

  React.useEffect(() => {
    if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
  }, [messages, sending]);

  async function send(text) {
    const t = (text ?? input).trim();
    if (!t || sending) return;
    const next = [...messages, { role: 'user', content: t }];
    setMessages(next); setInput(''); setSending(true); setSendingStartedAt(Date.now());
    const r = await AI.post('/api/ai/skill-builder/chat', { messages: next, existingSkillId });
    setSending(false); setSendingStartedAt(null);
    if (r.ok && r.body.ok) {
      const clean = (r.body.text || '').replace(/<SKILL_READY>[\s\S]*?<\/SKILL_READY>/g, '').trim();
      const meta = { tools: r.body.toolsUsed || [], duration_ms: r.body.duration_ms };
      setMessages([...next, { role: 'assistant', content: clean || '(skill je pripravený)', _meta: meta }]);
      if (r.body.proposal) setProposal(r.body.proposal);
      // If AI directly saved via the save_skill tool, surface it so we can close + reload
      if (r.body.savedSkill) {
        setTimeout(() => {
          if (onSaved) onSaved();
        }, 500);
      }
    } else {
      setMessages([...next, { role: 'assistant', content: '⚠️ ' + (r.body.message || r.body.error || 'Chyba.'), _error: true }]);
    }
  }

  async function save() {
    if (!proposal) return;
    setSaving(true);
    const body = {
      name: proposal.name, description: proposal.description, content: proposal.content,
      active: 1, restricted: 0,
    };
    const r = editingSkillId
      ? await AI.patch('/api/ai/skills/' + editingSkillId, body)
      : await AI.post('/api/ai/skills', body);
    setSaving(false);
    if (r.ok) onSaved(); else alert(r.body.error || 'Uloženie zlyhalo.');
  }

  return (
    <Modal title={editingSkillId ? 'Skill Builder · úprava' : 'Skill Builder · nový skill'} width={820} onClose={onClose}
           footer={<>
             <button className="btn" onClick={onClose}>Zavrieť</button>
             <button className="btn btn-primary btn-sm" onClick={save} disabled={!proposal || saving}>
               {saving ? 'Ukladám…' : (proposal ? `Uložiť „${proposal.name}"` : 'Čakám na návrh…')}
             </button>
           </>}>
      <div className="ai-builder-body">
        {/* Message area */}
        <div
          ref={scrollRef}
          style={{
            flex: 1, overflow: 'auto',
            padding: '14px 16px',
            background: 'var(--bg)',
            border: '1px solid var(--border)',
            borderRadius: 10,
            marginBottom: 10,
          }}
        >
          {messages.map((m, i) => <AiMessage key={i} m={m}/>)}
          {sending && (
            <div className="hstack" style={{alignItems:'flex-start', gap:12}}>
              <div className="ai-avatar ai-avatar-bot"><Ico.sparkles/></div>
              <TypingIndicator label="Skill Builder premýšľa…" startedAt={sendingStartedAt}/>
            </div>
          )}
        </div>

        {/* Proposal preview card */}
        {proposal && (
          <div className="card" style={{padding:12, background:'var(--blue-50)', border:'1px solid rgba(10,61,98,0.12)', marginBottom:10}}>
            <div className="hstack">
              <div style={{flex:1, minWidth:0}}>
                <div className="small muted" style={{textTransform:'uppercase', letterSpacing:'0.08em', fontWeight:600, marginBottom:2}}>Pripravený skill</div>
                <b className="mono" style={{fontSize:14}}>{proposal.name}</b>
                <div className="small muted" style={{overflow:'hidden', textOverflow:'ellipsis'}}>{proposal.description}</div>
              </div>
              <span className="chip green" style={{padding:'2px 8px'}}><Ico.check/> pripravené</span>
            </div>
            <details style={{marginTop:8}}>
              <summary className="small" style={{cursor:'pointer', color:'var(--navy-700)'}}>Zobraziť plný obsah ({proposal.content.length} znakov)</summary>
              <pre style={{maxHeight:220, overflow:'auto', fontSize:11, background:'#fff', padding:10, border:'1px solid var(--border)', borderRadius:6, marginTop:6, whiteSpace:'pre-wrap'}}>{proposal.content}</pre>
            </details>
          </div>
        )}

        {/* Input bar — full-width card, flex textarea, send button right-aligned */}
        <div style={{
          background: '#fff',
          border: '1px solid var(--border-strong)',
          borderRadius: 12,
          padding: 10,
          boxShadow: 'var(--shadow-sm)',
        }}>
          <textarea
            placeholder="Popíš čo má skill robiť, prípadne pošli príklad zadania… (Enter = odoslať, Shift+Enter = nový riadok)"
            rows={2}
            value={input}
            onChange={e => setInput(e.target.value)}
            onKeyDown={e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); send(); } }}
            disabled={sending}
            style={{
              width: '100%', border: 'none', outline: 'none', resize: 'vertical',
              fontFamily: 'var(--font)', fontSize: 13.5,
              minHeight: 50, padding: '4px 6px',
              background: 'transparent',
            }}
          />
          <div className="hstack" style={{marginTop:6}}>
            <span className="small muted">
              {editingSkillId ? '✎ Úprava skillu' : '✦ Tvorba nového skillu'}
              {proposal && <> · <b style={{color:'var(--navy-700)'}}>návrh pripravený</b></>}
            </span>
            <button
              className="btn btn-primary btn-sm"
              style={{marginLeft:'auto'}}
              onClick={() => send()}
              disabled={sending || !input.trim()}
            >
              <Ico.plus style={{transform:'rotate(45deg)'}}/> Odoslať
            </button>
          </div>
        </div>
      </div>
    </Modal>
  );
}

function AiFunctions({ notify }) { return <CrudList endpoint="/api/ai/functions" label="functions" itemName="function"  title="Vlastné AI funkcie" notify={notify} fields={[
  { key:'name', label:'Názov' },
  { key:'category', label:'Kategória' },
  { key:'description', label:'Popis', type:'textarea', rows:2 },
  { key:'prompt', label:'Pokyn pre AI', type:'textarea', rows:6 },
  { key:'enabled', label:'Zapnuté', type:'bool' },
  { key:'custom', label:'Vlastná (pridá sa do system promptu)', type:'bool' },
]} columns={['name','category','enabled']}/>; }

function AiTraining({ notify }) {
  const [list, setList] = React.useState([]);
  const [edit, setEdit] = React.useState(null);
  const isAdmin = (window.currentSession?.user?.role === 'admin');

  async function reload() {
    const r = await AI.get('/api/ai/training');
    if (r.ok) setList(r.body.training || []);
  }
  React.useEffect(() => { reload(); }, []);

  async function save(item) {
    const body = { title: item.title, content: item.content, active: item.active !== undefined ? !!item.active : true };
    const r = item.id ? await AI.patch(`/api/ai/training/${item.id}`, body) : await AI.post('/api/ai/training', body);
    if (r.ok) {
      setEdit(null); reload();
      notify('success', r.body.status === 'pending' ? 'Odoslané administrátorovi na schválenie.' : 'Uložené.');
    } else notify('error', r.body.error || 'Chyba.');
  }
  async function remove(t) { if (!confirm('Zmazať?')) return; await AI.del('/api/ai/training/' + t.id); reload(); }

  const statusChip = (t) => {
    if (!t.status || t.status === 'approved') return <span className="chip green dot">Schválené</span>;
    if (t.status === 'pending')                return <span className="chip amber dot">Čaká na schválenie</span>;
    return <span className="chip red dot">Zamietnuté</span>;
  };

  return (
    <>
      <div className="card">
        <div className="card-head">
          <h3>Znalostná báza ({list.length}) {!isAdmin && <span className="chip amber" style={{padding:'1px 8px'}}>pôjde na schválenie</span>}</h3>
          <div style={{marginLeft:'auto'}} className="hstack">
            <button className="btn btn-sm" onClick={reload}><Ico.refresh/>Obnoviť</button>
            <button className="btn btn-primary btn-sm" onClick={() => setEdit({})}><Ico.plus/>{isAdmin ? 'Pridať' : 'Požiadať'}</button>
          </div>
        </div>

        {!isAdmin && (
          <div className="small muted" style={{background:'var(--amber-100)', padding:'8px 14px', borderBottom:'1px solid rgba(243,167,18,0.2)'}}>
            ℹ️ Znalostná báza je spoločná pre celý tím. Vaše príspevky pred zverejnením schvaľuje administrátor.
          </div>
        )}

        {edit && <CrudEditor item={edit} fields={[
          { key:'title',   label:'Názov' },
          { key:'content', label:'Obsah (čo má asistent vedieť)', type:'textarea', rows:8 },
          { key:'active',  label:'Aktívna', type:'bool' },
        ]} title={edit.id ? 'Upraviť znalosť' : (isAdmin ? 'Nová znalosť' : 'Požiadať o novú znalosť')} onClose={() => setEdit(null)} onSave={save}/>}

        {list.length === 0 ? <div className="muted" style={{padding:30, textAlign:'center'}}>Žiadne znalosti.</div> :
         <div className="ai-table-wrap"><table className="tbl">
           <thead><tr><th>Názov</th><th>Stav</th><th>Aktívna</th><th></th></tr></thead>
           <tbody>
             {list.map(t => (
               <tr key={t.id} style={t.status === 'pending' ? {background:'rgba(246,191,71,0.08)'} : t.status === 'rejected' ? {opacity:0.5} : {}}>
                 <td><b>{t.title}</b><div className="small muted" style={{maxWidth:500, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap'}}>{(t.content || '').slice(0, 120)}</div></td>
                 <td>{statusChip(t)}</td>
                 <td>{t.active ? '✓' : '—'}</td>
                 <td style={{textAlign:'right', whiteSpace:'nowrap'}}>
                   {(isAdmin || t.requested_by === window.currentSession?.user?.id) && <>
                     <button className="btn btn-xs" onClick={() => setEdit(t)}>Upraviť</button>{' '}
                     <button className="btn btn-xs btn-danger" onClick={() => remove(t)}>Zmazať</button>
                   </>}
                 </td>
               </tr>
             ))}
           </tbody>
         </table></div>}
      </div>
    </>
  );
}

// Reusable CRUD list component for skills/functions/training
function CrudList({ endpoint, label, itemName, title, notify, fields, columns }) {
  const [list, setList] = React.useState([]);
  const [edit, setEdit] = React.useState(null);

  async function reload() {
    const r = await AI.get(endpoint);
    if (r.ok) setList(r.body[label] || []);
  }
  React.useEffect(() => { reload(); }, []);

  async function save(item) {
    const body = {};
    for (const f of fields) body[f.key] = item[f.key];
    const r = item.id ? await AI.patch(`${endpoint}/${item.id}`, body) : await AI.post(endpoint, body);
    if (r.ok) { setEdit(null); reload(); notify('success', 'Uložené.'); }
    else notify('error', r.body.error || 'Chyba.');
  }
  async function remove(item) { if (!confirm('Zmazať?')) return; await AI.del(`${endpoint}/${item.id}`); reload(); }

  return (
    <div className="card">
      <div className="card-head">
        <h3>{title} ({list.length})</h3>
        <div style={{marginLeft:'auto'}} className="hstack">
          <button className="btn btn-sm" onClick={reload}><Ico.refresh/>Obnoviť</button>
          <button className="btn btn-primary btn-sm" onClick={() => setEdit({})}><Ico.plus/>Pridať</button>
        </div>
      </div>

      {edit && <CrudEditor item={edit} fields={fields} title={edit.id ? 'Upraviť ' + itemName : 'Nový ' + itemName} onClose={() => setEdit(null)} onSave={save}/>}

      {list.length === 0 ? <div className="muted" style={{padding:30, textAlign:'center'}}>Žiadne položky.</div> :
       <div className="ai-table-wrap"><table className="tbl">
         <thead><tr>{columns.map(c => <th key={c}>{c}</th>)}<th></th></tr></thead>
         <tbody>
           {list.map(item => (
             <tr key={item.id}>
               {columns.map(c => {
                 const v = item[c];
                 if (typeof v === 'number' && (c === 'active' || c === 'enabled')) return <td key={c}>{v ? '✓' : '—'}</td>;
                 return <td key={c} style={{maxWidth:400, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap'}}>{v || '—'}</td>;
               })}
               <td style={{textAlign:'right', whiteSpace:'nowrap'}}>
                 <button className="btn btn-xs" onClick={() => setEdit(item)}>Upraviť</button>{' '}
                 <button className="btn btn-xs btn-danger" onClick={() => remove(item)}>Zmazať</button>
               </td>
             </tr>
           ))}
         </tbody>
       </table></div>}
    </div>
  );
}

function CrudEditor({ item, fields, title, onClose, onSave }) {
  const [state, setState] = React.useState(() => {
    const s = {};
    for (const f of fields) s[f.key] = item[f.key] != null ? item[f.key] : (f.type === 'bool' ? false : '');
    if (item.id) s.id = item.id;
    return s;
  });
  const set = (k, v) => setState(p => ({ ...p, [k]: v }));

  return (
    <Modal title={title} width={640} onClose={onClose}
           footer={<><button className="btn" onClick={onClose}>Zavrieť</button>
                    <button className="btn btn-primary btn-sm" onClick={() => onSave(state)}>Uložiť</button></>}>
      <div className="vstack" style={{gap:10}}>
        {fields.map(f => (
          <div key={f.key} className="field">
            <label>{f.label}</label>
            {f.type === 'textarea' ? (
              <textarea className="input" rows={f.rows || 3} value={state[f.key] || ''} onChange={e => set(f.key, e.target.value)}/>
            ) : f.type === 'bool' ? (
              <label className="checkbox" style={{marginTop:3}}>
                <input type="checkbox" checked={!!state[f.key]} onChange={e => set(f.key, e.target.checked)}/>
                <span>{f.label}</span>
              </label>
            ) : (
              <input className="input" value={state[f.key] || ''} onChange={e => set(f.key, e.target.value)}/>
            )}
          </div>
        ))}
      </div>
    </Modal>
  );
}

// ═════════ STATS ════════════════════════════════════════════════════════════
function AiStats() {
  const [data, setData] = React.useState(null);
  const [days, setDays] = React.useState(30);
  React.useEffect(() => {
    AI.get(`/api/ai/stats?days=${days}`).then(r => r.ok && setData(r.body));
  }, [days]);

  if (!data) return <div className="card" style={{padding:30, textAlign:'center'}}>Načítavam…</div>;

  return (
    <div className="vstack" style={{gap:14}}>
      <div className="card">
        <div className="card-head">
          <h3>Využitie AI — posledných {days} dní</h3>
          <div style={{marginLeft:'auto'}}>
            <select className="input" value={days} onChange={e => setDays(+e.target.value)} style={{padding:'5px 6px'}}>
              <option value={7}>7 dní</option>
              <option value={30}>30 dní</option>
              <option value={90}>90 dní</option>
              <option value={365}>1 rok</option>
            </select>
          </div>
        </div>
        <div className="card-body ai-stats-grid">
          <div className="card ai-stat-card"><div className="small muted">Vstupné tokeny</div><div className="ai-stat-value">{(data.totals.input || 0).toLocaleString('sk-SK')}</div></div>
          <div className="card ai-stat-card"><div className="small muted">Výstupné tokeny</div><div className="ai-stat-value">{(data.totals.output || 0).toLocaleString('sk-SK')}</div></div>
          <div className="card ai-stat-card"><div className="small muted">Náklady (odhad)</div><div className="ai-stat-value">{(data.totals.cost || 0).toFixed(3)} $</div></div>
        </div>
      </div>

      <div className="card">
        <div className="card-head"><h3>Detail po dňoch a modeli</h3></div>
        <div className="ai-table-wrap"><table className="tbl">
          <thead><tr><th>Dátum</th><th>Model</th><th>Vstup</th><th>Výstup</th><th>Náklad</th></tr></thead>
          <tbody>
            {(data.rows || []).map((r, i) => (
              <tr key={i}>
                <td className="mono small">{r.date}</td>
                <td className="mono small">{r.model}</td>
                <td>{(r.input_tok || 0).toLocaleString('sk-SK')}</td>
                <td>{(r.output_tok || 0).toLocaleString('sk-SK')}</td>
                <td>{(r.cost_usd || 0).toFixed(4)} $</td>
              </tr>
            ))}
          </tbody>
        </table></div>
      </div>
    </div>
  );
}

// ═════════ SETTINGS ═════════════════════════════════════════════════════════
function AiSettings({ notify }) {
  const [s, setS] = React.useState(null);
  const [busy, setBusy] = React.useState(false);

  async function reload() { const r = await AI.get('/api/ai/settings'); if (r.ok) setS(r.body.settings); }
  React.useEffect(() => { reload(); }, []);

  async function save() {
    setBusy(true);
    const body = {
      ai_system_prompt: s.systemPrompt,
      ai_default_model: s.defaultModel,
      ai_notify_email:  s.notifyEmail,
      ai_goodwe_powerstation: s.goodwePowerStationId,
      app_name: s.appName,
      company_name: s.companyName,
    };
    const r = await AI.patch('/api/ai/settings', body);
    setBusy(false);
    if (r.ok) { notify('success', 'Nastavenia uložené.'); reload(); }
    else notify('error', 'Chyba.');
  }

  if (!s) return <div className="card" style={{padding:30, textAlign:'center'}}>Načítavam…</div>;
  const set = (k, v) => setS(p => ({ ...p, [k]: v }));

  const isConnected = (v) => v && !String(v).startsWith('••••') ? '✓' : (v ? '✓' : '✗');

  return (
    <div className="vstack" style={{gap:14}}>
      <div className="card">
        <div className="card-head"><h3>Základné nastavenia</h3></div>
        <div className="card-body vstack" style={{gap:10, padding:14}}>
          <div className="ai-form-row">
            <div className="field" style={{flex:1, minWidth:0}}><label>Názov firmy</label><input className="input" value={s.companyName || ''} onChange={e => set('companyName', e.target.value)}/></div>
            <div className="field" style={{flex:1, minWidth:0}}><label>Názov aplikácie</label><input className="input" value={s.appName || ''} onChange={e => set('appName', e.target.value)}/></div>
          </div>
          <div className="field">
            <label>Systémový prompt (osobnosť asistenta)</label>
            <textarea className="input" rows={5} value={s.systemPrompt || ''} onChange={e => set('systemPrompt', e.target.value)} placeholder="Si firemný AI asistent…"/>
          </div>
          <div className="hstack" style={{gap:10}}>
            <div className="field" style={{flex:1}}>
              <label>Predvolený model</label>
              <select className="input" value={s.defaultModel || ''} onChange={e => set('defaultModel', e.target.value)}>
                <option value="">(auto)</option>
                <option value="claude-haiku-4-5-20251001">Haiku</option>
                <option value="claude-sonnet-4-6">Sonnet</option>
                <option value="claude-opus-4-7">Opus</option>
              </select>
            </div>
            <div className="field" style={{flex:1}}>
              <label>Hlavný notifikačný e-mail (pre úlohy)</label>
              <input className="input" value={s.notifyEmail || ''} onChange={e => set('notifyEmail', e.target.value)}/>
            </div>
          </div>
          <div className="field">
            <label>GoodWe — Power Station ID (voliteľné, default pre tool-y)</label>
            <input className="input mono" value={s.goodwePowerStationId || ''} onChange={e => set('goodwePowerStationId', e.target.value)}/>
          </div>
          <button className="btn btn-primary btn-sm" style={{alignSelf:'flex-start'}} onClick={save} disabled={busy}>
            {busy ? 'Ukladám…' : 'Uložiť'}
          </button>
        </div>
      </div>

      <div className="card">
        <div className="card-head"><h3>Stav integrácií</h3><div className="small muted" style={{marginLeft:'auto'}}>Konfigurácia je v <a href="#/integrations" style={{color:'var(--navy-700)'}}>Integráciách</a>.</div></div>
        <div className="ai-table-wrap"><table className="tbl">
          <tbody>
            <tr><td><b>Anthropic</b></td><td className="mono">{s.anthropicApiKey || '—'}</td><td>{isConnected(s.anthropicApiKey)}</td></tr>
            <tr><td><b>WooCommerce</b></td><td className="mono">{s.woocommerceUrl || '—'}</td><td>{isConnected(s.woocommerceSecret)}</td></tr>
            <tr><td><b>SuperFaktúra</b></td><td className="mono">{s.superfakturaEmail || '—'}</td><td>{isConnected(s.superfakturaApiKey)}</td></tr>
            <tr><td><b>ClickUp</b></td><td className="mono">{s.clickupToken || '—'}</td><td>{isConnected(s.clickupToken)}</td></tr>
            <tr><td><b>GoodWe</b></td><td className="mono">{s.goodweAccount || '—'}</td><td>{isConnected(s.goodwePassword)}</td></tr>
          </tbody>
        </table></div>
      </div>
    </div>
  );
}

// Export shared helpers + components so screens/floating-ai.jsx can reuse them
// (Babel Standalone top-level `const` isn't auto-global, so we expose explicitly).
Object.assign(window, {
  AiAssistant,
  // attachment helpers
  filesToAnthropicBlocks, buildUserContent, plainTextOfContent,
  fmtBytes, readFileAsBase64, readFileAsText,
  AI_IMAGE_TYPES, AI_TEXT_TYPES_RE, AI_PDF_TYPE, AI_MAX_FILE_BYTES, AI_MAX_FILES,
  // UI components
  AttachmentPreviewRow, InlineAttachments,
  // markdown renderer
  renderMarkdown,
});
