/* Workspace: holds state, composes panes. ALL scoring goes through the live Cognigy agent.
   - Guided mode: dialogue is pre-written; each agent turn is scored by Cognigy. Scores are
     PREFETCHED in the background when a scenario loads, so click-through is instant.
   - Free-type mode: you type the agent's replies; each is scored by Cognigy on send. */
const { useState: useStateW, useRef: useRefW, useEffect: useEffectW } = React;
const CFG = CXData.CXConfig;

function nowTime() { const d = new Date(); return String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0'); }

// fetch that always settles, so a hung endpoint can't freeze the composer
async function fetchWithTimeout(url, opts, ms) {
  const ctrl = new AbortController();
  const id = setTimeout(() => ctrl.abort(), ms || 35000);
  try { return await fetch(url, Object.assign({}, opts, { signal: ctrl.signal })); }
  finally { clearTimeout(id); }
}

/* One independent scoring call to the Cognigy agent. Fresh session each time => stateless. */
async function callCognigy(thread) {
  const transcript = thread.map(m => (m.from === 'in' ? 'Customer: ' : 'Agent: ') + m.t).join('\n');
  const sid = 'score-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8);
  const res = await fetchWithTimeout(CFG.COGNIGY_ENDPOINT, {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ userId: 'copilot-demo', sessionId: sid, text: 'Score this interaction. Transcript:\n' + transcript }),
  }, 35000);
  const j = await res.json();
  const raw = typeof j === 'string' ? j : (j.text || (Array.isArray(j) ? j.map(x => x.text || '').join('\n') : JSON.stringify(j)));
  return window.Scoring.normalizeScorecard(window.Scoring.parseLiveScorecard(raw));
}

/* The customer-simulator agent role-plays Daniel. Stateful: reuse one session per chat. */
async function callCustomer(message, sessionId) {
  const res = await fetchWithTimeout(CFG.CUSTOMER_ENDPOINT, {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ userId: 'agent-demo', sessionId, text: message }),
  }, 35000);
  const j = await res.json();
  const t = typeof j === 'string' ? j : (j.text || (Array.isArray(j) ? j.map(x => x.text || '').join(' ') : ''));
  return (t || '').trim() || null;
}

