/* global React, ReactDOM, NusantaraStoryClient */
(function (root) {
  const {useState, useRef, useEffect, useLayoutEffect, useCallback} = React;

  function StoryDialogueView({step, busy, error, progressLabel, onAdvance, onCancel, cancelLabel = ''}) {
    if (!step || step.kind !== 'dialogue') return null;
    return <main className="nusantara-story-stage bb-founding-stage bb-founding-dialogue-stage" data-story-step={step.id}>
      <section className="bb-founding-dialogue nusantara-story-dialogue" role="dialog" aria-modal="true" aria-label="Nusantara Pilot channel">
        <figure className="bb-founding-dialogue-portrait"><img src={step.speaker.portraitUrl} alt={step.speaker.name + ' Pilot portrait'}/></figure>
        <div className="bb-founding-dialogue-copy">
          <div className="bb-founding-dialogue-kicker">PILOT CHANNEL // NUSANTARA CAMPAIGN <span>{progressLabel}</span></div>
          <div className="bb-founding-dialogue-speaker"><b>{step.speaker.name}</b><span>{step.speaker.tag}</span></div>
          <p className="bb-founding-dialogue-line">{step.text}</p>
          {error && <div className="nusantara-story-error" role="alert"><b>SYNC INTERRUPTED</b><span>{error}</span></div>}
          <div className="bb-founding-dialogue-actions">
            {cancelLabel && <button type="button" className="nusantara-story-secondary" disabled={Boolean(busy)} onClick={onCancel}>{cancelLabel}</button>}
            <button type="button" disabled={Boolean(busy)} onClick={onAdvance}>{busy ? 'SYNCING…' : error ? 'RETRY' : 'CONTINUE'}</button>
          </div>
        </div>
      </section>
    </main>;
  }

  function StoryDialogue(props) {
    const rootRef = useRef(null);
    const {onAdvance, onCancel, busy} = props;
    useLayoutEffect(() => {
      const previous = document.activeElement;
      const previousRootOverflow = document.documentElement.style.overflow;
      const previousBodyOverflow = document.body.style.overflow;
      const shell = rootRef.current;
      document.documentElement.style.overflow = 'hidden';
      document.body.style.overflow = 'hidden';
      const button = shell?.querySelector('button:not([disabled])');
      button?.focus({preventScroll: true});
      const keyDown = event => {
        if (event.key === 'Escape' && onCancel && !busy) { event.preventDefault(); onCancel(); return; }
        if ((event.key === 'Enter' || event.key === 'ArrowRight') && event.target?.tagName !== 'BUTTON' && !busy) {
          event.preventDefault(); onAdvance(); return;
        }
        if (event.key !== 'Tab' || !shell) return;
        const controls = [...shell.querySelectorAll('button:not([disabled]), [href], [tabindex]:not([tabindex="-1"])')];
        const first = controls[0], last = controls[controls.length - 1];
        if (!first) return;
        if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); }
        else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); }
      };
      document.addEventListener('keydown', keyDown);
      return () => {
        document.removeEventListener('keydown', keyDown);
        document.documentElement.style.overflow = previousRootOverflow;
        document.body.style.overflow = previousBodyOverflow;
        if (previous?.isConnected) previous.focus({preventScroll: true});
      };
    }, [props.step?.id, busy, onAdvance, onCancel]);
    return <div ref={rootRef} className="nusantara-story-dialogue-root"><StoryDialogueView {...props}/></div>;
  }

  function StoryStatus({title, copy, error, onRetry, onCancel, cancelLabel}) {
    return <main className="nusantara-story-stage bb-founding-stage"><section className="bb-founding-panel" role={error ? 'alert' : 'status'}>
      <div className="bb-founding-kicker">NUSANTARA CAMPAIGN // STORY LINK</div>
      <div className="bb-founding-title">{title}</div><p>{copy}</p>
      <div className="nusantara-story-status-actions">
        {onRetry && <button type="button" onClick={onRetry}>RETRY</button>}
        {onCancel && <button type="button" className="nusantara-story-secondary" onClick={onCancel}>{cancelLabel || 'RETURN'}</button>}
      </div>
    </section></main>;
  }

  const prebattleNarratives = {'solo/mini-1':'laweyan-narrative-v1','solo/mini-2':'banjarsari-narrative-v1','solo/boss':'pasar-kliwon-narrative-v1'};
  function storyContext({organizationId, mode, attemptId = '', encounterId = 'solo/mini-1', expectedPilot = null}) {
    const pilotId = String(expectedPilot?.id || '').trim().toLowerCase();
    const ownedAttempt = String(attemptId || '').trim().toLowerCase();
    const ownedEncounter = String(encounterId || 'solo/mini-1').trim().toLowerCase();
    return {
      organizationId: String(organizationId || ''),
      mode,
      attemptId: ownedAttempt,
      encounterId: ownedEncounter,
      expectedPilot,
      key: [String(organizationId || ''), mode, mode === 'result' ? ownedAttempt : ownedEncounter + ':' + pilotId].join(':'),
    };
  }

  function sequenceMatchesStoryContext(sequence, context) {
    if (!sequence || sequence.organizationId !== context.organizationId) return false;
    if (context.mode === 'result') return sequence.attemptId === context.attemptId && sequence.sequenceId?.endsWith(':' + context.attemptId);
    const pilotId = String(context.expectedPilot?.id || '').trim().toLowerCase();
    const names = [context.expectedPilot?.callsign, context.expectedPilot?.name].map(value => String(value || '').trim().toUpperCase()).filter(Boolean);
    const playerStep = sequence.steps?.find(step => step.id === 'pilot-response');
    const narrative = prebattleNarratives[context.encounterId];
    const encounterPrefix = context.encounterId === 'solo/mini-1' ? '' : encodeURIComponent(context.encounterId) + ':';
    return sequence.attemptId === null && Boolean(narrative) && Boolean(pilotId)
      && sequence.sequenceId?.startsWith('nusantara-story:' + narrative + ':prebattle:' + encounterPrefix)
      && sequence.sequenceId?.endsWith(':' + encodeURIComponent(pilotId))
      && Boolean(playerStep) && names.includes(String(playerStep.speaker?.name || '').trim().toUpperCase());
  }

  function projectStorySnapshot(snapshot, {organizationId, mode, attemptId = '', encounterId = 'solo/mini-1', expectedPilot = null, enabled = true}) {
    const context = storyContext({organizationId, mode, attemptId, encounterId, expectedPilot});
    const current = snapshot?.contextKey === context.key && snapshot.identity === context.organizationId
      && (!snapshot.sequence || sequenceMatchesStoryContext(snapshot.sequence, context));
    if (current) return snapshot;
    return {contextKey: context.key, identity: context.organizationId, status: enabled && context.organizationId ? 'loading' : 'idle', sequence: null, busy: false, error: ''};
  }

  function useStorySequence({request, organizationId, mode, attemptId = '', encounterId = 'solo/mini-1', expectedPilot = null, enabled = true, onComplete}) {
    const onCompleteRef = useRef(onComplete);
    onCompleteRef.current = onComplete;
    const [snapshot, setSnapshot] = useState({contextKey: '', identity: '', status: 'idle', sequence: null, busy: false, error: ''});
    const clientRef = useRef(null);
    const requestRef = useRef(request);
    const clientContextKeyRef = useRef('');
    const clientGenerationRef = useRef(0);
    const expectedPilotKey = [expectedPilot?.id, expectedPilot?.callsign, expectedPilot?.name].join(':');
    const context = storyContext({organizationId, mode, attemptId, encounterId, expectedPilot});
    const renderedContextRef = useRef(context);
    renderedContextRef.current = context;
    if (!clientRef.current || requestRef.current !== request) {
      requestRef.current = request;
      const generation = ++clientGenerationRef.current;
      clientRef.current = NusantaraStoryClient.createSequenceClient({request, onChange: next => {
        if (generation === clientGenerationRef.current) setSnapshot({...next, contextKey: clientContextKeyRef.current});
      }});
    }
    const load = useCallback(async () => {
      if (!enabled || !organizationId) return null;
      const loadContext = storyContext({organizationId, mode, attemptId, encounterId, expectedPilot});
      const client = clientRef.current;
      clientContextKeyRef.current = loadContext.key;
      try {
        const sequence = mode === 'result'
          ? await client.loadResult({organizationId, attemptId})
          : await client.loadPrebattle({organizationId, encounterId, expectedPilot});
        if (sequence?.completed && renderedContextRef.current.key === loadContext.key && sequenceMatchesStoryContext(sequence, loadContext)) onCompleteRef.current?.(sequence);
        return sequence;
      } catch (error) { return null; }
    }, [enabled, organizationId, mode, attemptId, encounterId, expectedPilotKey]);
    useEffect(() => {
      if (!enabled || !organizationId) { clientRef.current.setIdentity(''); return; }
      const client = clientRef.current;
      void load();
      return () => client.setIdentity('');
    }, [load]);
    const acknowledge = useCallback(async () => {
      const acknowledgeContext = renderedContextRef.current;
      const next = await clientRef.current.acknowledge();
      if (next?.completed && renderedContextRef.current.key === acknowledgeContext.key && sequenceMatchesStoryContext(next, acknowledgeContext)) onCompleteRef.current?.(next);
      return next;
    }, []);
    return {...projectStorySnapshot(snapshot, {...context, enabled}), acknowledge, retry: load};
  }

  function PrebattleStoryGate({account, request, encounterId = 'solo/mini-1', expectedPilot, onComplete, onCancel}) {
    const organizationId = String(account?.organization?.id || '');
    const district = encounterId === 'solo/mini-2' ? 'Banjarsari' : encounterId === 'solo/boss' ? 'Pasar Kliwon' : 'Laweyan';
    const story = useStorySequence({request, organizationId, mode: 'prebattle', encounterId, expectedPilot, onComplete});
    if (story.status === 'loading' || story.status === 'idle') return <StoryStatus title="OPENING PILOT CHANNEL" copy={'Confirming the registered Crew before ' + district + ' deployment.'}/>;
    if (!story.sequence) return <StoryStatus title="COMMAND LINK INTERRUPTED" copy={story.error || 'The Pilot channel could not be opened.'} error onRetry={story.retry} onCancel={onCancel} cancelLabel="RETURN TO CREW CONFIRMATION"/>;
    const step = story.sequence.steps[story.sequence.stepIndex];
    if (!step) return null;
    return <StoryDialogue step={step} busy={story.busy} error={story.error} progressLabel={String(story.sequence.stepIndex + 1).padStart(2, '0') + ' / ' + String(story.sequence.steps.length).padStart(2, '0')} onAdvance={story.acknowledge}/>;
  }

  function ResultStoryStep({step, account, avatarSource = '', busy, error, onAdvance, onMinimize, minimized, Ceremony, Dialogue, Assistant}) {
    if (!step) return null;
    if (step.kind === 'dialogue') return <StoryDialogue step={step} busy={busy} error={error} progressLabel="RESULT CHANNEL" onAdvance={onAdvance}/>;
    if (step.kind === 'certificate') return <Ceremony districtHonor={step.honor} account={account} busy={busy} error={error} onClose={onAdvance} onContinueArchive={onAdvance}/>;
    if (step.presentation === 'fullscreen') return <Dialogue id="nusantara-result-assistant" ariaLabel="Nusantara result briefing" lines={[step.text]} stepIndex={0}
      assistantName={account?.organization?.assistantName || 'ANVILGEIST'} avatarSource={avatarSource || account?.organization?.avatarUrl || ''} busy={busy} error={error}
      primaryLabel={error ? 'RETRY' : 'CONTINUE'} explicitPrimaryLabel onAdvance={onAdvance}/>;
    return <Assistant account={account} telemetry={{}} tutorialStep={0} briefing={{label: 'CAMPAIGN RESULT', copy: step.text, stepIndex: 0, totalSteps: 1, actionLabel: error ? 'RETRY' : 'CONTINUE'}}
      minimized={minimized} onDismiss={onMinimize} onReopen={onMinimize} onAdvance={onAdvance} busy={busy} error={error}/>;
  }

  function ResultStoryFlow({account, request, attemptId, blocked, Ceremony, Dialogue, Assistant, avatarSource = '', trapFocus, onComplete, onDismiss}) {
    const flowRef = useRef(null);
    const [otherModal, setOtherModal] = useState(true);
    const [minimized, setMinimized] = useState(false);
    const organizationId = String(account?.organization?.id || '');
    const enabled = Boolean(organizationId && attemptId);
    const story = useStorySequence({request, organizationId, mode: 'result', attemptId, enabled, onComplete});
    useLayoutEffect(() => {
      const check = () => setOtherModal(Array.from(document.querySelectorAll('[aria-modal="true"]')).some(node => !flowRef.current?.contains(node) && node.getClientRects().length > 0));
      check();
      const observer = new MutationObserver(check);
      observer.observe(document.body, {childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style', 'hidden', 'aria-modal']});
      return () => observer.disconnect();
    }, []);
    const visible = enabled && !blocked && !otherModal;
    const step = story.sequence?.steps[story.sequence.stepIndex] || null;
    const modalStep = step && (step.kind !== 'assistant' || step.presentation === 'fullscreen');
    const fullscreenAssistant = step?.kind === 'assistant' && step.presentation === 'fullscreen';
    useLayoutEffect(() => {
      if (!visible || !modalStep) return undefined;
      const previous = document.activeElement;
      const shells = Array.from(document.querySelectorAll('.app-shell, [data-campaign-qa-shell]'));
      const inert = shells.map(node => node.inert);
      shells.forEach(node => { node.inert = true; });
      if (fullscreenAssistant) flowRef.current?.querySelector('[role="dialog"]')?.focus();
      return () => {
        shells.forEach((node, index) => { node.inert = inert[index]; });
        if (fullscreenAssistant && previous?.isConnected && !previous.closest?.('[inert]')) previous.focus({preventScroll: true});
      };
    }, [visible, step?.id, modalStep, fullscreenAssistant]);
    if (!visible) return <div ref={flowRef} className="nusantara-result-story-flow"/>;
    if (story.status === 'loading' || story.status === 'idle') return <div ref={flowRef} className="nusantara-result-story-flow"><StoryStatus title="RESTORING SOLO CHANNEL" copy="Opening your campaign debrief."/></div>;
    if (!step) return <div ref={flowRef} className="nusantara-result-story-flow"><StoryStatus title="COMMAND LINK INTERRUPTED" copy={story.error || 'The result story could not be restored.'} error onRetry={story.retry} onCancel={onDismiss} cancelLabel="RETURN TO SOLO"/></div>;
    return <div ref={flowRef} className={'nusantara-result-story-flow' + (modalStep ? ' is-modal' : ' is-panel')} data-result-story-step={step.id}
      onKeyDownCapture={fullscreenAssistant && trapFocus ? event => { if (event.key === 'Tab') trapFocus(event, flowRef.current); } : undefined}>
      <ResultStoryStep step={step} account={account} avatarSource={avatarSource} busy={story.busy} error={story.error} onAdvance={story.acknowledge}
        onMinimize={() => setMinimized(value => !value)} minimized={minimized} Ceremony={Ceremony} Dialogue={Dialogue} Assistant={Assistant}/>
    </div>;
  }

  function useDistrictHonors({account, request, refreshKey = ''}) {
    const organizationId = account?.status === 'signed-in' ? String(account?.organization?.id || '') : '';
    const [state, setState] = useState({identity: '', status: 'idle', honors: [], error: ''});
    const epochRef = useRef(0);
    const refresh = useCallback(async () => {
      const epoch = ++epochRef.current;
      if (!organizationId) { setState({identity: '', status: 'idle', honors: [], error: ''}); return []; }
      setState({identity: organizationId, status: 'loading', honors: [], error: ''});
      try {
        const honors = await NusantaraStoryClient.loadHonors({organizationId, request});
        if (epoch !== epochRef.current) return [];
        setState({identity: organizationId, status: 'ready', honors, error: ''});
        return honors;
      } catch (error) {
        if (epoch === epochRef.current) setState({identity: organizationId, status: 'error', honors: [], error: error.message || 'Campaign honors unavailable.'});
        return [];
      }
    }, [organizationId, request]);
    useEffect(() => { void refresh(); return () => { epochRef.current += 1; }; }, [refresh, refreshKey]);
    return state.identity === organizationId ? {...state, refresh} : {identity: organizationId, status: organizationId ? 'loading' : 'idle', honors: [], error: '', refresh};
  }

  root.NusantaraStoryUI = {StoryDialogueView, StoryDialogue, PrebattleStoryGate, ResultStoryStep, ResultStoryFlow, useDistrictHonors, projectStorySnapshot};
})(globalThis);
