/**
 * Non-side-effect class export for `<chat-thread-ui>`.
 *
 * Importing this file gives you the class(es) without auto-registering the tag.
 * Useful for test isolation, subclassing with tag-name override, or selective
 * composition.
 *
 * The auto-register path stays at `@adia-ai/web-components/components/chat-thread`
 * (which imports this file + calls `defineIfFree()`).
 *
 * @see ../../USAGE.md#registration--auto-vs-explicit
 */

import { UIElement } from '../../core/element.js';
import { renderMarkdown } from '../../core/markdown.js';

function escapeHTML(s) {
  return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}

/**
 * <chat-thread-ui> — Minimal chat-thread container.
 *
 * Structure:
 *   <chat-thread-ui>
 *     <header>
 *       <span slot="avatar">AI</span>
 *       <span slot="name">Assistant</span>
 *     </header>
 *     <section>
 *       <!-- messages rendered here -->
 *     </section>
 *     <footer>
 *       <chat-input-ui placeholder="Type a message..."></chat-input-ui>
 *     </footer>
 *   </chat-thread-ui>
 *
 * JS API:
 *   chat.appendMessage({ role: 'user', content: 'Hello' })
 *   chat.appendMessage({ role: 'assistant', content: 'Hi!' })
 *   chat.appendChunk('token')       // append to last assistant message
 *   chat.startStreaming()            // show cursor, disable input
 *   chat.stopStreaming()             // hide cursor, enable input
 *   chat.clear()
 *   chat.messages                   // read all messages
 *
 * Events:
 *   submit       — user pressed send (detail: { text, model })
 */
export class UIChatThread extends UIElement {
  static properties = {
    streaming: { type: Boolean, default: false, reflect: true },
  };

  static template = () => null; // wrapper mode

  #messages = [];
  #messagesEl = null;
  #inputEl = null;
  #scrollRaf = null;

  get messages() { return [...this.#messages]; }

  connected() {
    // Stamp default structure if empty
    if (!this.querySelector('header') && !this.querySelector('section')) {
      this.innerHTML = `
        <header>
          <span slot="avatar">AI</span>
          <span slot="name">Assistant</span>
        </header>
        <section></section>
        <footer>
          <chat-input-ui placeholder="Type a message..."></chat-input-ui>
        </footer>
      `;
    }

    this.#messagesEl = this.querySelector('section');
    this.#inputEl = this.querySelector('chat-input-ui');

    this.#inputEl?.addEventListener('submit', this.#onSubmit);
  }

  disconnected() {
    if (this.#scrollRaf != null) {
      cancelAnimationFrame(this.#scrollRaf);
      this.#scrollRaf = null;
    }
    this.#inputEl?.removeEventListener('submit', this.#onSubmit);
    this.#messagesEl = null;
    this.#inputEl = null;
  }

  #onSubmit = (e) => {
    if (this.streaming) return;
    const { text, model } = e.detail || {};
    if (!text) return;
    this.#inputEl.clear();
    this.dispatchEvent(new CustomEvent('submit', { bubbles: true, detail: { text, model } }));
  };

  appendMessage({ role, content = '', render = false }) {
    this.#messages.push({ role, content });
    const el = document.createElement('div');
    el.setAttribute('data-role', role);

    const body = render ? renderMarkdown(content) : escapeHTML(content);

    if (role === 'user') {
      el.innerHTML = `<div slot="bubble">${body}</div>`;
    } else {
      const cursor = render ? '' : '<span slot="cursor"></span>';
      el.innerHTML = `
        <span slot="avatar">${role === 'assistant' ? 'AI' : role[0].toUpperCase()}</span>
        <div slot="bubble">${body}${cursor}</div>
      `;
    }

    this.#messagesEl?.appendChild(el);
    this.#scrollToBottom();
    return el;
  }

  appendChunk(text) {
    const last = this.#messages[this.#messages.length - 1];
    if (!last || last.role === 'user') return;
    last.content += text;

    const bubble = this.#messagesEl?.querySelector('[data-role]:last-child [slot="bubble"]');
    if (bubble) {
      const cursor = bubble.querySelector('[slot="cursor"]');
      if (cursor) cursor.remove();
      bubble.insertAdjacentText('beforeend', text);
      bubble.insertAdjacentHTML('beforeend', '<span slot="cursor"></span>');
    }
    this.#scrollToBottom();
  }

  startStreaming() {
    this.streaming = true;
    if (this.#inputEl) this.#inputEl.disabled = true;
  }

  stopStreaming() {
    this.streaming = false;
    if (this.#inputEl) this.#inputEl.disabled = false;
    // Remove cursor from last message
    this.#messagesEl?.querySelector('[data-role]:last-child [slot="cursor"]')?.remove();

    // Render markdown in the last assistant bubble
    const last = this.#messages[this.#messages.length - 1];
    if (last && last.role === 'assistant' && last.content) {
      const bubble = this.#messagesEl?.querySelector('[data-role]:last-child [slot="bubble"]');
      if (bubble) bubble.innerHTML = renderMarkdown(last.content);
    }

    this.#inputEl?.focus();
  }

  clear() {
    this.#messages.length = 0;
    if (this.#messagesEl) this.#messagesEl.innerHTML = '';
  }

  #scrollToBottom() {
    const el = this.#messagesEl;
    if (!el) return;
    if (this.#scrollRaf != null) cancelAnimationFrame(this.#scrollRaf);
    this.#scrollRaf = requestAnimationFrame(() => {
      this.#scrollRaf = null;
      if (!this.isConnected) return;
      el.scrollTop = el.scrollHeight;
    });
  }
}
