import React, { useCallback } from 'react';
import { EventType, MatrixError, Preset, Visibility } from 'matrix-js-sdk';
import { useTranslation } from 'react-i18next';
import { Box, Button, Icon, Icons, Spinner, Text, color } from 'folds';
import type { BotPreset } from '../../../features/bots/catalog';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { Membership } from '../../../../types/matrix/room';
import { addRoomIdToMDirect, removeRoomIdFromMDirect } from '../../../utils/matrix';
import { isBotControlRoom } from '../../../features/bots/room';
import { BotStatePage } from './BotStatePage';

type BotNotConnectedProps = {
  preset: BotPreset;
};

type MDirectMap = Record<string, string[]>;

const DIRECT_ACCOUNT_DATA = EventType.Direct;

export function BotNotConnected({ preset }: BotNotConnectedProps) {
  const { t } = useTranslation();
  const mx = useMatrixClient();

  const [connectState, connect] = useAsyncCallback<string, Error | MatrixError, []>(
    useCallback(async () => {
      // Walk m.direct[bot.mxid] and decide what to do with each pointer:
      //   - Active strict control DM (Join/Invite, only self + bot): reuse.
      //     Portal/group rooms are ignored even if broken m.direct account
      //     data points at them.
      //     useBotRoom will pick real control rooms up via reactive listeners
      //     and BotExperienceHost will swap us out of this <BotNotConnected />.
      //   - Dead (Leave/Ban/null/unknown): drop the pointer. Private DMs
      //     created with TrustedPrivateChat cannot be rejoined unilaterally
      //     (server returns 403 «You are not invited»), so the only way
      //     forward is to forget the dead room and mint a new control DM.
      //     The forgotten room is invisible from the SDK's joined-rooms atom,
      //     so it won't surface in /direct/ either.
      const mDirect = (mx.getAccountData(DIRECT_ACCOUNT_DATA)?.getContent() ?? {}) as MDirectMap;
      const existingRooms = (mDirect[preset.mxid] ?? []).map((roomId) => {
        const room = mx.getRoom(roomId);
        return { roomId, room, myMembership: room?.getMyMembership() };
      });

      const deadRooms = existingRooms.filter(
        ({ room, myMembership }) =>
          !room || (myMembership !== Membership.Join && myMembership !== Membership.Invite)
      );
      if (deadRooms.length > 0) {
        // Null room (purged from store), Leave, Ban, or unknown — all dead.
        // Drop the pointer so this loop doesn't keep finding it forever.
        await Promise.all(deadRooms.map(({ roomId }) => removeRoomIdFromMDirect(mx, roomId)));
        deadRooms.forEach(({ room, myMembership, roomId }) => {
          if (room && myMembership === Membership.Leave) {
            // Forget so the SDK can fully GC; no-op if forget fails.
            mx.forget(roomId).catch(() => undefined);
          }
        });
      }

      const reusableRoom = existingRooms.find(({ room, myMembership }) => {
        if (!room || (myMembership !== Membership.Join && myMembership !== Membership.Invite)) {
          return false;
        }
        const botMembership = room.getMember(preset.mxid)?.membership;
        return (
          (botMembership === Membership.Join || botMembership === Membership.Invite) &&
          isBotControlRoom(mx, room, preset)
        );
      });
      if (reusableRoom) {
        return reusableRoom.roomId;
      }

      const { room_id: roomId } = await mx.createRoom({
        is_direct: true,
        invite: [preset.mxid],
        visibility: Visibility.Private,
        preset: Preset.TrustedPrivateChat,
      });

      await addRoomIdToMDirect(mx, roomId, preset.mxid);
      return roomId;
    }, [mx, preset])
  );

  const loading = connectState.status === AsyncStatus.Loading;
  const error = connectState.status === AsyncStatus.Error ? connectState.error : undefined;

  const handleConnect = () => {
    connect().catch(() => undefined);
  };

  return (
    <BotStatePage
      icon={<Icon size="600" src={Icons.Message} />}
      title={t('Bots.not_connected_title', { name: preset.name })}
      description={t('Bots.not_connected_description', { mxid: preset.mxid })}
    >
      {error && (
        <Box style={{ color: color.Critical.Main }} alignItems="Center" gap="200">
          <Icon src={Icons.Warning} filled size="100" />
          <Text size="T300" style={{ color: color.Critical.Main }}>
            <b>{error.message || t('Bots.connect_error')}</b>
          </Text>
        </Box>
      )}
      <Button
        variant="Primary"
        size="500"
        radii="400"
        onClick={handleConnect}
        disabled={loading}
        before={loading && <Spinner variant="Primary" fill="Solid" size="200" />}
      >
        <Text size="B500">{t('Bots.connect')}</Text>
      </Button>
    </BotStatePage>
  );
}
