import {
  type Comparator,
  combineComparators,
  conditional,
  hasScreenShare,
  type StreamVideoParticipant,
  screenSharing,
  speaking,
  type VideoTrackType,
  VisibilityState,
} from '@stream-io/video-react-sdk';

import {
  type CallBroadcastChannelMessage,
  type InitCallProperties,
} from 'src/types/stream';
import { getInitials } from 'src/utils/userUtils';

import { BROADCAST_CHANNEL_EVENTS } from './constants';

export const getTrackType = (
  participant: StreamVideoParticipant,
): VideoTrackType => {
  if (hasScreenShare(participant)) {
    return 'screenShareTrack';
  }
  return 'videoTrack';
};

export const getCallLayoutGridColumns = (totalParticipants: number) => {
  const baseColumns = {
    xs: 12,
    sm: 6,
    md: 4,
  };

  let lg = 12;

  if ([2, 3, 4].includes(totalParticipants)) {
    lg = 6;
  } else if ([5, 6, 9].includes(totalParticipants)) {
    lg = 4;
  } else if ([7, 8, 10, 11, 12].includes(totalParticipants)) {
    lg = 3;
  }

  return {
    ...baseColumns,
    lg,
  };
};

export const getCallLayoutGridItemHeight = (
  totalParticipants: number,
  columns: number,
) => {
  const rows = Math.ceil(totalParticipants / columns);
  return `calc((100% / ${rows}) - ${rows - 1}px)`;
};

export const getMaxWidthCallContainer = (
  heightBreakpoints: {
    sm: boolean;
    md: boolean;
    lg: boolean;
  },
  isScreenSharing: boolean,
) => {
  if (isScreenSharing) return '1450px';
  if (heightBreakpoints.sm) return '1070px';
  if (heightBreakpoints.md) return '1344px';
  return '1450px';
};

export const getScreenShareSideParticipantHeight = (
  totalParticipants: number,
  maxParticipants: number,
): string => {
  const totalSlots = Math.min(maxParticipants, totalParticipants);
  const spacing = 8;

  const slotHeight = `calc((100% - ${(totalSlots - 1) * spacing}px) / ${totalSlots})`;

  return slotHeight;
};

export const getCallLayoutAspectRatio = (
  totalParticipants: number,
  isScreenSharing: boolean,
) => {
  return [3, 4, 9].includes(totalParticipants) && !isScreenSharing
    ? '21 / 9'
    : '4 / 3';
};

export const getParticipantNames = (participants: StreamVideoParticipant[]) => {
  const displayNames = participants
    .slice(0, 10)
    .map(participant => participant.name.split(' ')[0]);

  const extraCount = participants.length - displayNames.length;

  if (extraCount > 0) {
    return `${displayNames.join(', ')}, +${extraCount}`;
  }

  return displayNames.join(', ');
};

export const getAvatarsFromParticipants = (
  streamParticipants: StreamVideoParticipant[],
) => {
  return streamParticipants.map((participant, index) => {
    return {
      src: participant.image,
      text: getInitials(participant.name),
      key: index,
    };
  });
};

export const currentUserLastComparator: Comparator<StreamVideoParticipant> = (
  a,
  b,
) => {
  if (a.isLocalParticipant && !b.isLocalParticipant) return 1;
  if (!a.isLocalParticipant && b.isLocalParticipant) return -1;
  return 0;
};

const ifInvisibleBy = conditional(
  (a: StreamVideoParticipant, b: StreamVideoParticipant) =>
    a.viewportVisibilityState?.videoTrack !== VisibilityState.VISIBLE ||
    b.viewportVisibilityState?.videoTrack !== VisibilityState.VISIBLE,
);

const joinedAtAscendingComparator: Comparator<StreamVideoParticipant> = (
  a,
  b,
) => {
  const aSec = a.joinedAt?.seconds;
  const bSec = b.joinedAt?.seconds;
  const aHas = aSec !== undefined && aSec !== '';
  const bHas = bSec !== undefined && bSec !== '';

  if (!aHas && !bHas) {
    return 0;
  }
  if (!aHas) {
    return 1;
  }
  if (!bHas) {
    return -1;
  }

  const aSecNum = Number(aSec);
  const bSecNum = Number(bSec);
  if (aSecNum < bSecNum) {
    return -1;
  }
  if (aSecNum > bSecNum) {
    return 1;
  }

  const aNano = a.joinedAt?.nanos ?? 0;
  const bNano = b.joinedAt?.nanos ?? 0;
  if (aNano < bNano) {
    return -1;
  }
  if (aNano > bNano) {
    return 1;
  }
  return 0;
};

