import { useEffect, useCallback } from 'react';
import { MatrixClient, Room, RoomEvent } from 'matrix-js-sdk';
import { IHierarchyRoom } from 'matrix-js-sdk/lib/@types/spaces';
import { useAtomValue, useSetAtom } from 'jotai';
import { Membership } from '../../types/matrix/room';
import { isSpace } from '../utils/room';
import { settingsAtom } from '../state/settings';
import {
  setRoomNotificationPreference,
  RoomNotificationMode,
} from './useRoomsNotificationPreferences';
import { updateJoiningProgressAtom } from '../state/joiningProgress';
import { useMatrixClient } from './useMatrixClient';
import { fetchAllHierarchyRooms } from './useSpaceHierarchy';

const JOIN_DELAY_MS = 500;

/** Join rules that allow automatic joining */
const AUTO_JOINABLE_RULES = ['public', 'restricted', 'knock_restricted'];

/** Spaces we've already kicked off auto-join for this session (avoid repeat on revisit). */
const autoJoinStartedForSpace = new Set<string>();

/** Spaces currently running auto-join (avoid concurrent runs). */
const processingSpaces = new Set<string>();

function getServerFromRoomId(roomId: string): string {
  const parts = roomId.split(':');
  return parts.length > 1 ? parts[1] : '';
}

function canAutoJoin(hierarchyRoom: IHierarchyRoom): boolean {
  const { join_rule: joinRule } = hierarchyRoom;
  if (!joinRule) {
    return false;
  }
  return AUTO_JOINABLE_RULES.includes(joinRule);
}

function isHierarchySpace(hierarchyRoom: IHierarchyRoom): boolean {
  return hierarchyRoom.room_type === 'm.space' || hierarchyRoom.room_type === 'm.forum';
}

function delay(ms: number): Promise<void> {
  return new Promise<void>((resolve) => {
    setTimeout(resolve, ms);
  });
}

type ProgressCallback = (current: number, total: number) => void;

async function joinRoomsSequentially(
  mx: MatrixClient,
  rooms: IHierarchyRoom[],
  spaceId: string,
  onProgress?: ProgressCallback
): Promise<void> {
  const spaceServer = getServerFromRoomId(spaceId);

  const itemsToJoin = rooms.filter((room) => {
    const existingRoom = mx.getRoom(room.room_id);
    if (existingRoom?.getMyMembership() === Membership.Join) {
      return false;
    }
    return canAutoJoin(room);
  });

  const spacesToJoin = itemsToJoin.filter(isHierarchySpace);
  const roomsToJoin = itemsToJoin.filter((r) => !isHierarchySpace(r));

  const total = itemsToJoin.length;
  let current = 0;

  if (total > 0) {
    onProgress?.(current, total);
  }

  const joinItem = async (item: IHierarchyRoom, isItemSpace: boolean): Promise<void> => {
    try {
      const itemServer = getServerFromRoomId(item.room_id);
      const viaServers = [itemServer, spaceServer].filter(Boolean);

      await mx.joinRoom(item.room_id, { viaServers });

      if (!isItemSpace) {
        await setRoomNotificationPreference(
          mx,
          item.room_id,
          RoomNotificationMode.SpecialMessages,
          RoomNotificationMode.Unset
        );
      }

      await delay(JOIN_DELAY_MS);
    } catch {
      // Continue with other items even if one fails
    } finally {
      current += 1;
      onProgress?.(current, total);
    }
  };

  await spacesToJoin.reduce(
    (promise, item) => promise.then(() => joinItem(item, true)),
    Promise.resolve()
  );

  await roomsToJoin.reduce(
    (promise, item) => promise.then(() => joinItem(item, false)),
    Promise.resolve()
  );
}

/** Joins all auto-joinable children of a space (and nested spaces/rooms). */
export async function autoJoinSpaceChildren(
  mx: MatrixClient,
  spaceId: string,
  onProgress?: ProgressCallback
): Promise<void> {
  if (processingSpaces.has(spaceId)) {
    return;
  }

  processingSpaces.add(spaceId);

  try {
    const hierarchyRooms = await fetchAllHierarchyRooms(mx, spaceId, undefined);
    const childRooms = hierarchyRooms.filter((r) => r.room_id !== spaceId);
    await joinRoomsSequentially(mx, childRooms, spaceId, onProgress);
  } finally {
    processingSpaces.delete(spaceId);
  }
}

/** Hook that automatically joins all accessible rooms when joining a space. */
export function useAutoJoinSpaceRooms(mx: MatrixClient): void {
  const settings = useAtomValue(settingsAtom);
  const setJoiningProgress = useSetAtom(updateJoiningProgressAtom);

  const handleSpaceJoin = useCallback(
    async (room: Room) => {
      if (!settings.autoJoinSpaceRooms) {
        return;
      }
      if (!isSpace(room)) {
        return;
      }
      if (room.getMyMembership() !== Membership.Join) {
        return;
      }

      const spaceId = room.roomId;

      try {
        await autoJoinSpaceChildren(mx, spaceId, (current, total) => {
          setJoiningProgress({ spaceId, progress: { current, total } });
        });
      } finally {
        setJoiningProgress({ spaceId, progress: null });
      }
    },
    [mx, settings.autoJoinSpaceRooms, setJoiningProgress]
  );

  useEffect(() => {
    const onMembershipChange = (room: Room, membership: string) => {
      if (membership === Membership.Join) {
        handleSpaceJoin(room);
      }
    };

    mx.on(RoomEvent.MyMembership, onMembershipChange);

    return () => {
      mx.removeListener(RoomEvent.MyMembership, onMembershipChange);
    };
  }, [mx, handleSpaceJoin]);
}

/**
 * Auto-joins space children when visiting an already-joined space.
 * Deferred so opening a space stays responsive.
 */
export function useAutoJoinOnSpaceVisit(space: Room | undefined): void {
  const mx = useMatrixClient();
  const settings = useAtomValue(settingsAtom);
  const setJoiningProgress = useSetAtom(updateJoiningProgressAtom);

  useEffect(() => {
    if (!space || !settings.autoJoinSpaceRooms) {
      return undefined;
    }
    if (!isSpace(space)) {
      return undefined;
    }
    if (space.getMyMembership() !== Membership.Join) {
      return undefined;
    }

    const spaceId = space.roomId;
    if (autoJoinStartedForSpace.has(spaceId)) {
      return undefined;
    }

    let cancelled = false;
    let idleId: number | undefined;
    let timeoutId: number | undefined;

    const run = () => {
      if (cancelled || autoJoinStartedForSpace.has(spaceId)) return;
      autoJoinStartedForSpace.add(spaceId);

      void (async () => {
        try {
          await autoJoinSpaceChildren(mx, spaceId, (current, total) => {
            if (!cancelled) {
              setJoiningProgress({ spaceId, progress: { current, total } });
            }
          });
        } finally {
          if (!cancelled) {
            setJoiningProgress({ spaceId, progress: null });
          }
        }
      })();
    };

    if (typeof window.requestIdleCallback === 'function') {
      idleId = window.requestIdleCallback(run, { timeout: 4000 });
    } else {
      timeoutId = window.setTimeout(run, 2000);
    }

    return () => {
      cancelled = true;
      if (idleId !== undefined && typeof window.cancelIdleCallback === 'function') {
        window.cancelIdleCallback(idleId);
      }
      if (timeoutId !== undefined) {
        window.clearTimeout(timeoutId);
      }
    };
  }, [space, settings.autoJoinSpaceRooms, mx, setJoiningProgress]);
}
