import React, { MouseEventHandler, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
  Box,
  Icon,
  IconButton,
  Icons,
  Menu,
  PopOut,
  RectCords,
  Scroll,
  Spinner,
  Switch,
  Text,
  Tooltip,
  TooltipProvider,
  color,
  config,
  toRem,
} from 'folds';
import FocusTrap from 'focus-trap-react';
import { useAtomValue } from 'jotai';
import { CallEmbed } from '../../plugins/call';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useSetting } from '../../state/hooks/settings';
import { settingsAtom } from '../../state/settings';
import { roomToParentsAtom } from '../../state/room/roomToParents';
import { useImagePackRooms } from '../../hooks/useImagePackRooms';
import { useRelevantSoundboardPacks } from '../../hooks/useSoundboardPacks';
import { SoundboardClipReader } from '../../plugins/soundboard';
import { UserSoundboardPack, RoomSoundboardPack } from '../../components/soundboard-pack-view';
import { stopPropagation } from '../../utils/keyboard';
import { playClipLocally, resolveClipObjectUrl } from '../../utils/soundboardClips';

type CallSoundboardProps = {
  callEmbed: CallEmbed;
};

type FlatClip = {
  key: string; // packId|shortcode
  packId: string;
  packName: string;
  clip: SoundboardClipReader;
};

/**
 * [P5-15 v2] In-call soundboard. Clips come from the aggregated soundboard packs
 * relevant to the call room (the room + parent spaces ∪ the user's personal
 * pack), just like custom emoji. Playing a clip publishes it into the call via
 * the EC fork (`io.lotus.inject_audio`, max one at a time) and plays it locally.
 * A management toggle reveals the pack editors (personal + this room, if
 * permitted). Space-wide packs are managed from Space settings.
 */
