/* OfficeTrack — Support Chat — main surface.

   Identify by phone (cold entry), then a natural free-text conversation with
   the assistant: it troubleshoots the customer's internet problem and, if it
   can't fix it remotely, books a technician visit. No canned chips, no
   decision-tree — just chat. The customer can send a photo of the modem; the
   model reads it. The only tappable UI is the final booking confirmation card.

   The browser renders what the server returns; all logic lives server-side. */

const { useState, useEffect, useRef } = React;

const BRAND_PRESETS = {
  navy:     { primary: '#15296B', headerBg: '#15296B', name: 'OfficeTrack' },
  cobalt:   { primary: '#1A4FE8', headerBg: '#1A4FE8', name: 'OfficeTrack' },
  lavender: { primary: '#7468C6', headerBg: '#9F95E3', name: 'OfficeTrack' },
};

async function api(path, body) {
  try {
    const r = await fetch(path, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body || {}),
    });
    return await r.json();
  } catch (e) {
    return { ok: false, error: 'network' };
  }
}

// Downscale before upload (max 1280px, JPEG ~0.8). Canvas re-encode strips EXIF.
function downscaleImage(file, maxEdge = 1280, quality = 0.8) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    const url = URL.createObjectURL(file);
    img.onload = () => {
      URL.revokeObjectURL(url);
      let { width, height } = img;
      const scale = Math.min(1, maxEdge / Math.max(width, height));
      width = Math.round(width * scale);
      height = Math.round(height * scale);
      const canvas = document.createElement('canvas');
      canvas.width = width; canvas.height = height;
      canvas.getContext('2d').drawImage(img, 0, 0, width, height);
      resolve(canvas.toDataURL('image/jpeg', quality));
    };
    img.onerror = () => { URL.revokeObjectURL(url); reject(new Error('img')); };
    img.src = url;
  });
}

