import React, { useCallback, useEffect, useRef, useState } from 'react';
import { marked } from 'marked';
import { api } from './api.js';

// Native amerc chat — speaks plain HTTP to amerc-api (long-poll + streamed
// file relay). No iframe, no external relay: works whenever the API is up.

// Render chat text as markdown. The agent (or user) owns its links — we only
// make them safe and open them in a new tab; the agent decides whether a link
// points at the netdisk, a showcase, or anywhere else.
marked.setOptions({ breaks: true, gfm: true });
function mdHtml(text) {
  let raw;
  try { raw = marked.parse(String(text || '')); } catch { return escapeText(text); }
  if (typeof window === 'undefined' || !window.DOMParser) return escapeText(text);
  const doc = new DOMParser().parseFromString(raw, 'text/html');
  doc.querySelectorAll('script,style,iframe,object,embed,link,meta,form,input,button').forEach((n) => n.remove());
  doc.querySelectorAll('*').forEach((el) => {
    [...el.attributes].forEach((a) => {
      if (/^on/i.test(a.name) || a.name === 'style') el.removeAttribute(a.name);
      if ((a.name === 'href' || a.name === 'src') && /^\s*(javascript|data):/i.test(a.value)) el.removeAttribute(a.name);
    });
    if (el.tagName === 'A') { el.setAttribute('target', '_blank'); el.setAttribute('rel', 'noopener noreferrer'); }
  });
  return doc.body.innerHTML;
}
function escapeText(s) { return String(s || '').replace(/[&<>]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c])); }

const FILE_TTL_MS = 5 * 60 * 1000;
const fmtBytes = (n) => (n >= 1048576 ? `${(n / 1048576).toFixed(1)} MB` : n >= 1024 ? `${(n / 1024).toFixed(0)} KB` : `${n} B`);
const fmtTime = (t) => new Date(t).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });

function FileMsg({ m, mine }) {
  const [now, setNow] = useState(Date.now());
  useEffect(() => { const t = setInterval(() => setNow(Date.now()), 10000); return () => clearInterval(t); }, []);
  const left = m.created_at + FILE_TTL_MS - now;
  const isImage = (m.file_mime || '').startsWith('image/');
  return (
    <div className="ch-file">
      <span className="ch-file-ico" aria-hidden="true">{isImage ? '🖼️' : '📦'}</span>
      <div className="ch-file-meta">
        <strong>{m.file_name}</strong>
        <span>{fmtBytes(m.file_size)} · {m.file_mime || 'file'}</span>
      </div>
      {mine
        ? <span className="ch-file-sent">sent ✓</span>
        : left > 0
          ? <a className="px-action ch-file-get" href={`/api/agents/files/${m.file_token}`} download={m.file_name}>⬇ download · {Math.ceil(left / 60000)}m left</a>
          : <span className="ch-file-gone">relay link expired — bytes were never stored</span>}
    </div>
  );
}

