import React, { MouseEventHandler, useCallback, useEffect, useRef, useState } from 'react';
import { useSetAtom } from 'jotai';
import {
  Box,
  Button,
  Chip,
  color,
  config,
  Icon,
  IconButton,
  Icons,
  Menu,
  MenuItem,
  PopOut,
  RectCords,
  Spinner,
  Text,
  toRem,
} from 'folds';
import FocusTrap from 'focus-trap-react';
import { SequenceCard } from '../../components/sequence-card';
import * as css from './styles.css';
import {
  ChatButton,
  ControlDivider,
  FullscreenButton,
  MicrophoneButton,
  ScreenShareButton,
  ScreenshareAudioButton,
  SoundButton,
  VideoButton,
} from './Controls';
import { CallEmbed, useCallControlState } from '../../plugins/call';
import { useSetting } from '../../state/hooks/settings';
import { settingsAtom } from '../../state/settings';
import { callEmbedAtom } from '../../state/callEmbed';
import { useResizeObserver } from '../../hooks/useResizeObserver';
import { ScreenSize, useScreenSize } from '../../hooks/useScreenSize';
import { stopPropagation } from '../../utils/keyboard';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { useCallEmbedRef } from '../../hooks/useCallEmbed';
import { useAfkAutoMute } from '../../hooks/useAfkAutoMute';
import { CallSoundboard } from './CallSoundboard';
import { useStateEvent } from '../../hooks/useStateEvent';
import { StateEvent } from '../../../types/matrix/room';
import { RoomQualityContent } from '../../utils/callQuality';

