;(function () {
/* VoiceFAB — single-mount FAB that stays alive across home ↔ trade
   route swaps and across all voice states. The FAB NEVER leaves
   horizontal centre — it only moves vertically between two anchors
   (bottom-center and panel-center). Three ring sizes:
     normal (76)  — idle
     medium (110) — decision, voiceMini
     large  (180) — chat, voice (background orb in the panel centre)
   In chat/voice the FAB acts as a translucent background element —
   reduced opacity, no pointer-events — so transcript bubbles are
   visible through it. */

const EASE = 'cubic-bezier(0.32, 0.72, 0.24, 1)';
const POS_DURATION = 800;
const RING_DURATION = 360;

const POSITION_ANCHORS = {
  'bottom-center': { bottom: 28, left: '50%', right: undefined, translateX: -38, translateY: -22 },
  'panel-center':  { bottom: 342, left: '50%', right: undefined, translateX: -38, translateY: 0 },
};

const MODE_TO_STYLE = {
  idle:      { anchor: 'bottom-center', size: 76,  buttonSize: 68  },
  decision:  { anchor: 'bottom-center', size: 110, buttonSize: 100 },
  chat:      { anchor: 'panel-center',  size: 180, buttonSize: 166 },
  voice:     { anchor: 'panel-center',  size: 180, buttonSize: 166 },
  voiceMini: { anchor: 'bottom-center', size: 110, buttonSize: 100 },
};

const VoiceFAB = ({ onPress, mode = 'idle', suppressed = false }) => {
  const videoRef = React.useRef(null);
  const [pressed, setPressed] = React.useState(false);
  React.useEffect(() => {
    const v = videoRef.current;
    if (!v) return;
    v.muted = true;
    v.defaultMuted = true;
    const tryPlay = () => v.play().catch(() => {});
    if (v.readyState >= 2) tryPlay();
    else v.addEventListener('loadeddata', tryPlay, { once: true });
  }, []);
  const releasePress = () => setPressed(false);

  const styleCfg = MODE_TO_STYLE[mode] || MODE_TO_STYLE.idle;
  const anchor = POSITION_ANCHORS[styleCfg.anchor];
  const ringSize = styleCfg.size;
  const buttonSize = styleCfg.buttonSize;
  /* Negative offset keeps the ring's centre on the wrapper's centre.
     For a 110px ring inside a 76px wrapper, offset = (76-110)/2 = -17. */
  const ringOffset = (76 - ringSize) / 2;
  const buttonOffset = (ringSize - buttonSize) / 2;

  const isActive = mode === 'voice' || mode === 'voiceMini' || mode === 'decision';
  const isBackground = mode === 'chat' || mode === 'voice';
  /* `suppressed` is set by the App when a modal popup is open
     (CashFlowSheet, EquitiPlusModal, etc). It disables pointer events on
     the FAB's interactive layers so taps don't fall through the popup
     into the FAB beneath. The wrapper already has pointer-events:none,
     but the ring + button below override it back to 'auto' — so we have
     to force them off here too. */
  const interactive = !isBackground && !suppressed;
  /* Only chat fades the FAB to a translucent watermark behind the
     transcript. Voice mode keeps the gem-smoke fully opaque so the
     orb is the focal point of the listening state. */
  const isFaded = mode === 'chat';

  const wrapperStyle = {
    position: 'absolute',
    width: '76px',
    height: '76px',
    bottom: anchor.bottom + 'px',
    left: anchor.left,
    transform: 'translateX(' + anchor.translateX + 'px) translateY(' + anchor.translateY + 'px)',
    pointerEvents: 'none',
    zIndex: 9200,
    transition: [
      'bottom ' + POS_DURATION + 'ms ' + EASE,
      'transform ' + POS_DURATION + 'ms ' + EASE,
    ].join(', '),
  };

  return (
    <div
      onMouseDown={() => setPressed(true)}
      onMouseUp={releasePress}
      onMouseLeave={releasePress}
      onTouchStart={() => setPressed(true)}
      onTouchEnd={releasePress}
      onTouchCancel={releasePress}
      style={wrapperStyle}
    >
      <div style={{
        position: 'absolute',
        top: ringOffset + 'px',
        left: ringOffset + 'px',
        width: ringSize + 'px',
        height: ringSize + 'px',
        borderRadius: (ringSize / 2) + 'px',
        backgroundColor: '#fff',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        opacity: isFaded ? 0.22 : 1,
        transform: pressed && !isBackground ? 'scale(1.06)' : 'scale(1)',
        boxShadow: isFaded
          ? '0 0 60px rgba(0,175,171,0.12)'
          : pressed
            ? '0px 0px 40px rgba(0, 175, 171, 0.7)'
            : isActive
              ? '0px 0px 36px rgba(0, 175, 171, 0.55)'
              : '0px 0px 24px rgba(0, 175, 171, 0.4)',
        transition: 'transform 220ms ' + EASE
          + ', box-shadow 280ms ' + EASE
          + ', opacity ' + RING_DURATION + 'ms ' + EASE
          + ', top ' + RING_DURATION + 'ms ' + EASE
          + ', left ' + RING_DURATION + 'ms ' + EASE
          + ', width ' + RING_DURATION + 'ms ' + EASE
          + ', height ' + RING_DURATION + 'ms ' + EASE
          + ', border-radius ' + RING_DURATION + 'ms ' + EASE,
        pointerEvents: interactive ? 'auto' : 'none',
      }}>
        <button onClick={onPress} style={{
          border: 'none', padding: 0, margin: 0,
          cursor: interactive ? 'pointer' : 'default',
          font: 'inherit',
          width: buttonSize + 'px',
          height: buttonSize + 'px',
          borderRadius: (buttonSize / 2) + 'px',
          backgroundColor: colors.teal,
          overflow: 'hidden',
          transition: 'width ' + RING_DURATION + 'ms ' + EASE
            + ', height ' + RING_DURATION + 'ms ' + EASE
            + ', border-radius ' + RING_DURATION + 'ms ' + EASE,
          pointerEvents: interactive ? 'auto' : 'none',
        }}>
          <video
            ref={videoRef}
            autoPlay
            loop
            muted
            playsInline
            style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block', pointerEvents: 'none' }}
          >
            <source src="superapp/img/gem-smoke.mp4" type="video/mp4"/>
          </video>
        </button>
      </div>
    </div>
  );
};

Object.assign(window, { VoiceFAB });
})();
