const { useState, useEffect, useRef, useCallback } = React;

const SCREENS = { WELCOME: 'welcome', CHAT: 'chat', LOADING: 'loading', DOWNLOAD: 'download' };

async function api(path, opts = {}) {
  const method = opts.method ?? 'GET';
  const isPost = method !== 'GET';
  const body = isPost
    ? (typeof opts.body === 'string' ? opts.body : JSON.stringify(opts.body ?? {}))
    : undefined;
  const res = await fetch(path, {
    method,
    credentials: 'include',
    headers: isPost ? { 'Content-Type': 'application/json' } : {},
    body,
  });
  if (!res.ok) {
    let detail = '';
    try { detail = (await res.json()).error ?? ''; } catch (_) {}
    throw new Error(`${path} ${res.status} ${detail}`);
  }
  return res.json();
}

function Typewriter({ text, delay = 0, speed = 22, onProgress, onDone, showCursor = false }) {
  const [shown, setShown] = useState('');
  const [done, setDone] = useState(false);
  const onProgressRef = useRef(onProgress);
  const onDoneRef = useRef(onDone);
  useEffect(() => { onProgressRef.current = onProgress; });
  useEffect(() => { onDoneRef.current = onDone; });

  useEffect(() => {
    if (!text) { setShown(''); setDone(false); return; }
    let stopped = false;
    let nextTimer;
    const startTimer = setTimeout(() => {
      let i = 0;
      const tick = () => {
        if (stopped) return;
        i++;
        setShown(text.slice(0, i));
        if (onProgressRef.current) onProgressRef.current();
        if (i >= text.length) {
          setDone(true);
          if (onDoneRef.current) onDoneRef.current();
          return;
        }
        nextTimer = setTimeout(tick, speed);
      };
      tick();
    }, delay);
    return () => {
      stopped = true;
      clearTimeout(startTimer);
      if (nextTimer) clearTimeout(nextTimer);
    };
  }, [text, delay, speed]);

  return (
    <>
      {shown}
      {showCursor && !done && <span className="tw-cursor" aria-hidden="true">▍</span>}
    </>
  );
}

function Welcome({ onStart, starting, error }) {
  // Sequence: 2s wait → title line 1 → line 2 → paragraph → button appears.
  const [phase, setPhase] = useState(0);
  // phase: 0 hidden, 1 line1, 2 line2, 3 paragraph, 4 button

  useEffect(() => {
    const t = setTimeout(() => setPhase(1), 2000);
    return () => clearTimeout(t);
  }, []);

  const PARAGRAPH =
    'Antes de tu primera sesión, vamos a platicar unos minutos. La meta es ' +
    'que Sebastián llegue a la sesión 1 con cosas concretas para construir ' +
    'contigo, no a empezar desde cero. Voy a ir adaptando las preguntas a ' +
    'lo que me cuentes.';

  return (
    <div className="welcome">
      <div className="welcome-inner">
        <div className="tag">Onboarding · Sesión 1</div>
        <h1>
          {phase >= 1 && (
            <Typewriter
              text="Hola Allan,"
              speed={55}
              showCursor
              onDone={() => setPhase(2)}
            />
          )}
          <br/>
          <span>
            {phase >= 2 && (
              <Typewriter
                text="soy el asistente de Sebastián."
                speed={45}
                showCursor
                onDone={() => setPhase(3)}
              />
            )}
          </span>
        </h1>
        <p>
          {phase >= 3 && (
            <Typewriter
              text={PARAGRAPH}
              speed={18}
              showCursor
              onDone={() => setPhase(4)}
            />
          )}
        </p>
        <div className={`welcome-cta ${phase >= 4 ? 'visible' : ''}`}>
          <button className="btn-primary" onClick={onStart} disabled={starting || phase < 4}>
            {starting ? 'Cargando...' : 'Empezar →'}
          </button>
          {error && <div className="inline-error">{error}</div>}
        </div>
      </div>
    </div>
  );
}

