import React from 'react';
import { Box, Icon, Icons, Text, color } from 'folds';
import { MatrixEvent, Room } from 'matrix-js-sdk';
import { useTranslation } from 'react-i18next';
import {
  BubbleLayout,
  ChannelLayout,
  ChannelMessageAvatar,
  Time,
  Username,
  UsernameBold,
} from '../../../components/message';
import { Event } from './Message';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { getMemberDisplayName } from '../../../utils/room';
import { getMxIdLocalPart } from '../../../utils/matrix';
import { MemberPowerTag } from '../../../../types/matrix/room';
import colorMXID from '../../../../util/colorMXID';

export type CallAggregate = {
  callId: string;
  // earliest m.call.member event TS — used only for anchor placement; not
  // the conversation start (that excludes the ringing prelude).
  startTs: number;
  // latest event TS, or null while the call is still ongoing.
  endTs: number | null;
  // TS when both sides were first simultaneously joined. null if the call
  // was never answered.
  conversationStart: number | null;
  // TS when joined count last dropped from 2 → 1. Correct for 1:1 DMs (the
  // only place the call button is exposed today); for incidental group-room
  // calls it underestimates duration once a third party joins. null while
  // ongoing or when the call was never answered.
  conversationEnd: number | null;
  ongoing: boolean;
  // True if both sides joined the call at any point — set either when the
  // walk observed the 1→2 transition (connectedAt) or when ≥2 distinct
  // senders ever joined (pagination-edge fallback for windows that begin
  // mid-call).
  wasAnswered: boolean;
  // unique sender mxids that joined the call (any time)
  participants: string[];
  // first joiner — call initiator
  anchorSenderId: string;
  // Number of unsuccessful (missed/cancelled) sessions collapsed into this
  // bubble. 1 means a standalone call; >1 means a chain of retries from the
  // same caller within the merge window was folded together to reduce
  // chat-spam, like WhatsApp/iOS Recents. Answered/ongoing calls are never
  // merged and always have count 1.
  mergedCount: number;
};

export type CallMessageProps = {
  room: Room;
  mEvent: MatrixEvent;
  aggregate: CallAggregate;
  highlight: boolean;
  canDelete?: boolean;
  hideReadReceipts?: boolean;
  showDeveloperTools?: boolean;
  streamRailStart?: boolean;
  streamRailEnd?: boolean;
  layout?: 'stream' | 'channel';
  channelHeaderInBubble?: boolean;
  // Username coloring — mirrors `Message.tsx` so call bubbles match the
  // surrounding chat rhythm. In 1:1 DMs `legacyUsernameColor` is true and
  // the username is rendered in the mxid-hash colour; in group rooms it
  // uses the power-level tag colour.
  memberPowerTag?: MemberPowerTag;
  accessibleTagColors?: Map<string, string>;
  legacyUsernameColor?: boolean;
  // Spread onto Event wrapper (e.g. data-message-item, data-message-id)
  // so the virtual paginator can locate the row.
  [dataAttr: `data-${string}`]: string | number | undefined;
};

function formatDuration(t: ReturnType<typeof useTranslation>['t'], ms: number): string {
  const total = Math.max(0, Math.round(ms / 1000));
  const minutes = Math.floor(total / 60);
  const seconds = total % 60;
  if (minutes === 0) {
    return t('Call.duration_seconds', { seconds });
  }
  return t('Call.duration_minutes_seconds', { minutes, seconds });
}