export const createSpeakingWithThreshold = (
  isSpeakingOverThreshold: (sessionId: string) => boolean,
): Comparator<StreamVideoParticipant> => {
  return (a, b) => {
    const aSpeaking = isSpeakingOverThreshold(a.sessionId);
    const bSpeaking = isSpeakingOverThreshold(b.sessionId);
    if (aSpeaking && !bSpeaking) return -1;
    if (!aSpeaking && bSpeaking) return 1;
    return 0;
  };
};

export const createGroupCallComparator = (
  isSpeakingOverThreshold: (sessionId: string) => boolean,
) =>
  combineComparators(
    currentUserLastComparator,
    ifInvisibleBy(createSpeakingWithThreshold(isSpeakingOverThreshold)),
    joinedAtAscendingComparator,
  );

export const screenSharingComparator = combineComparators(
  currentUserLastComparator,
  screenSharing,
  ifInvisibleBy(speaking),
  joinedAtAscendingComparator,
);

export const focusExistingCallTab = (callId: string): Promise<boolean> => {
  return new Promise(resolve => {
    const channel = new BroadcastChannel(callId);

    let responseReceived = false;

    channel.onmessage = (event: MessageEvent<CallBroadcastChannelMessage>) => {
      if (
        event.data.type === BROADCAST_CHANNEL_EVENTS.FOCUS_ACK &&
        event.data.callId === callId
      ) {
        responseReceived = true;
        channel.close();
        resolve(true);
      }
    };

    channel.postMessage({
      type: BROADCAST_CHANNEL_EVENTS.FOCUS_REQUEST,
      callId,
    } satisfies CallBroadcastChannelMessage);

    setTimeout(() => {
      if (!responseReceived) {
        channel.close();
        resolve(false);
      }
    }, 500);
  });
};

export const listenForWindowFocusRequest = (callId: string) => {
  const channel = new BroadcastChannel(callId);

  channel.onmessage = (event: MessageEvent<CallBroadcastChannelMessage>) => {
    if (
      event.data.type === BROADCAST_CHANNEL_EVENTS.FOCUS_REQUEST &&
      event.data.callId === callId
    ) {
      window.focus();

      channel.postMessage({
        type: BROADCAST_CHANNEL_EVENTS.FOCUS_ACK,
        callId,
      } satisfies CallBroadcastChannelMessage);
    }
  };

  return () => {
    channel.close();
  };
};

export const buildIncomingCallPopupUrl = (callProps: InitCallProperties) => {
  const params = new URLSearchParams({
    callId: callProps.id,
    providerCallId: callProps.providerCallId,
    creatorId: String(callProps.creator.id),
    creatorName: callProps.creator.name || '',
    creatorFullName: callProps.creator.fullName || '',
    creatorPicture: callProps.creator.profilePicture || '',
    cameraEnabled: String(callProps.initializationConfig.cameraEnabled),
    isGroupCall: String(callProps.isGroupCall || false),
    joinedUserNames: JSON.stringify(callProps.joinedUserNames || []),
  });

  return `/incoming-call?${params.toString()}`;
};

export const parseIncomingCallParams = (
  searchParams: URLSearchParams,
): InitCallProperties | null => {
  const callId = searchParams.get('callId');
  const providerCallId = searchParams.get('providerCallId');

  if (!callId || !providerCallId) return null;

  return {
    id: callId,
    providerCallId,
    initializationConfig: {
      cameraEnabled: searchParams.get('cameraEnabled') === 'true',
    },
    creator: {
      id: Number(searchParams.get('creatorId')),
      name: searchParams.get('creatorName') || '',
      fullName: searchParams.get('creatorFullName') || '',
      profilePicture: searchParams.get('creatorPicture') || null,
      isCalling: false,
    },
    isGroupCall: searchParams.get('isGroupCall') === 'true',
    joinedUserNames: JSON.parse(searchParams.get('joinedUserNames') || '[]'),
  };
};
