// components/MorphyFloat.jsx
//
// Pitchdeck page: floating Morphy presence. Lower-right pulse-dot when idle,
// expandable drawer when clicked. Constrained per pitchdeck-canonical-v1
// Morpheme. TT voice activates here.
//
// SCAR-200: Constitution Stack enforcement at every Morphy invocation.
// SCAR-144: TT is sacred; routes through shared TT context.
// CEREBRAS DOCTRINE #15: never tell model "return ONLY XML"; capture _thinking.
//
// Defensive: all window globals checked before use. Component renders even
// when libs are absent.
//
// Wire-in: window.MorphyFloat = MorphyFloat
// Mounted in pitchdeck.html as <MorphyFloat />

var _RF = React;
var _useState = _RF.useState;
var _useEffect = _RF.useEffect;
var _useRef = _RF.useRef;

function MorphyFloat({ currentSlide, audienceMode }) {
  var _isOpen = _useState(false);
  var isOpen = _isOpen[0];
  var setIsOpen = _isOpen[1];

  var _isPulsing = _useState(true);
  var isPulsing = _isPulsing[0];
  var setIsPulsing = _isPulsing[1];

  var _reasoning = _useState(null);
  var reasoning = _reasoning[0];
  var setReasoning = _reasoning[1];

  var _pttActive = _useState(false);
  var pttActive = _pttActive[0];
  var setPttActive = _pttActive[1];

  var audioCtxRef = _useRef(null);

  // --- Defensive global access helpers ---
  function hasMorphemeLoader() {
    return typeof window.MorphemeLoader !== 'undefined' && window.MorphemeLoader;
  }
  function hasTTContext() {
    return typeof window.TTContext !== 'undefined' && window.TTContext;
  }
  function hasCerebrasBackchannel() {
    return typeof window.CerebrasBackchannel !== 'undefined' && window.CerebrasBackchannel;
  }
  function hasPersistence() {
    return typeof window.Persistence !== 'undefined' && window.Persistence;
  }

  // Load Pitchdeck Morpheme on mount + audience mode change
  _useEffect(function() {
    if (!hasMorphemeLoader()) {
      console.warn('[MorphyFloat] MorphemeLoader not available, continuing without Morpheme');
      return;
    }
    try {
      // v3 FIX-4: bare-stem ID convention. Loader prepends '/morphemes/' and
      // appends '.yaml'. File on disk: morphemes/pitchdeck-canonical-v1.yaml.
      // Bug fix 2026-05-09: MorphemeLoader.load returns a Promise. Reading
      // .technique_disclosure off the Promise itself (instead of the resolved
      // value) was producing a false-positive SCAR-200 violation log every
      // time. Resolve via .then before the constitutional check.
      var loadResult = window.MorphemeLoader.load(
        'pitchdeck-canonical-v1',
        { audienceOverlay: audienceMode }
      );
      var resolveCheck = function (morpheme) {
        if (!morpheme) {
          console.error('[MorphyFloat] Pitchdeck Morpheme failed to load');
          return;
        }
        if (!morpheme.technique_disclosure || !morpheme.hybrid_commitment) {
          console.error('[MorphyFloat] SCAR-200 violation: Morpheme missing required fields');
        }
      };
      if (loadResult && typeof loadResult.then === 'function') {
        loadResult.then(resolveCheck).catch(function (err) {
          console.warn('[MorphyFloat] Morpheme load rejected:', err && err.message);
        });
      } else {
        resolveCheck(loadResult);
      }
    } catch (e) {
      console.warn('[MorphyFloat] Morpheme load error:', e);
    }
  }, [audienceMode]);

  // Wave E (canonical TT always-on). Replaces v2 PTT pattern.
  // First click: acquires mic ONCE via window.VoiceSession.start, wires
  // mic -> source -> tt.node -> destination -> RTCPeerConnection per SCAR-144.
  // Subsequent clicks: toggle mute. Session held for entire page lifetime.
  // pttActive state name retained for CSS class compatibility, but now reflects
  // "voice session is live" rather than "user is holding the button".
  var voiceState = _useState('idle'); // idle | starting | active | muted | error
  var voiceStateValue = voiceState[0];
  var setVoiceStateValue = voiceState[1];

  React.useEffect(function () {
    if (typeof window.VoiceSession === 'undefined' || !window.VoiceSession.on) return;
    var unsub = window.VoiceSession.on('state', function (e) {
      setVoiceStateValue(e && e.state ? e.state : 'idle');
      setPttActive(e && (e.state === 'active' || e.state === 'starting'));
    });
    return unsub;
  }, []);

  var handleMicToggle = function () {
    if (typeof window.VoiceSession === 'undefined') {
      console.warn('[MorphyFloat] VoiceSession not loaded. Check lib/voice-session.js script tag.');
      return;
    }
    var current = window.VoiceSession.getState();
    if (current === 'idle' || current === 'stopped' || current === 'error') {
      // First click of session. Acquire mic + open WebRTC.
      window.VoiceSession.start({
        // Pitchdeck zone: presentation register, conservative voice prompt.
        instructions: 'You are Morphy in pitchdeck zone. Audience: ' + (audienceMode || 'unselected') + '. Stay grounded in slide content. Defer to slide knowledge_stack. Decline anything outside scope.'
      }).catch(function (err) {
        console.warn('[MorphyFloat] VoiceSession.start failed:', err && err.message);
      });
    } else if (current === 'active') {
      window.VoiceSession.setMuted(true);
    } else if (current === 'muted') {
      window.VoiceSession.setMuted(false);
    } else if (current === 'starting') {
      // Ignore clicks while connecting.
    }
  };

  // Cerebras backchannel: fires per substantive turn
  var handleAudienceQuestion = function(question) {
    if (!hasCerebrasBackchannel()) {
      console.warn('[MorphyFloat] CerebrasBackchannel not available');
      return;
    }
    window.CerebrasBackchannel.runTurn(question, {
      zone: 'pitchdeck',
      slide: currentSlide,
      audienceMode: audienceMode,
    }).then(function(result) {
      setReasoning(result);
      if (hasPersistence() && typeof window.Persistence.writeReceipt === 'function') {
        window.Persistence.writeReceipt({
          zone: 'pitchdeck',
          slide_id: currentSlide ? currentSlide.id : null,
          audience_mode: audienceMode,
          question: question,
          reasoning_thinking: result ? result._thinking : null,
          yama_scores: result ? result.yamas : null,
          timestamp: new Date().toISOString(),
        });
      }
    }).catch(function(err) {
      console.warn('[MorphyFloat] Backchannel error:', err);
    });
  };

  // Try-this-with-Morphy link generator
  var tryWithMorphyHref = currentSlide && currentSlide.try_with_morphy_seed
    ? '/dungeon-master?seed=' + encodeURIComponent(currentSlide.try_with_morphy_seed)
    : '/dungeon-master';

  return (
    <div className="morphy-float" data-open={isOpen}>
      {!isOpen && (
        <button
          className={'morphy-pulse-dot' + (isPulsing ? ' pulsing' : '')}
          onClick={function() { setIsOpen(true); setIsPulsing(false); }}
          aria-label="Open Morphy"
        >
          <span className="morphy-glyph">M</span>
        </button>
      )}
      {isOpen && (
        <div className="drawer expanded">
          <header className="morphy-drawer-header">
            <span className="morphy-status">Pitchdeck mode</span>
            <span className="morphy-mode-tag">{audienceMode || 'unselected'}</span>
            <button
              className="morphy-close"
              onClick={function() { setIsOpen(false); }}
              aria-label="Close drawer"
            >&times;</button>
          </header>

          <section className="morphy-current-slide">
            <h3>{currentSlide ? currentSlide.title : 'Loading...'}</h3>
            <p className="morphy-slide-subtitle">
              {currentSlide ? currentSlide.subtitle : ''}
            </p>
          </section>

          {reasoning && (
            <section className="morphy-reasoning">
              <h4>Cerebras LOG-5 base-7 reasoning</h4>
              <details>
                <summary>View reasoning trace</summary>
                <pre className="morphy-thinking">
                  {reasoning._thinking || ''}
                </pre>
                {reasoning.yamas && (
                  <ul className="morphy-yamas">
                    {Object.keys(reasoning.yamas).map(function(y) {
                      return (
                        <li key={y}>
                          <strong>{y}:</strong> {
                            typeof reasoning.yamas[y] === 'number'
                              ? reasoning.yamas[y].toFixed(2)
                              : reasoning.yamas[y]
                          }
                        </li>
                      );
                    })}
                  </ul>
                )}
              </details>
            </section>
          )}

          <section className="morphy-actions">
            <button
              className={'morphy-ptt morphy-mic-' + voiceStateValue}
              onClick={handleMicToggle}
              disabled={voiceStateValue === 'starting'}
              data-voice-state={voiceStateValue}
              title={
                voiceStateValue === 'idle' ? 'Click to start voice session (canonical Tensor Tympani)'
                : voiceStateValue === 'starting' ? 'Connecting...'
                : voiceStateValue === 'active' ? 'Mic active. Click to mute.'
                : voiceStateValue === 'muted' ? 'Muted. Click to unmute.'
                : 'Voice unavailable'
              }
            >
              {voiceStateValue === 'idle' && 'Start voice'}
              {voiceStateValue === 'starting' && 'Connecting...'}
              {voiceStateValue === 'active' && 'Mic on'}
              {voiceStateValue === 'muted' && 'Muted'}
              {voiceStateValue === 'error' && 'Voice off'}
              {voiceStateValue === 'stopped' && 'Start voice'}
            </button>

            <a
              className="morphy-try-link"
              href={tryWithMorphyHref}
            >
              Try this with Morphy
            </a>
          </section>

          <footer className="morphy-drawer-footer">
            <p className="morphy-disclosure">
              I am operating in pitchdeck mode. I will speak only to slide content
              and pre-approved facts for this audience. If you ask me something
              outside the deck, I will say so and offer to take the conversation
              to the dungeon-master page.
            </p>
          </footer>
        </div>
      )}
    </div>
  );
}

window.MorphyFloat = MorphyFloat;