type CallControlsProps = {
  callEmbed: CallEmbed;
};
export function CallControls({ callEmbed }: CallControlsProps) {
  const controlRef = useRef<HTMLDivElement>(null);
  const callEmbedRef = useCallEmbedRef();
  const setCallEmbed = useSetAtom(callEmbedAtom);
  const screenSize = useScreenSize();
  const [narrowContainer, setNarrowContainer] = useState(document.body.clientWidth < 500);
  const [isFullscreen, setIsFullscreen] = useState(false);

  useResizeObserver(
    useCallback(() => {
      const element = controlRef.current;
      if (!element) return;
      setNarrowContainer(element.clientWidth < 500);
    }, []),
    useCallback(() => controlRef.current, []),
  );

  // Collapse to the stacked/compact layout whenever the bar's own container is
  // narrow (a small desktop call window) OR the viewport is a phone. The old
  // element-only `< 500` check left the ~11-control row overflowing off-screen
  // in the 500–750px band (landscape phones / small tablets).
  const compact = narrowContainer || screenSize === ScreenSize.Mobile;

  useEffect(() => {
    const onFullscreenChange = () => setIsFullscreen(!!document.fullscreenElement);
    document.addEventListener('fullscreenchange', onFullscreenChange);
    return () => document.removeEventListener('fullscreenchange', onFullscreenChange);
  }, []);

  const handleFullscreen = useCallback(() => {
    if (document.fullscreenElement) {
      document.exitFullscreen();
    } else {
      callEmbedRef.current?.requestFullscreen();
    }
  }, [callEmbedRef]);

  const { microphone, video, sound, screenshare, spotlight, screenshareAudioMuted } =
    useCallControlState(callEmbed.control);

  useAfkAutoMute(callEmbed);

  const [cords, setCords] = useState<RectCords>();
  const [shareConfirm, setShareConfirm] = useState(false);
  useEffect(() => {
    if (!shareConfirm) return;
    const onKeyDown = (e: KeyboardEvent) => {
      if (e.key === 'Escape') setShareConfirm(false);
    };
    window.addEventListener('keydown', onKeyDown);
    return () => window.removeEventListener('keydown', onKeyDown);
  }, [shareConfirm]);
  const [pttMode] = useSetting(settingsAtom, 'pttMode');
  const [pttKey] = useSetting(settingsAtom, 'pttKey');
  const [deafenKey] = useSetting(settingsAtom, 'deafenKey');
  const [soundboardEnabled] = useSetting(settingsAtom, 'soundboardEnabled');

  // [P5-31] Hard room publish policy — hide controls the server will refuse so
  // users don't click dead buttons. Absent/true = allowed.
  const roomQualityEvent = useStateEvent(callEmbed.room, StateEvent.LotusRoomQuality);
  const roomQuality = roomQualityEvent?.getContent<RoomQualityContent>();
  const cameraAllowed = roomQuality?.allow_camera !== false;
  const screenshareAllowed = roomQuality?.allow_screenshare !== false;
  // Keep a forbidden control visible while its track is still live (so the user
  // can stop it); otherwise hide it entirely.
  const showCamera = cameraAllowed || video;
  const showScreenshare = screenshareAllowed || screenshare;
  const showVideoGroup = showCamera || showScreenshare || !!document.fullscreenEnabled;
  const [pttActive, setPttActive] = useState(false);

  // Track microphone via ref so the PTT effect doesn't need it as a dep (avoids listener churn)
  const microphoneRef = useRef(microphone);
  useEffect(() => {
    microphoneRef.current = microphone;
  }, [microphone]);

  // Handle PTT mode toggle mid-call — save/restore mic state (I-4)
  const pttModeRef = useRef(pttMode);
  const micBeforePTTRef = useRef<boolean | null>(null);
  useEffect(() => {
    if (pttMode && !pttModeRef.current) {
      micBeforePTTRef.current = microphoneRef.current;
      callEmbed.control.setMicrophone(false);
    } else if (!pttMode && pttModeRef.current) {
      callEmbed.control.setMicrophone(micBeforePTTRef.current ?? true);
      micBeforePTTRef.current = null;
    }
    pttModeRef.current = pttMode;
  }, [pttMode, callEmbed]);

  const handleOpenMenu: MouseEventHandler<HTMLButtonElement> = (evt) => {
    setCords(evt.currentTarget.getBoundingClientRect());
  };

  const handleSpotlightClick = () => {
    callEmbed.control.toggleSpotlight();
    setCords(undefined);
  };

  const handleReactionsClick = () => {
    callEmbed.control.toggleReactions();
    setCords(undefined);
  };

  const handleSettingsClick = () => {
    callEmbed.control.toggleSettings();
    setCords(undefined);
  };

  const handleMicrophoneToggle = useCallback(
    () => callEmbed.control.toggleMicrophone(),
    [callEmbed],
  );
  const handleVideoToggle = useCallback(() => callEmbed.control.toggleVideo(), [callEmbed]);

  const pttActiveRef = useRef(false);

  useEffect(() => {
    if (!pttMode) return;
    const iframeWindow = callEmbed.iframe.contentWindow;

    const onKeyDown = (e: KeyboardEvent) => {
      if (e.code !== pttKey || e.repeat) return;
      const target = e.target as HTMLElement;
      // BUG-7: use ownerDocument.body so isEditable works inside the EC iframe
      const isEditable = (el: HTMLElement): boolean => {
        const tag = el.tagName;
        if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return true;
        let node: HTMLElement | null = el;
        while (node && node !== el.ownerDocument.body) {
          if (node.contentEditable === 'true') return true;
          if (node.contentEditable === 'false') return false;
          node = node.parentElement;
        }
        return false;
      };
      if (isEditable(target)) return;
      e.preventDefault();
      // C-M5: mark PTT active BEFORE unmuting so the mic echo (onMediaState)
      // doesn't treat this transient unmute as a user-initiated undeafen.
      callEmbed.control.pttActive = true;
      if (!microphoneRef.current) callEmbed.control.setMicrophone(true);
      pttActiveRef.current = true;
      setPttActive(true);
    };
    const onKeyUp = (e: KeyboardEvent) => {
      if (e.code !== pttKey) return;
      callEmbed.control.pttActive = false;
      callEmbed.control.setMicrophone(false);
      pttActiveRef.current = false;
      setPttActive(false);
    };
    const onBlur = () => {
      callEmbed.control.pttActive = false;
      callEmbed.control.setMicrophone(false);
      pttActiveRef.current = false;
      setPttActive(false);
    };
    const onFocus = () => {
      callEmbed.control.pttActive = false;
      callEmbed.control.setMicrophone(false);
      pttActiveRef.current = false;
      setPttActive(false);
    };
    window.addEventListener('keydown', onKeyDown);
    window.addEventListener('keyup', onKeyUp);
    window.addEventListener('blur', onBlur);
    window.addEventListener('focus', onFocus);
    // BUG-9: also wire iframe blur/focus so stuck-mic release works when focus moves to iframe
    iframeWindow?.addEventListener('keydown', onKeyDown);
    iframeWindow?.addEventListener('keyup', onKeyUp);
    iframeWindow?.addEventListener('blur', onBlur);
    iframeWindow?.addEventListener('focus', onFocus);
    return () => {
      window.removeEventListener('keydown', onKeyDown);
      window.removeEventListener('keyup', onKeyUp);
      window.removeEventListener('blur', onBlur);
      window.removeEventListener('focus', onFocus);
      iframeWindow?.removeEventListener('keydown', onKeyDown);
      iframeWindow?.removeEventListener('keyup', onKeyUp);
      iframeWindow?.removeEventListener('blur', onBlur);
      iframeWindow?.removeEventListener('focus', onFocus);
      // BUG-8: if callEmbed changes while PTT is active, release mic on cleanup
      if (pttActiveRef.current) {
        callEmbed.control.pttActive = false;
        callEmbed.control.setMicrophone(false);
        pttActiveRef.current = false;
        setPttActive(false);
      }
    };
    // microphone intentionally read via microphoneRef — excluded from deps to avoid listener churn
  }, [pttMode, pttKey, callEmbed]);

  useEffect(() => {
    const isEditable = (el: HTMLElement): boolean => {
      const tag = el.tagName;
      if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return true;
      let node: HTMLElement | null = el;
      while (node && node !== el.ownerDocument.body) {
        if (node.contentEditable === 'true') return true;
        if (node.contentEditable === 'false') return false;
        node = node.parentElement;
      }
      return false;
    };
    const onKeyDown = (e: KeyboardEvent) => {
      if (e.code !== deafenKey) return;
      if (e.repeat) return;
      if (isEditable(e.target as HTMLElement)) return;
      e.preventDefault();
      callEmbed.control.toggleSound();
    };
    // C-L4: also bind the EC iframe window so the deafen key works when focus is
    // inside the iframe (mirrors the PTT binding above).
    const iframeWindow = callEmbed.iframe.contentWindow;
    window.addEventListener('keydown', onKeyDown);
    iframeWindow?.addEventListener('keydown', onKeyDown);
    return () => {
      window.removeEventListener('keydown', onKeyDown);
      iframeWindow?.removeEventListener('keydown', onKeyDown);
    };
  }, [callEmbed, deafenKey]);

  const [hangupState, hangup] = useAsyncCallback(
    useCallback(() => callEmbed.hangup(), [callEmbed]),
  );
  const exiting =
    hangupState.status === AsyncStatus.Loading || hangupState.status === AsyncStatus.Success;

  // C-M4: the normal teardown relies on EC echoing a Close/Hangup action after
  // it ACKs HangupCall (useCallHangupEvent -> clears callEmbedAtom -> dispose).
  // If EC ACKs but never echoes, the End button would spin forever. Fall back to
  // disposing the embed a few seconds after a successful hangup send, unless it
  // was already torn down by the normal path.
  useEffect(() => {
    if (hangupState.status !== AsyncStatus.Success) return undefined;
    const id = setTimeout(() => {
      if (!callEmbed.disposed) setCallEmbed(undefined);
    }, 4000);
    return () => clearTimeout(id);
  }, [hangupState.status, callEmbed, setCallEmbed]);

  const pttKeyLabel = pttKey === 'Space' ? 'SPACE' : pttKey.replace('Key', '').replace('Digit', '');

  return (
    <Box
      ref={controlRef}
      className={css.CallControlContainer}
      justifyContent="Center"
      alignItems="Center"
    >
      {pttMode && (
        <Chip
          variant={pttActive ? 'Success' : 'Warning'}
          fill="Soft"
          radii="400"
          style={{
            position: 'absolute',
            top: '-2.2rem',
            left: '50%',
            transform: 'translateX(-50%)',
            pointerEvents: 'none',
            whiteSpace: 'nowrap',
          }}
          outlined
        >
          <Text size="T200" style={{ fontWeight: 700 }}>
            {pttActive ? '● Live' : `PTT — Hold ${pttKeyLabel}`}
          </Text>
        </Chip>
      )}
      {shareConfirm && (
        <>
          <div
            style={{ position: 'fixed', inset: 0, zIndex: 99 }}
            onClick={() => setShareConfirm(false)}
            aria-hidden="true"
          />
          <Box
            style={{
              position: 'absolute',
              bottom: '110%',
              left: '50%',
              transform: 'translateX(-50%)',
              background: color.Surface.Container,
              border: `${config.borderWidth.B300} solid ${color.Surface.ContainerLine}`,
              borderRadius: '0.75rem',
              padding: '1rem 1.25rem',
              zIndex: 100,
              minWidth: '260px',
              // Don't run past the screen edges on a narrow phone (centered via
              // translateX(-50%)); clamp to the viewport minus a small margin.
              maxWidth: `calc(100vw - 2 * ${config.space.S400})`,
              boxShadow: '0 8px 32px rgba(0,0,0,0.35)',
              display: 'flex',
              flexDirection: 'column',
              gap: '0.75rem',
            }}
          >
            <Text size="T300" style={{ fontWeight: 600 }}>
              Share your screen?
            </Text>
            <Text size="T200" style={{ opacity: 0.75 }}>
              Your screen will be visible to all participants in this call.
            </Text>
            <Box gap="200">
              <Button
                size="300"
                variant="Success"
                fill="Solid"
                radii="300"
                onClick={() => {
                  callEmbed.control.toggleScreenshare();
                  setShareConfirm(false);
                }}
              >
                <Text size="B300">Share</Text>
              </Button>
              <Button
                size="300"
                variant="Secondary"
                fill="Soft"
                radii="300"
                outlined
                onClick={() => setShareConfirm(false)}
              >
                <Text size="B300">Cancel</Text>
              </Button>
            </Box>
          </Box>
        </>
      )}
      <SequenceCard
        className={css.ControlCard}
        variant="SurfaceVariant"
        gap="400"
        radii="500"
        alignItems="Center"
        justifyContent="SpaceBetween"
        wrap="Wrap"
      >
        <Box alignItems="Center" gap="Inherit" grow="Yes" direction={compact ? 'Column' : 'Row'}>
          <Box shrink="No" alignItems="Inherit" justifyContent="Inherit" gap="200">
            <MicrophoneButton enabled={microphone} onToggle={handleMicrophoneToggle} />
            <SoundButton enabled={sound} onToggle={() => callEmbed.control.toggleSound()} />
          </Box>
          {!compact && showVideoGroup && <ControlDivider />}
          {showVideoGroup && (
            <Box shrink="No" alignItems="Inherit" justifyContent="Inherit" gap="200">
              {/* Show a forbidden control while its track is still live so the
                  user can stop it; once stopped it hides and can't be restarted. */}
              {showCamera && <VideoButton enabled={video} onToggle={handleVideoToggle} />}
              {showScreenshare && (
                <>
                  <ScreenShareButton
                    enabled={screenshare}
                    onToggle={() =>
                      screenshare ? callEmbed.control.toggleScreenshare() : setShareConfirm(true)
                    }
                  />
                  {/* Mute-screenshare-audio sits directly next to the screenshare
                      control since they're the same concern. */}
                  <ScreenshareAudioButton
                    muted={screenshareAudioMuted}
                    onToggle={() => callEmbed.control.toggleScreenshareAudio()}
                  />
                </>
              )}
              {!!document.fullscreenEnabled && (
                <FullscreenButton isFullscreen={isFullscreen} onToggle={handleFullscreen} />
              )}
            </Box>
          )}
        </Box>
        {!compact && <ControlDivider />}
        <Box alignItems="Center" gap="Inherit" grow="Yes" direction={compact ? 'Column' : 'Row'}>
          <Box shrink="No" alignItems="Inherit" justifyContent="Inherit" gap="200">
            <ChatButton />
            {soundboardEnabled && <CallSoundboard callEmbed={callEmbed} />}
            <PopOut
              anchor={cords}
              position="Top"
              align="Center"
              content={
                <FocusTrap
                  focusTrapOptions={{
                    initialFocus: false,
                    onDeactivate: () => setCords(undefined),
                    clickOutsideDeactivates: true,
                    isKeyForward: (evt: KeyboardEvent) => evt.key === 'ArrowDown',
                    isKeyBackward: (evt: KeyboardEvent) => evt.key === 'ArrowUp',
                    escapeDeactivates: stopPropagation,
                  }}
                >
                  <Menu>
                    <Box direction="Column" style={{ padding: config.space.S100 }}>
                      <MenuItem
                        size="300"
                        variant="Surface"
                        radii="300"
                        onClick={handleSpotlightClick}
                      >
                        <Text size="B300" truncate>
                          {spotlight ? 'Grid View' : 'Spotlight View'}
                        </Text>
                      </MenuItem>
                      <MenuItem
                        size="300"
                        variant="Surface"
                        radii="300"
                        onClick={handleReactionsClick}
                      >
                        <Text size="B300" truncate>
                          Reactions
                        </Text>
                      </MenuItem>
                      <MenuItem
                        size="300"
                        variant="Surface"
                        radii="300"
                        onClick={handleSettingsClick}
                      >
                        <Text size="B300" truncate>
                          Settings
                        </Text>
                      </MenuItem>
                    </Box>
                  </Menu>
                </FocusTrap>
              }
            >
              <IconButton
                variant="Surface"
                fill="Soft"
                radii="400"
                size="400"
                onClick={handleOpenMenu}
                outlined
                aria-label="More options"
                aria-expanded={!!cords}
                aria-haspopup="menu"
              >
                <Icon size="400" src={Icons.VerticalDots} />
              </IconButton>
            </PopOut>
          </Box>
          <Box shrink="No" direction="Column">
            <Button
              style={{ minWidth: toRem(88) }}
              variant="Critical"
              fill="Solid"
              onClick={hangup}
              before={
                exiting ? (
                  <Spinner variant="Critical" fill="Solid" size="200" />
                ) : (
                  <Icon src={Icons.PhoneDown} size="200" filled />
                )
              }
              disabled={exiting}
            >
              <Text size="B400">End</Text>
            </Button>
          </Box>
        </Box>
      </SequenceCard>
    </Box>
  );
}