function Workspace() {
  const items = CXData.INTERACTIONS;
  const [activeId, setActiveId] = useStateW(items[0].id);
  const convo = items.find(i => i.id === activeId);

  const [path, setPath] = useStateW('strong');
  const [mode, setMode] = useStateW(CFG.DATA_SOURCE === 'cognigy' ? 'cognigy' : 'scripted');
  const [resolved, setResolved] = useStateW(false);
  const [busy, setBusy] = useStateW(false);
  const [error, setError] = useStateW(null);

  const scoreCache = useRefW({});   // key -> normalized scorecard
  const inflight = useRefW({});     // key -> in-flight promise (dedupe)
  const latestKey = useRefW('');    // guards against out-of-order resolves
  const custSession = useRefW('cust-init'); // stable session for the customer-sim role-play

  // guided playback
  const [step, setStep] = useStateW(0);
  const [scriptedScore, setScriptedScore] = useStateW(null);
  const script = CXData.SCENARIOS[path].path;

  // free-type
  const [liveThread, setLiveThread] = useStateW([]);
  const [liveScore, setLiveScore] = useStateW(null);
  const [text, setText] = useStateW('');

  const neutralCustLines = () => CXData.SCENARIOS['strong'].path.filter(m => m.from === 'in');

  // cached-or-fetch (dedupes concurrent requests for the same key)
  const ensureScore = (thread, key) => {
    if (key && scoreCache.current[key]) return Promise.resolve(scoreCache.current[key]);
    if (key && inflight.current[key]) return inflight.current[key];
    const p = callCognigy(thread)
      .then(card => { if (card && key) scoreCache.current[key] = card; if (key) delete inflight.current[key]; return card; })
      .catch(e => { if (key) delete inflight.current[key]; throw e; });
    if (key) inflight.current[key] = p;
    return p;
  };

  // Prefetch every agent-turn score for the active scenario, in the background.
  useEffectW(() => {
    if (mode !== 'scripted') return;
    const sc = CXData.SCENARIOS[path].path;
    sc.forEach((m, i) => { if (m.from === 'out') ensureScore(sc.slice(0, i + 1), path + ':' + (i + 1)).catch(() => {}); });
  }, [path, mode]);

  // ----- handlers -----
  const onPath = (p) => { setPath(p); setStep(0); setScriptedScore(null); setResolved(false); setError(null); setBusy(false); latestKey.current = ''; };

  const toggleMode = () => {
    setError(null); setResolved(false); setBusy(false);
    setMode(m => {
      const next = m === 'scripted' ? 'cognigy' : 'scripted';
      if (next === 'cognigy') {
        custSession.current = 'cust-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8);
        setLiveScore(null);
        setLiveThread([{ from: 'in', t: neutralCustLines()[0].t, time: nowTime() }]);
      } else { setStep(0); setScriptedScore(null); }
      return next;
    });
  };

  // guided: reveal next message; show its Cognigy score (cached instantly, else spinner)
  const onNext = () => {
    if (step >= script.length) return;
    const revealing = script[step];
    const newStep = step + 1;
    setStep(newStep);
    if (revealing.from !== 'out') return;
    const key = path + ':' + newStep;
    latestKey.current = key;
    if (scoreCache.current[key]) { setScriptedScore(scoreCache.current[key]); setBusy(false); return; }
    setBusy(true); setError(null);
    ensureScore(script.slice(0, newStep), key)
      .then(card => { if (latestKey.current !== key) return; if (card) setScriptedScore(card); else setError('Cognigy responded, but the scorecard could not be parsed.'); })
      .catch(() => { if (latestKey.current === key) setError('Could not reach Cognigy (browser CORS or network). See the README to run behind a proxy.'); })
      .finally(() => { if (latestKey.current === key) setBusy(false); });
  };
  const onReset = () => { setStep(0); setScriptedScore(null); setResolved(false); setError(null); setBusy(false); latestKey.current = ''; };

  // free-type: rep sends a reply -> customer-sim agent reacts AND coaching agent scores (in parallel)
  const onSend = async (reply) => {
    const newThread = [...liveThread, { from: 'out', t: reply, time: nowTime() }];
    setLiveThread(newThread); setText(''); setBusy(true); setError(null);
    const [custReply, card] = await Promise.all([
      callCustomer(reply, custSession.current).catch(() => null),
      callCognigy(newThread).catch(() => 'ERR'),
    ]);
    if (card === 'ERR' || card == null) setError('Could not reach the Cognigy coaching agent (browser CORS or network). See the README.');
    else setLiveScore(card);
    setBusy(false);
    if (custReply) setLiveThread(t => [...t, { from: 'in', t: custReply, time: nowTime() }]);
  };

  const isLive = mode === 'cognigy';
  const messages = isLive ? liveThread : script.slice(0, step);
  const current = isLive ? liveScore : scriptedScore;
  // Chips = the agent's dedicated, paste-ready suggestedReply field ONLY — never the coaching prose.
  const suggestions = (current && current.coaching ? current.coaching.map(c => (c.suggestedReply || '').trim()).filter(s => s.length >= 8) : []);

  return (
    <div className="app">
      <Rail />
      <div className="main">
        <TopBar path={path} onPath={onPath} mode={mode} onMode={toggleMode} live={busy} />
        <div className="work">
          <Queue items={items} activeId={activeId} onPick={(id) => { const it = items.find(i => i.id === id); if (it && it.active) setActiveId(id); }} />
          <Conversation
            convo={convo}
            messages={messages}
            mode={mode}
            started={step > 0}
            atEnd={step >= script.length}
            busy={busy}
            onNext={onNext}
            onReset={onReset}
            suggestions={suggestions}
            onSend={onSend}
            text={text}
            setText={setText}
            sending={busy}
            resolved={resolved}
            onResolve={() => setResolved(true)}
            onReopen={() => setResolved(false)}
          />
          <Coaching score={current} mode={mode} busy={busy} error={error} />
        </div>
      </div>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('root')).render(<Workspace />);