function Chat({ initialTurns, onFinish }) {
  const [turns, setTurns] = useState(initialTurns ?? []);
  const [input, setInput] = useState('');
  const [sending, setSending] = useState(false);
  const [error, setError] = useState(null);
  const scrollRef = useRef(null);
  const textareaRef = useRef(null);
  const finishedRef = useRef(false);
  // Synchronous guard: setState is batched/async so a fast double-click or
  // Enter+click can fire submit() twice before `sending` updates. The ref
  // updates immediately, so the second call returns early.
  const sendingRef = useRef(false);

  const scrollToBottom = useCallback(() => {
    const el = scrollRef.current;
    if (el) el.scrollTop = el.scrollHeight;
    // Fallback for breakpoints/layouts where the document is the scroll root
    // (mobile, when nested flex scroll isn't engaged yet).
    if (typeof window !== 'undefined') {
      window.scrollTo({ top: document.documentElement.scrollHeight, behavior: 'auto' });
    }
  }, []);

  useEffect(() => { scrollToBottom(); }, [turns, sending, scrollToBottom]);

  useEffect(() => {
    if (textareaRef.current) textareaRef.current.focus();
  }, []);

  const markTurnDone = useCallback((idx) => {
    setTurns((t) => t.map((turn, i) => i === idx ? { ...turn, isNew: false } : turn));
    // Auto-finish if backend already signaled finishConversation on this turn.
    if (finishedRef.current) {
      setTimeout(() => onFinish(), 400);
    }
  }, [onFinish]);

  const submit = useCallback(async () => {
    const msg = input.trim();
    if (!msg || sendingRef.current) return;
    sendingRef.current = true;
    setSending(true);
    setError(null);
    setInput('');
    setTurns((t) => [...t, { role: 'user', content: msg, isNew: false }]);
    try {
      const r = await api('/api/chat', {
        method: 'POST',
        body: JSON.stringify({ message: msg }),
      });
      if (r.finishConversation) finishedRef.current = true;
      setTurns((t) => [...t, { role: 'assistant', content: r.assistantMessage, isNew: true }]);
      // onFinish gets fired by markTurnDone once the typewriter finishes,
      // so the user gets to read the closing line before the loading screen.
    } catch (e) {
      setError('Algo se cayó. Volvé a intentar en un momento.');
      setTurns((t) => t.slice(0, -1));
      setInput(msg);
    } finally {
      sendingRef.current = false;
      setSending(false);
      // setInput('') resets the value but autosize only fires on user input,
      // so the inline height stays at whatever the last keystroke set it to
      // (up to 200px). Reset it back to a single row.
      setTimeout(() => {
        const el = textareaRef.current;
        if (el) {
          el.style.height = 'auto';
          el.focus();
        }
      }, 50);
    }
  }, [input]);

  const onKeyDown = (e) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      submit();
    }
  };

  const autosize = (e) => {
    setInput(e.target.value);
    const el = e.target;
    el.style.height = 'auto';
    el.style.height = Math.min(el.scrollHeight, 200) + 'px';
  };

  return (
    <div className="chat">
      <div className="chat-header">
        <div className="dot"></div>
        <div className="title">Asistente de Sebastián · onboarding sesión 1</div>
      </div>

      <div className="chat-scroll" ref={scrollRef}>
        {turns.map((t, i) => (
          <div key={i} className={`msg ${t.role}`}>
            <div className="who">{t.role === 'user' ? 'Tú' : 'Asistente'}</div>
            <div className="bubble">
              {t.role === 'assistant' && t.isNew ? (
                <Typewriter
                  text={t.content}
                  speed={18}
                  showCursor
                  onProgress={scrollToBottom}
                  onDone={() => markTurnDone(i)}
                />
              ) : (
                t.content
              )}
            </div>
          </div>
        ))}
        {sending && (
          <div className="msg assistant">
            <div className="who">Asistente</div>
            <div className="bubble">
              <div className="typing"><span/><span/><span/></div>
            </div>
          </div>
        )}
        {error && <div className="inline-error">{error}</div>}
      </div>

      <div className="composer">
        <div className="composer-inner">
          <textarea
            ref={textareaRef}
            value={input}
            onChange={autosize}
            onKeyDown={onKeyDown}
            rows={1}
            placeholder="Escribe tu respuesta..."
            disabled={sending}
          />
          <button
            className="send-btn"
            onClick={submit}
            disabled={sending || !input.trim()}
            aria-label="Enviar"
          >
            →
          </button>
        </div>
      </div>
    </div>
  );
}