export function CallMessage({
  room,
  mEvent,
  aggregate,
  highlight,
  canDelete,
  hideReadReceipts,
  showDeveloperTools,
  // Vestigial Stream-rail props — accepted but unused by the bubble/channel
  // layouts (pending the stream-rail cleanup).
  streamRailStart: _streamRailStart,
  streamRailEnd: _streamRailEnd,
  layout = 'stream',
  channelHeaderInBubble,
  memberPowerTag,
  accessibleTagColors,
  legacyUsernameColor,
  ...rest
}: CallMessageProps) {
  const { t } = useTranslation();
  const mx = useMatrixClient();

  const senderId = aggregate.anchorSenderId;
  const isOwnMessage = !!mx.getUserId() && senderId === mx.getUserId();
  const senderName = getMemberDisplayName(room, senderId) || getMxIdLocalPart(senderId) || senderId;

  const tagColor = memberPowerTag?.color
    ? accessibleTagColors?.get(memberPowerTag.color)
    : undefined;
  const usernameColor = legacyUsernameColor ? colorMXID(senderId) : tagColor;
  const usernameStyle = { color: usernameColor ?? color.Primary.Main };

  // Other side never joined → caller's view «Cancelled», callee's view «Missed».
  // When `mergedCount > 1` the bubble represents a chain of retries that
  // we collapsed at the timeline aggregation step; switch to the plural
  // _count keys so i18next picks the locale-aware form (en: one/other,
  // ru: one/few/many).
  const { wasAnswered, ongoing, mergedCount } = aggregate;
  let title: string;
  if (ongoing) {
    title = t('Call.bubble_ongoing');
  } else if (wasAnswered) {
    title = t(isOwnMessage ? 'Call.bubble_outgoing' : 'Call.bubble_incoming');
  } else if (mergedCount > 1) {
    title = t(isOwnMessage ? 'Call.bubble_cancelled_count' : 'Call.bubble_missed_count', {
      count: mergedCount,
    });
  } else {
    title = t(isOwnMessage ? 'Call.bubble_cancelled' : 'Call.bubble_missed');
  }

  const subtitle: string | null = (() => {
    if (ongoing) return t('Call.bubble_in_progress');
    if (!wasAnswered) return null;
    if (aggregate.conversationStart == null || aggregate.conversationEnd == null) return null;
    return formatDuration(t, aggregate.conversationEnd - aggregate.conversationStart);
  })();

  const iconSrc = wasAnswered || ongoing ? Icons.Phone : Icons.PhoneDown;
  let iconColor: string;
  if (ongoing) {
    iconColor = color.Success.Main;
  } else if (wasAnswered) {
    iconColor = color.Surface.OnContainer;
  } else {
    iconColor = color.Critical.Main;
  }

  const bubbleBody = (
    <Box gap="300" alignItems="Center" style={{ minWidth: 0 }}>
      <Icon src={iconSrc} size="300" style={{ color: iconColor, flexShrink: 0 }} />
      <Box direction="Column" style={{ minWidth: 0 }}>
        <Text as="span" size="T300" truncate>
          {title}
        </Text>
        {subtitle && (
          <Text as="span" size="T200" priority="300" truncate>
            {subtitle}
          </Text>
        )}
      </Box>
    </Box>
  );

  return (
    <Event
      key={mEvent.getId()}
      room={room}
      mEvent={mEvent}
      highlight={highlight}
      canDelete={canDelete}
      hideReadReceipts={hideReadReceipts}
      showDeveloperTools={showDeveloperTools}
      bubble={layout !== 'channel'}
      {...rest}
    >
      {layout === 'channel' ? (
        <ChannelLayout
          isOwn={isOwnMessage}
          headerInBubble={channelHeaderInBubble}
          avatar={
            <ChannelMessageAvatar room={room} senderId={senderId} senderDisplayName={senderName} />
          }
          header={
            <>
              <Username as="span" style={usernameStyle}>
                <Text as="span" size={channelHeaderInBubble ? 'T200' : 'T400'} truncate>
                  {/* Own events show the user's own nick too (not a «me» label). */}
                  <UsernameBold>{senderName}</UsernameBold>
                </Text>
              </Username>
              <Time ts={mEvent.getTs()} compact size="T200" priority="300" />
            </>
          }
        >
          {bubbleBody}
        </ChannelLayout>
      ) : (
        <BubbleLayout time={<Time ts={mEvent.getTs()} compact />} isOwn={isOwnMessage}>
          {bubbleBody}
        </BubbleLayout>
      )}
    </Event>
  );
}