export default function ChatPanel({ instance, accesskey, sessionId: resumeId, onClose }) {
  const [sid, setSid] = useState(resumeId || null);
  const [msgs, setMsgs] = useState([]);
  const [skills, setSkills] = useState([]);
  const [instStatus, setInstStatus] = useState(instance?.status || '');
  const [sessStatus, setSessStatus] = useState('active');
  const [input, setInput] = useState('');
  const [err, setErr] = useState('');
  const [up, setUp] = useState(null); // { name, pct, phase }
  const seqRef = useRef(0);
  const bodyRef = useRef(null);
  const aliveRef = useRef(true);

  // open (or resume) the session
  useEffect(() => {
    if (sid) return;
    api(`/agents/instances/${instance.id}/sessions`, { method: 'POST', body: { connector: 'chat', accesskey: accesskey || undefined } })
      .then((r) => setSid(r.sessionId))
      .catch((e) => setErr(e.message));
  }, [sid, instance, accesskey]);

  // long-poll loop
  useEffect(() => {
    if (!sid) return undefined;
    aliveRef.current = true;
    let timer;
    const loop = async () => {
      while (aliveRef.current) {
        try {
          const d = await api(`/agents/sessions/${sid}/messages?after=${seqRef.current}&wait=1`);
          if (!aliveRef.current) return;
          if (d.messages?.length) {
            seqRef.current = d.messages[d.messages.length - 1].seq;
            setMsgs((prev) => [...prev, ...d.messages]);
          }
          if (d.skills) setSkills(d.skills);
          if (d.instanceStatus) setInstStatus(d.instanceStatus);
          if (d.sessionStatus) setSessStatus(d.sessionStatus);
        } catch {
          await new Promise((r) => { timer = setTimeout(r, 3000); });
        }
      }
    };
    loop();
    return () => { aliveRef.current = false; clearTimeout(timer); };
  }, [sid]);

  useEffect(() => { bodyRef.current?.scrollTo({ top: bodyRef.current.scrollHeight, behavior: 'smooth' }); }, [msgs]);

  const send = async (e) => {
    e?.preventDefault();
    const text = input.trim();
    if (!text || !sid) return;
    setInput(''); setErr('');
    try { await api(`/agents/sessions/${sid}/messages`, { method: 'POST', body: { content: text } }); }
    catch (e2) { setErr(e2.message); setInput(text); }
  };

  // file/image send: stream straight through the relay; never stored server-side
  const sendFile = (file) => {
    if (!file || !sid) return;
    setErr('');
    const xhr = new XMLHttpRequest();
    xhr.open('POST', `/api/agents/sessions/${sid}/files?name=${encodeURIComponent(file.name)}`);
    if (file.type) xhr.setRequestHeader('Content-Type', file.type);
    xhr.upload.onprogress = (ev) => {
      const pct = ev.total ? Math.round((ev.loaded / ev.total) * 100) : 0;
      setUp({ name: file.name, pct, phase: pct >= 100 ? 'waiting for the agent to take it…' : 'streaming…' });
    };
    xhr.onload = () => {
      setUp(null);
      if (xhr.status === 408) setErr('file not picked up within 5 minutes — try again while the agent is responsive');
      else if (xhr.status >= 400) setErr(`file send failed (${xhr.status})`);
    };
    xhr.onerror = () => { setUp(null); setErr('file send failed — network error'); };
    setUp({ name: file.name, pct: 0, phase: 'streaming…' });
    xhr.send(file);
  };

  const closeSession = async () => {
    if (sid && sessStatus === 'active') { try { await api(`/agents/sessions/${sid}/close`, { method: 'POST' }); } catch { /* already gone */ } }
    onClose?.();
  };

  const live = sessStatus === 'active';
  return (
    <div className="ch">
      <div className="ch-head">
        <span className={`ch-dot ${instStatus}`} title={`instance ${instStatus}`} />
        <div className="ch-head-t">
          <strong>{instance.name || `instance #${instance.id}`}</strong>
          <span className="ch-sub">{live ? `${instStatus} · session #${sid ?? '…'}` : `session #${sid} · closed (chatlog)`}</span>
        </div>
        <button className="ch-x" onClick={closeSession} type="button">{live ? 'end session ✕' : 'close ✕'}</button>
      </div>
      {skills.length > 0 && (
        <div className="ch-skills" title="skills the broker reports as loaded">
          <span className="ch-skills-label">skills loaded</span>
          {skills.map((s) => <span key={s} className="ch-skill">{s}</span>)}
        </div>
      )}
      <div className="ch-body" ref={bodyRef}>
        {msgs.map((m) => {
          if (m.kind === 'event') return <div key={m.seq} className="ch-event">— {m.content} · {fmtTime(m.created_at)} —</div>;
          const mine = m.role === 'user';
          return (
            <div key={m.seq} className={`ch-msg ${mine ? 'mine' : 'theirs'}`}>
              {m.kind === 'file' ? <FileMsg m={m} mine={mine} /> : <div className="ch-md" dangerouslySetInnerHTML={{ __html: mdHtml(m.content) }} />}
              <span className="ch-when">{fmtTime(m.created_at)}</span>
            </div>
          );
        })}
        {!msgs.length && <div className="ch-event">{sid ? 'no messages yet — say something' : 'opening session…'}</div>}
      </div>
      {up && (
        <div className="ch-upload">
          <span>📤 {up.name}</span>
          <span className="ch-upload-bar"><i style={{ width: `${up.pct}%` }} /></span>
          <span className="ch-upload-phase">{up.phase}</span>
        </div>
      )}
      {err && <p className="px-auth-err ch-err">{err}</p>}
      {live ? (
        <form className="ch-foot" onSubmit={send}>
          <label className="ch-attach" title="send a file or image — relayed live, never stored on the server">
            📎<input type="file" hidden onChange={(e) => { sendFile(e.target.files?.[0]); e.target.value = ''; }} />
          </label>
          <input className="ch-input" placeholder={instStatus === 'online' ? 'message the agent…' : `agent is ${instStatus} — messages queue in the chatlog`} value={input} onChange={(e) => setInput(e.target.value)} />
          <button className="ch-send" type="submit" disabled={!input.trim()}>➤</button>
        </form>
      ) : (
        <div className="ch-foot ch-foot-closed">this session is closed — its chatlog is kept here</div>
      )}
    </div>
  );
}

// Chatlog: every session you started (or that ran on your instances)
export function SessionLog({ onOpen }) {
  const [sessions, setSessions] = useState(null);
  const [err, setErr] = useState('');
  useEffect(() => { api('/agents/sessions').then((d) => setSessions(d.sessions || [])).catch((e) => { setErr(e.message); setSessions([]); }); }, []);
  if (err) return <p className="px-auth-err">{err}</p>;
  if (!sessions) return <p className="ap-hint">loading chatlog…</p>;
  if (!sessions.length) return <p className="ap-hint">No sessions yet — open an agent and start one.</p>;
  return (
    <div className="ch-log">
      {sessions.map((s) => (
        <button key={s.id} className="ch-log-row" type="button" onClick={() => onOpen(s)}>
          <span className={`ch-dot ${s.status === 'active' ? 'online' : 'down'}`} />
          <div className="ch-log-main">
            <strong>{s.class_name || 'agent'} <em>· session #{s.id}</em></strong>
            <span className="ch-log-last">{s.last ? (s.last.kind === 'file' ? `📦 ${s.last.file_name}` : s.last.content) : 'no messages'}</span>
          </div>
          <div className="ch-log-meta">
            <span>{s.messageCount} msgs</span>
            <span>{new Date(s.started_at).toLocaleDateString()}</span>
          </div>
        </button>
      ))}
    </div>
  );
}