function ChatSurface({ lang, setLang, tweaks }) {
  const t = window.I18N[lang] || window.I18N.es;
  const brand = BRAND_PRESETS[(tweaks && tweaks.brandPreset)] || BRAND_PRESETS.navy;

  // Always start at the identification gate so the welcome ALWAYS asks for
  // the customer's data (phone / account / email) — consistent on every load
  // and reset. (We don't auto-restore a stored session into a "verified"
  // greeting; re-identifying is one quick step and avoids the inconsistent
  // "what's your problem?" message appearing without asking who they are.)
  const [authState, setAuthState] = useState('pending');
  const sessionTokenRef = useRef(null);

  const [messages, setMessages] = useState(
    [{ kind: 'ai', text: t.onboardingWelcome, _welcome: true }]);

  const [identifiedPoi, setIdentifiedPoi] = useState(null);
  const [pendingMultiPoi, setPendingMultiPoi] = useState(null);
  const [composer, setComposer] = useState('');
  const [pendingImage, setPendingImage] = useState(null);
  const [busy, setBusy] = useState(false);
  const [geo, setGeo] = useState(null);            // {lat,lng,address} confirmed on map
  const [showMap, setShowMap] = useState(false);
  const [listening, setListening] = useState(false);

  const convRef = useRef(null);
  const scrollRef = useRef(null);
  const recRef = useRef(null);
  const voiceSupported = typeof window !== 'undefined'
    && (window.SpeechRecognition || window.webkitSpeechRecognition);

  const push = (msg) => setMessages(m => [...m, msg]);
  const dropTyping = () => setMessages(m => m.filter(x => x.kind !== 'typing'));
  const ai = (text) => push({ kind: 'ai', text });

  // re-render the welcome message if the language toggle changes pre-chat
  useEffect(() => {
    setMessages(ms => ms.map(m =>
      m._welcome ? { ...m, text: t.onboardingWelcome } : m));
  }, [lang]);

  // best-effort audit flush on tab close
  useEffect(() => {
    function onUnload() {
      try {
        const conv = convRef.current, tok = sessionTokenRef.current;
        if (!conv || !tok) return;
        const payload = JSON.stringify({
          conversationId: conv, sessionToken: tok, reason: 'unload' });
        if (navigator.sendBeacon) {
          navigator.sendBeacon('/api/session/close',
            new Blob([payload], { type: 'application/json' }));
        }
      } catch (e) {}
    }
    window.addEventListener('beforeunload', onUnload);
    return () => window.removeEventListener('beforeunload', onUnload);
  }, []);

  useEffect(() => {
    const el = scrollRef.current;
    if (!el) return;
    requestAnimationFrame(() =>
      el.scrollTo({ top: el.scrollHeight, behavior: 'smooth' }));
  }, [messages]);

  /* ─── identification gate ─── */
  async function handleIdentifyAttempt(text) {
    const q = String(text || '').trim();
    // Accept any identifier: phone, email, account/customer number, code.
    if (q.replace(/\s/g, '').length < 4) {
      setMessages(m => [...m, { kind: 'user', text: q }]);
      ai(t.onboardingInvalidId);
      return;
    }
    setBusy(true);
    setMessages(m => [...m, { kind: 'user', text: q }, { kind: 'typing' }]);
    const d = await api('/api/identify', { query: q });
    dropTyping();
    if (d && d.error === 'network') {
      ai(t.netError);
      setBusy(false);
      return;
    }
    if (d && d.ok && d.pois && d.pois.length === 1) {
      finishIdentify(d.pois[0], d.sessionToken);
      setBusy(false);
      return;
    }
    if (d && d.ok && d.pois && d.pois.length > 1) {
      setPendingMultiPoi(d.pois);
      ai(t.onboardingMultiPoi);
      push({ kind: 'buttons',
             buttons: d.pois.map(p => `${p.name} — ${p.address || p.city || ''}`) });
      setBusy(false);
      return;
    }
    ai(d && d.error === 'no_match' ? t.onboardingNoMatch : t.aiError);
    setBusy(false);
  }

  function finishIdentify(poi, sessionToken) {
    sessionTokenRef.current = sessionToken;
    try {
      window.sessionStorage.setItem('otSessionToken', sessionToken);
      window.sessionStorage.setItem('otPoiCode', poi.code);
    } catch (e) {}
    setIdentifiedPoi(poi);
    setAuthState('verified');
    ai(`${t.onboardingGreetingHi} ${poi.name || ''}! ${t.onboardingGreetingAsk}`.trim());
  }

  async function handleMultiPoiPick(label) {
    if (!pendingMultiPoi) return;
    const poi = (pendingMultiPoi || []).find(p => label.startsWith(p.name));
    if (!poi) return;
    setBusy(true);
    setMessages(m => [...m, { kind: 'user', text: poi.name }, { kind: 'typing' }]);
    const d = await api('/api/identify/confirm', { poiCode: poi.code });
    dropTyping();
    if (d && d.ok && d.sessionToken) {
      setPendingMultiPoi(null);
      finishIdentify(d.poi || poi, d.sessionToken);
    } else {
      ai(t.aiError);
    }
    setBusy(false);
  }

  /* ─── main pipeline ─── */
  async function handleUserText(text, displayText, image) {
    if (busy) return;
    const v = String(text || '').trim();
    if (!v && !image) return;

    if (authState === 'pending') {
      if (pendingMultiPoi) return handleMultiPoiPick(v);
      return handleIdentifyAttempt(v);
    }

    setBusy(true);
    const userMsg = image
      ? { kind: 'image', src: image, caption: displayText || (v || null) }
      : { kind: 'user', text: displayText || v };
    setMessages(m => [...m, userMsg, { kind: 'typing' }]);

    const poiCodeStored = (() => {
      try { return window.sessionStorage.getItem('otPoiCode') || null; }
      catch (e) { return null; }
    })();

    const d = await api('/api/turn', {
      poiCode: (identifiedPoi && identifiedPoi.code) || poiCodeStored,
      userPrompt: v,
      image: image || undefined,
      geo: geo || undefined,
      conversationId: convRef.current,
      lang,
      sessionToken: sessionTokenRef.current,
    });
    dropTyping();

    if (d && d.error === 'network') {
      ai(t.netError);
      setBusy(false);
      return;
    }
    if (d && d.error === 'unauthorized') {
      try { window.sessionStorage.removeItem('otSessionToken'); } catch (e) {}
      sessionTokenRef.current = null;
      setAuthState('pending');
      ai(t.onboardingWelcome);
      setBusy(false);
      return;
    }
    if (!d || d.ok === false) {
      ai(d && d.detail ? `${t.aiError} (${d.detail})` : t.aiError);
      setBusy(false);
      return;
    }

    if (d.conversationId) convRef.current = d.conversationId;
    const clean = window.cleanAiText ? window.cleanAiText(d.message) : (d.message || '');
    const hasSlots = Array.isArray(d.slots) && d.slots.length;
    if (clean) ai(clean);
    else if (!d.pendingAction && !hasSlots) ai(t.aiClarify);

    if (hasSlots) push({ kind: 'slots', slots: d.slots });
    if (d.pendingAction && d.pendingAction.summary) {
      push({ kind: 'pending-action', name: d.pendingAction.name,
             summary: d.pendingAction.summary });
    }
    if (d.auditLogged) push({ kind: 'audit-note', text: t.auditLogged });
    setBusy(false);
  }

  function pickSlot(s) {
    const label = [s.label, s.time].filter(Boolean).join(', ');
    const wire = (t.slotPick || 'Quiero ese horario: ') + label;
    handleUserText(wire, label);
  }

  function handlePendingDecision(decision) {
    const isConfirm = decision === 'confirm';
    handleUserText(
      isConfirm ? '__CONFIRM_PENDING__' : '__CANCEL_PENDING__',
      isConfirm ? t.confirmActionLabel : t.cancelActionLabel);
  }

  /* ─── composer: text + photo ─── */
  async function handlePickImage(file) {
    if (!file) return;
    try { setPendingImage(await downscaleImage(file)); }
    catch (e) { ai(t.aiError); }
  }

  function handleSend() {
    const v = composer.trim();
    const img = pendingImage;
    if (!v && !img) return;
    setComposer('');
    setPendingImage(null);
    handleUserText(v, undefined, img || undefined);
  }

  /* ─── voice: Web Speech API → composer ─── */
  function toggleVoice() {
    if (listening) {
      try { recRef.current && recRef.current.stop(); } catch (e) {}
      return;
    }
    const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
    if (!SR) return;
    const rec = new SR();
    rec.lang = { es: 'es-AR', en: 'en-US', pt: 'pt-BR', he: 'he-IL' }[lang] || 'es-AR';
    rec.interimResults = true;
    rec.continuous = false;
    rec.onresult = (e) => {
      let txt = '';
      for (let i = 0; i < e.results.length; i++) txt += e.results[i][0].transcript;
      setComposer(txt);
    };
    rec.onerror = () => setListening(false);
    rec.onend = () => setListening(false);
    recRef.current = rec;
    setListening(true);
    try { rec.start(); } catch (e) { setListening(false); }
  }

  function reset() {
    try {
      if (convRef.current && sessionTokenRef.current) {
        api('/api/session/close', {
          conversationId: convRef.current,
          sessionToken: sessionTokenRef.current, reason: 'reset' });
      }
    } catch (e) {}
    setBusy(false);
    convRef.current = null;
    setComposer('');
    setPendingImage(null);
    setPendingMultiPoi(null);
    setIdentifiedPoi(null);
    setGeo(null); setShowMap(false);
    try { recRef.current && recRef.current.stop(); } catch (e) {}
    setListening(false);
    try {
      window.sessionStorage.removeItem('otSessionToken');
      window.sessionStorage.removeItem('otPoiCode');
    } catch (e) {}
    sessionTokenRef.current = null;
    setAuthState('pending');
    setMessages([{ kind: 'ai', text: t.onboardingWelcome, _welcome: true }]);
  }

  /* ─── render ─── */
  function renderMessage(m, i) {
    const live = i === messages.length - 1 && !busy;
    if (m.kind === 'user')
      return <ChatMsg key={i} from="me" brandColor={brand.primary}>{m.text}</ChatMsg>;
    if (m.kind === 'ai')
      return <ChatMsg key={i} from="ai" brandColor={brand.primary}><RichText text={m.text}/></ChatMsg>;
    if (m.kind === 'image')
      return (
        <ChatMsg key={i} from="me" brandColor={brand.primary}>
          <img src={m.src} alt="" style={{ maxWidth: '200px', borderRadius: 10, display: 'block' }}/>
          {m.caption && <div style={{ marginTop: 4, fontSize: 13 }}>{m.caption}</div>}
        </ChatMsg>
      );
    if (m.kind === 'typing')
      return <TypingDots key={i} brandColor={brand.primary}/>;
    if (m.kind === 'audit-note')
      return (
        <div key={i} style={{ alignSelf: 'center', margin: '4px 0',
          padding: '4px 10px', borderRadius: 999,
          background: 'rgba(21,41,107,0.10)', color: brand.primary,
          fontSize: 12, fontWeight: 500 }}>{m.text}</div>
      );
    if (m.kind === 'slots')
      return (
        <div key={i} className="nc-card-wrap">
          <SlotChips slots={m.slots} disabled={!live}
                     onPick={(s) => live && pickSlot(s)}/>
        </div>
      );
    if (m.kind === 'buttons') // only used by the multi-account picker
      return (
        <div key={i} className="nc-card-wrap">
          <SuggestionChips
            items={m.buttons.map((b, k) => ({ id: 'b' + k, label: b }))}
            brandColor={brand.primary}
            onPick={(it) => live && handleUserText(it.label)}/>
        </div>
      );
    if (m.kind === 'pending-action') {
      const isCancel = m.name === 'cancelAppointment';
      const accent = isCancel ? '#C03A2B' : brand.primary;
      return (
        <div key={i} className="nc-pending-card" style={{
          margin: '8px 0 6px', padding: '14px 16px', borderRadius: 14,
          border: `2px solid ${accent}`, background: 'rgba(255,255,255,0.9)',
          boxShadow: '0 2px 8px rgba(0,0,0,0.06)', alignSelf: 'stretch' }}>
          <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: 0.4,
            textTransform: 'uppercase', color: accent, marginBottom: 6 }}>
            {isCancel ? t.confirmCancelTitle : t.confirmRescheduleTitle}
          </div>
          <div style={{ fontSize: 15, color: '#1F224A', lineHeight: 1.4,
            fontWeight: 500, marginBottom: 12 }}>{m.summary}</div>
          {!isCancel && (
            <div style={{ marginBottom: 12 }}>
              {geo ? (
                <div style={{ fontSize: 13, color: NCColors.success, fontWeight: 600 }}>
                  {t.locationConfirmed}
                  {geo.address && (
                    <div style={{ fontSize: 11.5, color: '#8A8DA3', marginTop: 2,
                                  fontWeight: 400 }}>{geo.address}</div>
                  )}
                </div>
              ) : (
                <button onClick={() => live && setShowMap(true)}
                  disabled={!live}
                  style={{ display: 'inline-flex', alignItems: 'center', gap: 6,
                    padding: '7px 12px 7px 8px', borderRadius: 9,
                    border: `1.5px solid ${accent}33`, background: `${accent}0D`,
                    color: accent, fontSize: 13, fontWeight: 600,
                    cursor: live ? 'pointer' : 'default' }}>
                  <span style={{ display: 'flex' }}>{window.OTIcons.pin}</span>
                  {t.confirmLocationCta.replace('📍 ', '')}
                </button>
              )}
            </div>
          )}
          <div style={{ display: 'flex', gap: 10 }}>
            <button disabled={!live} onClick={() => live && handlePendingDecision('confirm')}
              style={{ flex: 1, padding: '12px 14px', borderRadius: 10, border: 'none',
                cursor: live ? 'pointer' : 'default', background: accent, color: '#fff',
                fontSize: 14, fontWeight: 600, opacity: live ? 1 : 0.5 }}>
              {t.confirmActionLabel}
            </button>
            <button disabled={!live} onClick={() => live && handlePendingDecision('cancel')}
              style={{ flex: 1, padding: '12px 14px', borderRadius: 10,
                border: '1.5px solid #C9CBD9', cursor: live ? 'pointer' : 'default',
                background: '#fff', color: '#4A4D6E', fontSize: 14, fontWeight: 500,
                opacity: live ? 1 : 0.5 }}>
              {t.cancelActionLabel}
            </button>
          </div>
        </div>
      );
    }
    return null;
  }

  return (
    <div className="nc-screen" dir={t.dir} data-lang={lang}>
      <ChatHeader t={t} lang={lang} onLang={setLang}
                  brandColor={brand.headerBg} brandName={brand.name}
                  onReset={reset}/>
      <div className="nc-chat-scroll" ref={scrollRef}>
        <div className="nc-chat-inner">
          {messages.map(renderMessage)}
        </div>
        <div className="nc-powered">{t.poweredBy}</div>
      </div>

      <Composer t={composerT(t, authState)} value={composer} onChange={setComposer}
                onSend={handleSend} brandColor={brand.primary}
                disabled={busy}
                allowImage={authState === 'verified'}
                pendingImage={pendingImage}
                onPickImage={handlePickImage}
                onClearImage={() => setPendingImage(null)}
                voiceSupported={!!voiceSupported}
                listening={listening}
                onVoice={toggleVoice}/>

      <LocationSheet t={t} open={showMap} brandColor={brand.primary}
                     onConfirm={(g) => { setGeo(g); setShowMap(false); }}
                     onCancel={() => setShowMap(false)}/>
    </div>
  );
}

function composerT(t, authState) {
  if (authState === 'pending')
    return { ...t, chatPlaceholder: t.onboardingPlaceholder };
  return t;
}

Object.assign(window, { ChatSurface, BRAND_PRESETS });
