(function (global) {
  // AI School Research Assistant — a chat panel scoped to whichever school
  // profile is currently open on index.html. Deliberately layout-agnostic:
  // this module only builds a DOM subtree and returns it plus a small
  // control surface (setActiveSchool/open/close) — script.js decides where
  // to mount that subtree (today: a third column in .layout, see
  // styles.css's .school-assistant-mount). Swapping to a drawer/modal/
  // floating layout later means changing only the mount point, not this
  // file.
  //
  // Conversation state is in-memory only (cleared on page reload, and
  // reset whenever the active school changes — see setActiveSchool) since
  // no conversation-persistence layer exists yet anywhere in this app.
  //
  // Rendering is incremental, not a full re-render per message (that was
  // the original V1 approach — messagesEl.innerHTML = '' + rebuild on
  // every send/receive — which is what made the panel feel jittery: every
  // existing bubble got destroyed and recreated on every turn, so no
  // entrance animation could ever play and the whole list visibly
  // flickered). Each bubble is created once and appended once; a CSS
  // animation on insertion (styles.css) gives it a natural fade-in, and
  // the backend still returns one complete answer (no real token
  // streaming — see the V1 plan's deferred-scope list), so the "typing"
  // feel is a client-side progressive reveal of that finished text
  // (revealText below), the same technique most chat UIs without real
  // streaming infra use to avoid a jarring instant wall-of-text.

  const SUGGESTED_QUESTIONS = [
    'What are the strongest programs here?',
    'What are the biggest admissions challenges?',
    'How expensive is this school?',
    'What should I know before applying?',
    'What are the biggest pros and cons?'
  ];

  function resolveApiUrl(pathname) {
    const configuredBase = typeof global.COLLEGE_PLAYBOOK_API_BASE === 'string'
      ? global.COLLEGE_PLAYBOOK_API_BASE.trim()
      : '';
    if (configuredBase) {
      return `${configuredBase.replace(/\/$/, '')}${pathname}`;
    }
    if (global.location && global.location.port === '4173') {
      return `http://${global.location.hostname}:3001${pathname}`;
    }
    return pathname;
  }

  function el(tag, className, text) {
    const node = document.createElement(tag);
    if (className) node.className = className;
    if (text != null) node.textContent = text;
    return node;
  }

  function prefersReducedMotion() {
    return typeof global.matchMedia === 'function' && global.matchMedia('(prefers-reduced-motion: reduce)').matches;
  }

  // Reveals `fullText` into `node` progressively rather than setting it
  // all at once — total duration scales with length (short answers don't
  // crawl, long ones don't drag past ~2.6s) and is skipped entirely for
  // very short text or a reduced-motion preference. `onStep` fires after
  // each chunk so the caller can keep the view scrolled to it.
  function revealText(node, fullText, { onStep, onDone } = {}) {
    if (prefersReducedMotion() || fullText.length < 40) {
      node.textContent = fullText;
      if (onStep) onStep();
      if (onDone) onDone();
      return () => {};
    }

    const totalDurationMs = Math.min(2600, Math.max(500, fullText.length * 4));
    const stepCount = Math.max(20, Math.round(totalDurationMs / 16));
    const chunkSize = Math.max(1, Math.ceil(fullText.length / stepCount));
    const intervalMs = totalDurationMs / stepCount;

    let shown = 0;
    const timer = setInterval(() => {
      shown = Math.min(fullText.length, shown + chunkSize);
      node.textContent = fullText.slice(0, shown);
      if (onStep) onStep();
      if (shown >= fullText.length) {
        clearInterval(timer);
        if (onDone) onDone();
      }
    }, intervalMs);

    return () => clearInterval(timer);
  }

  function createSchoolAssistantPanel() {
    const state = {
      schoolId: null,
      schoolName: null,
      messages: [], // { role: 'user' | 'assistant', content }
      sending: false
    };

    const root = el('div', 'school-assistant-panel');

    const header = el('div', 'school-assistant-header');
    const headerTitle = el('div', 'school-assistant-title', 'AI School Assistant');
    const headerSchool = el('div', 'school-assistant-school', 'Select a school to get started.');
    const closeBtn = el('button', 'school-assistant-close', '✕');
    closeBtn.type = 'button';
    closeBtn.setAttribute('aria-label', 'Close AI assistant');
    const headerText = el('div', 'school-assistant-header-text');
    headerText.appendChild(headerTitle);
    headerText.appendChild(headerSchool);
    header.appendChild(headerText);
    header.appendChild(closeBtn);
    root.appendChild(header);

    const messagesEl = el('div', 'school-assistant-messages');
    messagesEl.setAttribute('aria-live', 'polite');
    root.appendChild(messagesEl);

    const suggestionsEl = el('div', 'school-assistant-suggestions');
    root.appendChild(suggestionsEl);

    const errorEl = el('div', 'school-assistant-error');
    errorEl.hidden = true;
    root.appendChild(errorEl);

    const inputRow = el('div', 'school-assistant-input-row');
    const textarea = document.createElement('textarea');
    textarea.className = 'school-assistant-input';
    textarea.placeholder = 'Ask a question…';
    textarea.rows = 1;
    textarea.disabled = true;
    const sendBtn = el('button', 'school-assistant-send', 'Send');
    sendBtn.type = 'button';
    sendBtn.disabled = true;
    inputRow.appendChild(textarea);
    inputRow.appendChild(sendBtn);
    root.appendChild(inputRow);

    // Only auto-follows new content if the view was already near the
    // bottom — mirrors ChatGPT's behavior of not yanking you back down
    // mid-reveal if you've scrolled up to reread something earlier.
    function isNearBottom() {
      return messagesEl.scrollHeight - messagesEl.scrollTop - messagesEl.clientHeight < 48;
    }
    function scrollToBottom(force) {
      if (force || isNearBottom()) {
        messagesEl.scrollTop = messagesEl.scrollHeight;
      }
    }

    function clearEmptyState() {
      const intro = messagesEl.querySelector('.school-assistant-intro');
      if (intro) intro.remove();
    }

    function showEmptyState() {
      messagesEl.innerHTML = '';
      if (!state.schoolName) return;
      const intro = el('div', 'school-assistant-intro');
      intro.appendChild(el('p', null, `I'm here to help you research ${state.schoolName}.`));
      intro.appendChild(el('p', 'school-assistant-intro-sub', "Ask me anything about its academics, admissions, cost, or student experience."));
      messagesEl.appendChild(intro);
    }

    function renderSuggestions() {
      suggestionsEl.innerHTML = '';
      if (!state.schoolName || state.messages.length > 0) return;
      SUGGESTED_QUESTIONS.forEach(question => {
        const chip = el('button', 'school-assistant-suggestion-chip', question);
        chip.type = 'button';
        chip.addEventListener('click', () => {
          if (state.sending) return;
          submitQuestion(question);
        });
        suggestionsEl.appendChild(chip);
      });
    }

    function appendUserMessage(text) {
      clearEmptyState();
      const wasNearBottom = isNearBottom();
      const bubble = el('div', 'school-assistant-message school-assistant-message-user', text);
      messagesEl.appendChild(bubble);
      scrollToBottom(wasNearBottom);
    }

    function appendThinkingBubble() {
      const wasNearBottom = isNearBottom();
      const bubble = el('div', 'school-assistant-message school-assistant-message-assistant school-assistant-message-pending');
      const dots = el('span', 'school-assistant-typing-dots');
      dots.appendChild(el('span', 'school-assistant-typing-dot'));
      dots.appendChild(el('span', 'school-assistant-typing-dot'));
      dots.appendChild(el('span', 'school-assistant-typing-dot'));
      bubble.appendChild(dots);
      messagesEl.appendChild(bubble);
      scrollToBottom(wasNearBottom);
      return bubble;
    }

    function setError(message) {
      if (!message) {
        errorEl.hidden = true;
        errorEl.textContent = '';
        return;
      }
      errorEl.hidden = false;
      errorEl.textContent = message;
    }

    function setSending(isSending) {
      state.sending = isSending;
      sendBtn.disabled = isSending || !state.schoolId;
      textarea.disabled = isSending || !state.schoolId;
      sendBtn.textContent = isSending ? 'Thinking…' : 'Send';
    }

    async function submitQuestion(question) {
      const trimmed = String(question || '').trim();
      if (!trimmed || !state.schoolId || state.sending) return;

      setError(null);
      appendUserMessage(trimmed);
      state.messages.push({ role: 'user', content: trimmed });
      renderSuggestions(); // hides the chips now that a real message exists
      setSending(true);

      const thinkingBubble = appendThinkingBubble();

      try {
        const conversation = state.messages.slice(0, -1); // everything before this question
        const response = await (global.Auth && typeof global.Auth.apiFetch === 'function'
          ? global.Auth.apiFetch('/api/assistant/ask', {
              method: 'POST',
              body: JSON.stringify({ schoolId: state.schoolId, conversation, question: trimmed })
            })
          : fetch(resolveApiUrl('/api/assistant/ask'), {
              method: 'POST',
              credentials: 'include',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({ schoolId: state.schoolId, conversation, question: trimmed })
            }).then(async res => ({ ok: res.ok, status: res.status, body: await res.json().catch(() => ({})) })));

        if (!response.ok || !response.body || response.body.success === false) {
          thinkingBubble.remove();
          const message = (response.body && response.body.message) || 'I couldn\'t connect to the AI assistant. Please try again.';
          setError(message);
          state.messages.pop(); // don't keep a question that got no answer in the conversation sent to the model
          return;
        }

        const answer = response.body.answer;
        state.messages.push({ role: 'assistant', content: answer });

        // Morph the same bubble in place (dots -> text) instead of
        // removing it and inserting a new one — one less layout jump.
        thinkingBubble.classList.remove('school-assistant-message-pending');
        thinkingBubble.textContent = '';
        const wasNearBottom = isNearBottom();
        revealText(thinkingBubble, answer, { onStep: () => scrollToBottom(wasNearBottom) });
      } catch (_err) {
        thinkingBubble.remove();
        setError('I couldn\'t connect to the AI assistant. Please try again.');
        state.messages.pop();
      } finally {
        setSending(false);
      }
    }

    function autoResizeTextarea() {
      textarea.style.height = 'auto';
      textarea.style.height = `${Math.min(textarea.scrollHeight, 120)}px`;
    }

    function handleSend() {
      const question = textarea.value;
      textarea.value = '';
      autoResizeTextarea();
      submitQuestion(question);
    }

    sendBtn.addEventListener('click', handleSend);
    textarea.addEventListener('input', autoResizeTextarea);
    textarea.addEventListener('keydown', event => {
      if (event.key === 'Enter' && !event.shiftKey) {
        event.preventDefault();
        handleSend();
      }
    });
    closeBtn.addEventListener('click', () => {
      root.dispatchEvent(new CustomEvent('school-assistant-close-request'));
    });

    // The one integration point with the rest of the app: called from
    // script.js's selectProfile() whenever the active school changes.
    // Always starts a fresh conversation on a real school change — an
    // NYU conversation must never silently continue once the student has
    // switched to Boston University.
    function setActiveSchool(schoolId, schoolName) {
      if (!schoolId || schoolId === state.schoolId) {
        if (schoolName) {
          state.schoolName = schoolName;
          headerSchool.textContent = `Now researching: ${schoolName}`;
        }
        return;
      }
      state.schoolId = schoolId;
      state.schoolName = schoolName || schoolId;
      state.messages = [];
      setError(null);
      headerSchool.textContent = `Now researching: ${state.schoolName}`;
      textarea.disabled = false;
      sendBtn.disabled = false;
      showEmptyState();
      renderSuggestions();
    }

    showEmptyState();

    return {
      element: root,
      setActiveSchool,
      // preventScroll: true — without it, focusing this textarea (which
      // sits at the bottom of the panel, which itself sits low on a tall
      // profile page) makes the browser auto-scroll the WHOLE page down
      // to bring it into view the instant the panel opens, not just the
      // panel's own message list — reported live as "opening the panel
      // scrolls the screen to the bottom."
      focusInput: () => textarea.focus({ preventScroll: true })
    };
  }

  global.CollegePlaybookAISchoolAssistant = { createSchoolAssistantPanel };
})(window);
