import React, { useCallback, useEffect, useMemo, useState } from 'react';
import FocusTrap from 'focus-trap-react';
import {
  Dialog,
  Overlay,
  OverlayCenter,
  OverlayBackdrop,
  Header,
  config,
  Box,
  Text,
  IconButton,
  color,
  Button,
  Spinner,
  toRem,
} from 'folds';
import { MatrixClient, MatrixError, Room } from 'matrix-js-sdk';
import { Icon, Icons } from '../icons';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { AsyncStatus, useAsyncCallback } from '../../hooks/useAsyncCallback';
import { stopPropagation } from '../../utils/keyboard';
import { getSpaceChildren, getStateEvent, isSpace } from '../../utils/room';
import { Membership, StateEvent } from '../../../types/matrix/room';
import { fetchAllHierarchyRooms } from '../../hooks/useSpaceHierarchy';
import { getRoomCreatorsForRoomId } from '../../hooks/useRoomCreators';
import { getRoomPermissionsAPI } from '../../hooks/useRoomPermissions';
import { IPowerLevels } from '../../hooks/usePowerLevels';

const REMOVE_DELAY_MS = 100;
const PREVIEW_LIMIT = 20;

export type InaccessibleSpaceChild = {
  parentId: string;
  roomId: string;
};

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

function canEditSpaceChildren(mx: MatrixClient, spaceRoom: Room): boolean {
  const creators = getRoomCreatorsForRoomId(mx, spaceRoom.roomId);
  const powerLevelsEvent = getStateEvent(spaceRoom, StateEvent.RoomPowerLevels);
  const powerLevels = (powerLevelsEvent?.getContent() ?? {}) as IPowerLevels;
  const permissions = getRoomPermissionsAPI(creators, powerLevels);
  return permissions.stateEvent(StateEvent.SpaceChild, mx.getSafeUserId());
}

export type InaccessibleScanResult = {
  inaccessible: InaccessibleSpaceChild[];
  /** Total valid m.space.child entries walked (editable spaces only). */
  childCount: number;
  /** Children skipped because the user is joined to them. */
  joinedCount: number;
  /** Unjoined children that hierarchy can still summarize (Joinable / not Inaccessible). */
  joinableCount: number;
};

/**
 * Collects space children that appear as Unknown/Inaccessible in the lobby.
 * Matches lobby getRoom (joined-only): not joined + no hierarchy summary.
 * Walks nested subspaces the user can edit.
 */
export async function collectInaccessibleSpaceChildren(
  mx: MatrixClient,
  rootSpaceId: string
): Promise<InaccessibleScanResult> {
  let hierarchySummaries = new Map<string, { room_id: string }>();
  try {
    const hierarchyRooms = await fetchAllHierarchyRooms(mx, rootSpaceId, 3);
    hierarchySummaries = new Map(hierarchyRooms.map((room) => [room.room_id, room]));
  } catch {
    // Hierarchy often fails for restricted spaces; treat missing summaries as inaccessible.
    hierarchySummaries = new Map();
  }

  const inaccessible: InaccessibleSpaceChild[] = [];
  const visited = new Set<string>();
  let childCount = 0;
  let joinedCount = 0;
  let joinableCount = 0;

  const walk = (spaceId: string) => {
    if (visited.has(spaceId)) return;
    visited.add(spaceId);

    const spaceRoom = mx.getRoom(spaceId);
    if (!spaceRoom || spaceRoom.getMyMembership() !== Membership.Join) return;
    if (!canEditSpaceChildren(mx, spaceRoom)) return;

    for (const childId of getSpaceChildren(spaceRoom)) {
      childCount += 1;
      const childRoom = mx.getRoom(childId);
      // Lobby uses joined-only getRoom; left/invite rooms still render as Inaccessible.
      const joined = childRoom?.getMyMembership() === Membership.Join;

      if (joined && childRoom && isSpace(childRoom)) {
        joinedCount += 1;
        walk(childId);
        continue;
      }

      if (joined) {
        joinedCount += 1;
        continue;
      }

      const hasHierarchySummary = hierarchySummaries.has(childId);
      if (hasHierarchySummary) {
        joinableCount += 1;
        continue;
      }

      inaccessible.push({ parentId: spaceId, roomId: childId });
    }
  };

  walk(rootSpaceId);
  return { inaccessible, childCount, joinedCount, joinableCount };
}

async function removeSpaceChildrenSequentially(
  mx: MatrixClient,
  children: InaccessibleSpaceChild[],
  onProgress?: (current: number, total: number) => void
): Promise<void> {
  const total = children.length;

  for (let i = 0; i < children.length; i += 1) {
    const { parentId, roomId } = children[i];
    onProgress?.(i, total);

    try {
      await mx.sendStateEvent(parentId, StateEvent.SpaceChild as any, {}, roomId);
      await delay(REMOVE_DELAY_MS);
    } catch {
      // Continue removing others even if one fails
    }
  }

  onProgress?.(total, total);
}

type RemoveInaccessiblePromptProps = {
  roomId: string;
  onDone: () => void;
  onCancel: () => void;
};

