import React, { RefObject, useRef } from 'react';
import { useSetAtom } from 'jotai';
import { Badge, Box, Button, color, Header, Icon, Icons, Scroll, Text, toRem } from 'folds';
import {
  useCallEmbed,
  useCallJoined,
  useCallEmbedPlacementSync,
  useCallLoadError,
} from '../../hooks/useCallEmbed';
import { callEmbedAtom } from '../../state/callEmbed';
import { ContainerColor } from '../../styles/ContainerColor.css';
import { PrescreenControls } from './PrescreenControls';
import { usePowerLevelsContext } from '../../hooks/usePowerLevels';
import { useRoom } from '../../hooks/useRoom';
import { useRoomCreators } from '../../hooks/useRoomCreators';
import { useRoomPermissions } from '../../hooks/useRoomPermissions';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { StateEvent } from '../../../types/matrix/room';
import { useCallMembers, useCallSession } from '../../hooks/useCall';
import { useStateEvent } from '../../hooks/useStateEvent';
import { VoiceLimitContent } from '../common-settings/general/RoomVoiceLimit';
import { CallMemberRenderer } from './CallMemberCard';
import * as css from './styles.css';
import { CallControls } from './CallControls';
import { useLivekitSupport } from '../../hooks/useLivekitSupport';
import { webRTCSupported } from '../../utils/rtc';

function LivekitServerMissingMessage() {
  return (
    <Text style={{ margin: 'auto', color: color.Critical.Main }} size="L400" align="Center">
      Your homeserver does not support calling.
    </Text>
  );
}

function WebRTCMissingError() {
  return (
    <Text style={{ margin: 'auto', color: color.Critical.Main }} size="L400" align="Center">
      Your browser does not support WebRTC, which is required for calling.
    </Text>
  );
}

function JoinMessage({
  hasParticipant,
  livekitSupported,
  rtcSupported,
}: {
  hasParticipant?: boolean;
  livekitSupported?: boolean;
  rtcSupported?: boolean;
}) {
  if (rtcSupported === false) {
    return <WebRTCMissingError />;
  }

  if (livekitSupported === false) {
    return <LivekitServerMissingMessage />;
  }

  if (hasParticipant) return null;

  return (
    <Text style={{ margin: 'auto' }} size="L400" align="Center">
      Voice chat’s empty — Be the first to hop in!
    </Text>
  );
}

function NoPermissionMessage() {
  return (
    <Text style={{ margin: 'auto' }} size="L400" align="Center">
      You don&#39;t have permission to join!
    </Text>
  );
}

function AlreadyInCallMessage() {
  return (
    <Text style={{ margin: 'auto', color: color.Warning.Main }} size="L400" align="Center">
      Already in another call — End the current call to join!
    </Text>
  );
}

function ChannelFullMessage({ current, max }: { current: number; max: number }) {
  return (
    <Text style={{ margin: 'auto', color: color.Critical.Main }} size="L400" align="Center">
      Channel Full ({current}/{max}) — Wait for someone to leave before joining.
    </Text>
  );
}