export function CallSoundboard({ callEmbed }: CallSoundboardProps) {
  const mx = useMatrixClient();
  const { room } = callEmbed;
  const roomToParents = useAtomValue(roomToParentsAtom);
  const packRooms = useImagePackRooms(room.roomId, roomToParents);
  const packs = useRelevantSoundboardPacks(packRooms);
  const [soundboardVolume] = useSetting(settingsAtom, 'soundboardVolume');
  const master = Math.max(0, Math.min(1, soundboardVolume / 100));

  const [cords, setCords] = useState<RectCords>();
  const [manage, setManage] = useState(false);
  const [playingKey, setPlayingKey] = useState<string>(); // host-side spam guard
  const [error, setError] = useState<string>();

  // C-L6: the play() flow schedules a 30s safety timeout that clears playingKey;
  // guard those setState calls against the component unmounting first.
  const mountedRef = useRef(true);
  const safetyTimerRef = useRef<number | undefined>(undefined);
  useEffect(
    () => () => {
      mountedRef.current = false;
      if (safetyTimerRef.current !== undefined) window.clearTimeout(safetyTimerRef.current);
    },
    [],
  );

  const groups = useMemo(
    () =>
      packs
        .map((pack) => ({
          id: pack.id,
          name: pack.meta.name ?? 'Soundboard',
          clips: pack.getClips(),
        }))
        .filter((g) => g.clips.length > 0),
    [packs],
  );

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

  const play = useCallback(
    async (flat: FlatClip) => {
      if (playingKey) return; // one at a time (fork also enforces this)
      setPlayingKey(flat.key);
      setError(undefined);
      // Per-play timer token: `done` clears its OWN timer by identity, so a
      // stale done() from a prior clip can't disarm a newer clip's safety timer
      // (which — since a rejected audio.play() fires neither ended nor error —
      // is sometimes the only thing that unsticks the playingKey guard).
      let myTimer: number | undefined;
      const done = () => {
        if (myTimer !== undefined) {
          window.clearTimeout(myTimer);
          if (safetyTimerRef.current === myTimer) safetyTimerRef.current = undefined;
          myTimer = undefined;
        }
        if (!mountedRef.current) return;
        setPlayingKey((k) => (k === flat.key ? undefined : k));
      };
      try {
        const url = await resolveClipObjectUrl(mx, flat.clip.url);
        const vol = (flat.clip.volume / 100) * master;
        callEmbed.control.injectAudio(url, vol);
        const audio = playClipLocally(url, vol);
        if (audio) {
          audio.addEventListener('ended', done, { once: true });
          audio.addEventListener('error', done, { once: true });
          // Safety: clear the guard even if the audio never signals end.
          myTimer = window.setTimeout(done, 30_000);
          safetyTimerRef.current = myTimer;
        } else {
          done();
        }
      } catch {
        setError('Could not play that clip.');
        done();
      }
    },
    [mx, callEmbed, master, playingKey],
  );

  return (
    <PopOut
      anchor={cords}
      position="Top"
      align="Center"
      content={
        <FocusTrap
          focusTrapOptions={{
            initialFocus: false,
            onDeactivate: () => setCords(undefined),
            clickOutsideDeactivates: true,
            escapeDeactivates: stopPropagation,
          }}
        >
          <Menu
            style={{
              maxWidth: `min(${manage ? toRem(420) : toRem(340)}, calc(100vw - 2 * ${config.space.S400}))`,
              maxHeight: '70vh',
            }}
          >
            <Box direction="Column" style={{ maxHeight: '70vh' }}>
              <Box
                shrink="No"
                alignItems="Center"
                justifyContent="SpaceBetween"
                gap="200"
                style={{
                  padding: config.space.S200,
                  borderBottom: `${config.borderWidth.B300} solid ${color.Surface.ContainerLine}`,
                }}
              >
                <Text size="L400">Soundboard</Text>
                <Box as="label" alignItems="Center" gap="200" style={{ cursor: 'pointer' }}>
                  <Text size="T200" priority="300">
                    Manage
                  </Text>
                  <Switch variant="Primary" value={manage} onChange={setManage} />
                </Box>
              </Box>

              <Scroll size="300" hideTrack visibility="Hover">
                <Box direction="Column" gap="300" style={{ padding: config.space.S200 }}>
                  {manage ? (
                    <>
                      <RoomSoundboardPack room={room} stateKey="" />
                      <UserSoundboardPack />
                    </>
                  ) : (
                    <>
                      {groups.length === 0 && (
                        <Text size="T200" priority="300">
                          No soundboard clips here yet. Turn on <b>Manage</b> to upload some, or add
                          a pack in Space settings.
                        </Text>
                      )}
                      {groups.map((g) => (
                        <Box key={g.id} direction="Column" gap="100">
                          <Text size="L400">{g.name}</Text>
                          <Box wrap="Wrap" gap="200">
                            {g.clips.map((clip) => {
                              const key = `${g.id}|${clip.shortcode}`;
                              const flat: FlatClip = {
                                key,
                                packId: g.id,
                                packName: g.name,
                                clip,
                              };
                              return (
                                <Box
                                  key={key}
                                  as="button"
                                  direction="Column"
                                  alignItems="Center"
                                  justifyContent="Center"
                                  gap="100"
                                  disabled={!!playingKey}
                                  onClick={() => play(flat)}
                                  aria-label={`Play ${clip.name}`}
                                  style={{
                                    width: toRem(76),
                                    minHeight: toRem(76),
                                    height: 'auto',
                                    padding: config.space.S100,
                                    borderRadius: config.radii.R400,
                                    border: `${config.borderWidth.B300} solid ${color.Surface.ContainerLine}`,
                                    background:
                                      playingKey === key
                                        ? color.Primary.Container
                                        : color.SurfaceVariant.Container,
                                    cursor: playingKey ? 'default' : 'pointer',
                                    opacity: playingKey && playingKey !== key ? 0.5 : 1,
                                  }}
                                >
                                  <Text size="H4">
                                    {playingKey === key ? (
                                      <Spinner size="200" />
                                    ) : (
                                      clip.emoji || '🔊'
                                    )}
                                  </Text>
                                  <Text
                                    size="T200"
                                    style={{
                                      maxWidth: '100%',
                                      textAlign: 'center',
                                      wordBreak: 'break-word',
                                      lineHeight: 1.15,
                                      display: '-webkit-box',
                                      WebkitLineClamp: 3,
                                      WebkitBoxOrient: 'vertical',
                                      overflow: 'hidden',
                                    }}
                                  >
                                    {clip.name}
                                  </Text>
                                </Box>
                              );
                            })}
                          </Box>
                        </Box>
                      ))}
                    </>
                  )}
                  {error && (
                    <Text size="T200" style={{ color: color.Critical.Main }}>
                      {error}
                    </Text>
                  )}
                </Box>
              </Scroll>
            </Box>
          </Menu>
        </FocusTrap>
      }
    >
      <TooltipProvider
        tooltip={
          <Tooltip>
            <Text>Soundboard</Text>
          </Tooltip>
        }
      >
        {(triggerRef) => (
          <IconButton
            ref={triggerRef}
            variant="Surface"
            fill="Soft"
            radii="400"
            size="400"
            onClick={handleOpen}
            outlined
            aria-label="Soundboard"
            aria-expanded={!!cords}
            aria-haspopup="menu"
          >
            <Icon size="400" src={Icons.BellRing} />
          </IconButton>
        )}
      </TooltipProvider>
    </PopOut>
  );
}