function Loading({ onDone, onError }) {
  const [substeps, setSubsteps] = useState([
    'Procesando lo que me contaste',
    'Aterrizando todo a tu contexto',
    'Generando tu guía personalizada',
    'Empacando tu PDF',
  ]);
  const [step, setStep] = useState(0);
  const [pdfUrl, setPdfUrl] = useState(null);
  const errorRef = useRef(false);

  useEffect(() => {
    let cancelled = false;
    let taskId = null;

    async function kickoff() {
      try {
        const r = await api('/api/finalize', { method: 'POST' });
        taskId = r.taskId;
        poll();
      } catch (e) {
        if (!cancelled) onError('No se pudo iniciar la generación.');
      }
    }

    async function poll() {
      if (cancelled || !taskId) return;
      try {
        const r = await api(`/api/finalize/status/${taskId}`);
        if (r.substeps && r.substeps.length === 4) {
          setSubsteps(r.substeps);
        }
        setStep(r.step);
        if (r.status === 'done' && r.pdfUrl) {
          setPdfUrl(r.pdfUrl);
          setTimeout(() => onDone(r.pdfUrl), 600);
          return;
        }
        if (r.status === 'error' && !errorRef.current) {
          errorRef.current = true;
          onError(r.error ?? 'Algo se cayó al generar.');
          return;
        }
      } catch (_) {
        // transient — retry
      }
      setTimeout(poll, 1100);
    }

    kickoff();
    return () => { cancelled = true; };
  }, [onDone, onError]);

  return (
    <div className="loading">
      <div className="loading-inner">
        <div className="tag">Generando tu guía</div>
        <h2>Dame unos segundos, Allan.</h2>
        <p>Estoy aterrizando todo lo que me contaste a tu guía personalizada para la sesión 1.</p>

        {substeps.map((label, i) => {
          const num = String(i + 1).padStart(2, '0');
          const active = step === i;
          const done = step > i;
          return (
            <div
              key={i}
              className={`substep ${active ? 'active' : ''} ${done ? 'done' : ''}`}
            >
              <div className="substep-num">{num}</div>
              <div className="substep-text">{label}</div>
              <div className="substep-icon">{done ? '✓' : active ? '·' : ''}</div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

function Download({ pdfUrl }) {
  return (
    <div className="download">
      <div className="download-inner">
        <div className="tag">Tu guía está lista</div>
        <h2>Listo, Allan.<br/><span>Tu guía está lista.</span></h2>
        <p>
          Léela tú y tu pareja antes de la sesión 1. Sebastián ya tiene también
          toda la lectura previa que necesita.
        </p>
        <a className="btn-primary" href={pdfUrl} download="Guia-Sesion-1-Allan-Guerrero.pdf">
          Descargar mi guía de preparación →
        </a>
        <div className="footer-note">Nos vemos en la sesión 1.</div>
      </div>
    </div>
  );
}

function App() {
  const [screen, setScreen] = useState(SCREENS.WELCOME);
  const [turns, setTurns] = useState([]);
  const [pdfUrl, setPdfUrl] = useState(null);
  const [starting, setStarting] = useState(false);
  const [startError, setStartError] = useState(null);

  // On mount: try to resume (cookie-based). Mark animate-on-mount only for
  // first-time fetches (resumed: false). Resumed turns appear instantly.
  useEffect(() => {
    (async () => {
      try {
        const r = await api('/api/start', { method: 'POST' });
        const tagged = (r.turns ?? []).map((t) => ({
          ...t,
          isNew: !r.resumed && t.role === 'assistant',
        }));
        if (r.resumed) {
          if (r.finished) {
            setTurns(tagged);
            setScreen(SCREENS.LOADING);
          } else if (tagged.length > 0) {
            setTurns(tagged);
            setScreen(SCREENS.CHAT);
          }
        } else if (tagged.length > 0) {
          // First load created session + initial assistant message.
          // Wait for explicit "Empezar" click.
          setTurns(tagged);
        }
      } catch (e) {
        // ignore — user will click Empezar
      }
    })();
  }, []);

  const onStart = useCallback(async () => {
    setStarting(true);
    setStartError(null);
    try {
      if (turns.length === 0) {
        const r = await api('/api/start', { method: 'POST' });
        const tagged = (r.turns ?? []).map((t) => ({
          ...t,
          isNew: t.role === 'assistant',
        }));
        setTurns(tagged);
      }
      setScreen(SCREENS.CHAT);
    } catch (e) {
      setStartError('No pude iniciar. Refrescá la página, por favor.');
    } finally {
      setStarting(false);
    }
  }, [turns]);

  return (
    <>
      {screen === SCREENS.WELCOME && (
        <Welcome onStart={onStart} starting={starting} error={startError} />
      )}
      {screen === SCREENS.CHAT && (
        <Chat initialTurns={turns} onFinish={() => setScreen(SCREENS.LOADING)} />
      )}
      {screen === SCREENS.LOADING && (
        <Loading
          onDone={(url) => { setPdfUrl(url); setScreen(SCREENS.DOWNLOAD); }}
          onError={(msg) => setStartError(msg)}
        />
      )}
      {screen === SCREENS.DOWNLOAD && <Download pdfUrl={pdfUrl} />}
    </>
  );
}

ReactDOM.createRoot(document.getElementById('app')).render(<App />);