function CallPrescreen() {
  const mx = useMatrixClient();
  const room = useRoom();
  const livekitSupported = useLivekitSupport();
  const rtcSupported = webRTCSupported();

  const powerLevels = usePowerLevelsContext();
  const creators = useRoomCreators(room);

  const permissions = useRoomPermissions(creators, powerLevels);
  const hasPermission = permissions.stateEvent(
    StateEvent.GroupCallMemberPrefix,
    mx.getSafeUserId(),
  );

  const callSession = useCallSession(room);
  const callMembers = useCallMembers(callSession);
  const hasParticipant = callMembers.length > 0;

  const callEmbed = useCallEmbed();
  const inOtherCall = callEmbed && callEmbed.roomId !== room.roomId;

  // Voice channel user limit (io.lotus.voice_limit). 0 / absent means no limit.
  const limitEvent = useStateEvent(room, StateEvent.LotusVoiceLimit);
  const maxUsers = limitEvent?.getContent<VoiceLimitContent>().max_users ?? 0;
  // A user already counted in the session is rejoining and should not be blocked.
  const alreadyMember = callMembers.some((m) => m.sender === mx.getSafeUserId());
  const channelFull = maxUsers > 0 && !alreadyMember && callMembers.length >= maxUsers;

  const canJoin = hasPermission && livekitSupported && rtcSupported && !channelFull;

  return (
    <Scroll variant="Surface" hideTrack>
      <Box className={css.CallViewContent} alignItems="Center" justifyContent="Center">
        <Box style={{ maxWidth: toRem(382), width: '100%' }} direction="Column" gap="100">
          {hasParticipant && (
            <Header size="300">
              <Box grow="Yes" alignItems="Center">
                <Text size="L400">Participant</Text>
              </Box>
              <Badge variant="Critical" fill="Solid" size="400">
                <Text as="span" size="L400" truncate>
                  {callMembers.length} Live
                </Text>
              </Badge>
            </Header>
          )}
          <CallMemberRenderer members={callMembers} />
          <PrescreenControls canJoin={canJoin} />
          <Box className={css.PrescreenMessage} alignItems="Center">
            {!inOtherCall && !hasPermission && <NoPermissionMessage />}
            {!inOtherCall && hasPermission && channelFull && (
              <ChannelFullMessage current={callMembers.length} max={maxUsers} />
            )}
            {!inOtherCall && hasPermission && !channelFull && (
              <JoinMessage
                hasParticipant={hasParticipant}
                livekitSupported={livekitSupported}
                rtcSupported={rtcSupported}
              />
            )}
            {inOtherCall && <AlreadyInCallMessage />}
          </Box>
        </Box>
      </Box>
    </Scroll>
  );
}

function CallLoadErrorMessage() {
  const setCallEmbed = useSetAtom(callEmbedAtom);

  // Disposing the embed tears down the hung iframe and returns the user to the
  // prescreen, from which they can join again ("Retry") or simply walk away.
  const dismiss = () => setCallEmbed(undefined);

  return (
    <Box
      className={css.CallViewContent}
      direction="Column"
      alignItems="Center"
      justifyContent="Center"
      gap="300"
    >
      <Icon src={Icons.Warning} size="400" style={{ color: color.Critical.Main }} />
      <Text style={{ color: color.Critical.Main }} size="L400" align="Center">
        The call failed to load. Check your connection and try again.
      </Text>
      <Box gap="200" alignItems="Center">
        <Button variant="Primary" size="300" radii="400" onClick={dismiss}>
          <Text size="B400">Retry</Text>
        </Button>
        <Button variant="Secondary" fill="Soft" size="300" radii="400" onClick={dismiss}>
          <Text size="B400">Leave</Text>
        </Button>
      </Box>
    </Box>
  );
}

type CallJoinedProps = {
  containerRef: RefObject<HTMLDivElement>;
  joined: boolean;
};
function CallJoined({ joined, containerRef }: CallJoinedProps) {
  const callEmbed = useCallEmbed();

  return (
    <Box grow="Yes" direction="Column">
      <Box grow="Yes" ref={containerRef} />
      {callEmbed && joined && <CallControls callEmbed={callEmbed} />}
    </Box>
  );
}

export function CallView() {
  const room = useRoom();
  const callContainerRef = useRef<HTMLDivElement>(null) as React.RefObject<HTMLDivElement>;
  useCallEmbedPlacementSync(callContainerRef);

  const callEmbed = useCallEmbed();
  const callJoined = useCallJoined(callEmbed);
  const loadError = useCallLoadError(callEmbed);

  const isCurrentRoom = callEmbed?.roomId === room.roomId;
  const currentJoined = isCurrentRoom && callJoined;
  // Show the recovery UI when this room's embed failed to load and we never
  // made it into the call (a hung iframe / blank spinner otherwise).
  const showLoadError = isCurrentRoom && !currentJoined && Boolean(loadError);

  return (
    <Box
      className={ContainerColor({ variant: 'Surface' })}
      style={{ minWidth: toRem(280) }}
      grow="Yes"
    >
      {showLoadError && <CallLoadErrorMessage />}
      {!currentJoined && !showLoadError && <CallPrescreen />}
      {!showLoadError && <CallJoined joined={currentJoined} containerRef={callContainerRef} />}
    </Box>
  );
}