export function RemoveInaccessiblePrompt({
  roomId,
  onDone,
  onCancel,
}: RemoveInaccessiblePromptProps) {
  const mx = useMatrixClient();
  const [progress, setProgress] = useState<{ current: number; total: number } | null>(null);
  const [scanResult, setScanResult] = useState<InaccessibleScanResult | null>(null);

  const [scanState, scan] = useAsyncCallback<InaccessibleScanResult, MatrixError | Error, []>(
    useCallback(async () => collectInaccessibleSpaceChildren(mx, roomId), [mx, roomId])
  );

  const children = scanResult?.inaccessible ?? null;

  const [removeState, removeChildren] = useAsyncCallback<undefined, MatrixError | Error, []>(
    useCallback(async () => {
      if (!children || children.length === 0) return;
      await removeSpaceChildrenSequentially(mx, children, (current, total) => {
        setProgress({ current, total });
      });
      setProgress(null);
    }, [mx, children])
  );

  useEffect(() => {
    scan();
  }, [scan]);

  useEffect(() => {
    if (scanState.status === AsyncStatus.Success) {
      setScanResult(scanState.data);
    }
  }, [scanState]);

  useEffect(() => {
    if (removeState.status === AsyncStatus.Success) {
      onDone();
    }
  }, [removeState, onDone]);

  const previewIds = useMemo(() => (children ?? []).slice(0, PREVIEW_LIMIT).map((c) => c.roomId), [children]);
  const isScanning = scanState.status === AsyncStatus.Loading || scanState.status === AsyncStatus.Idle;
  const isRemoving = removeState.status === AsyncStatus.Loading;
  const count = children?.length ?? 0;

  const getButtonText = () => {
    if (isRemoving && progress) return `Removing: ${progress.current}/${progress.total}`;
    if (isRemoving) return 'Removing...';
    if (count === 0) return 'Nothing to Remove';
    return `Remove ${count} Room${count === 1 ? '' : 's'}`;
  };

  return (
    <Overlay open backdrop={<OverlayBackdrop />}>
      <OverlayCenter>
        <FocusTrap
          focusTrapOptions={{
            initialFocus: false,
            onDeactivate: onCancel,
            clickOutsideDeactivates: true,
            escapeDeactivates: stopPropagation,
          }}
        >
          <Dialog variant="Surface">
            <Header
              style={{
                padding: `0 ${config.space.S200} 0 ${config.space.S400}`,
                borderBottomWidth: config.borderWidth.B300,
              }}
              variant="Surface"
              size="500"
            >
              <Box grow="Yes">
                <Text size="H4">Remove Inaccessible</Text>
              </Box>
              <IconButton size="300" onClick={onCancel} radii="300" disabled={isRemoving}>
                <Icon src={Icons.Cross} />
              </IconButton>
            </Header>
            <Box style={{ padding: config.space.S400 }} direction="Column" gap="400">
              <Box direction="Column" gap="200">
                {isScanning && (
                  <Box alignItems="Center" gap="200">
                    <Spinner size="200" />
                    <Text priority="400">Scanning space hierarchy for inaccessible rooms…</Text>
                  </Box>
                )}
                {scanState.status === AsyncStatus.Error && (
                  <Text style={{ color: color.Critical.Main }} size="T300">
                    Failed to scan space. {scanState.error.message}
                  </Text>
                )}
                {scanState.status === AsyncStatus.Success && count === 0 && (
                  <Box direction="Column" gap="100">
                    <Text priority="400">No inaccessible rooms found in this space.</Text>
                    {scanResult && (
                      <Text size="T200" priority="300">
                        Scanned {scanResult.childCount} child
                        {scanResult.childCount === 1 ? '' : 'ren'}: {scanResult.joinedCount} joined,{' '}
                        {scanResult.joinableCount} joinable via hierarchy.
                      </Text>
                    )}
                  </Box>
                )}
                {scanState.status === AsyncStatus.Success && count > 0 && (
                  <>
                    <Text priority="400">
                      Remove {count} inaccessible room{count === 1 ? '' : 's'} from this space? This
                      only unlinks them from the space; it does not leave or delete rooms.
                    </Text>
                    <Box
                      direction="Column"
                      gap="100"
                      style={{
                        maxHeight: toRem(200),
                        overflow: 'auto',
                        padding: config.space.S200,
                        backgroundColor: color.SurfaceVariant.Container,
                        borderRadius: config.radii.R400,
                      }}
                    >
                      {previewIds.map((id) => (
                        <Text key={id} size="T200" style={{ wordBreak: 'break-all' }}>
                          {id}
                        </Text>
                      ))}
                      {count > PREVIEW_LIMIT && (
                        <Text size="T200" priority="300">
                          …and {count - PREVIEW_LIMIT} more
                        </Text>
                      )}
                    </Box>
                  </>
                )}
                {removeState.status === AsyncStatus.Error && (
                  <Text style={{ color: color.Critical.Main }} size="T300">
                    Failed to remove some rooms. {removeState.error.message}
                  </Text>
                )}
              </Box>
              <Button
                type="submit"
                variant="Critical"
                onClick={() => removeChildren()}
                before={
                  isRemoving || isScanning ? (
                    <Spinner fill="Solid" variant="Critical" size="200" />
                  ) : undefined
                }
                disabled={
                  isScanning ||
                  isRemoving ||
                  count === 0 ||
                  removeState.status === AsyncStatus.Success
                }
              >
                <Text size="B400">{getButtonText()}</Text>
              </Button>
            </Box>
          </Dialog>
        </FocusTrap>
      </OverlayCenter>
    </Overlay>
  );
}
