import type { RoomSummary } from './types';

export type RoomMode = 'AllMessages' | 'MentionsAndKeywordsOnly' | 'Mute';

export interface MutedSpaces {
  spaces: string[];
  applied: Record<string, Record<string, RoomMode | null>>;
}

export const NO_MUTED_SPACES: MutedSpaces = { spaces: [], applied: {} };

function isRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === 'object' && value !== null && !Array.isArray(value);
}

function isMode(value: unknown): value is RoomMode {
  return value === 'AllMessages' || value === 'MentionsAndKeywordsOnly' || value === 'Mute';
}

export function parseMutedSpaces(raw: unknown): MutedSpaces {
  if (!isRecord(raw)) return NO_MUTED_SPACES;
  const spaces = Array.isArray(raw['spaces']) ? [...new Set(raw['spaces'].filter((s): s is string => typeof s === 'string'))] : [];
  const applied: MutedSpaces['applied'] = {};
  if (isRecord(raw['applied'])) {
    for (const [spaceId, rooms] of Object.entries(raw['applied'])) {
      if (!spaces.includes(spaceId) || !isRecord(rooms)) continue;
      const entry: Record<string, RoomMode | null> = {};
      for (const [roomId, mode] of Object.entries(rooms)) if (mode === null || isMode(mode)) entry[roomId] = mode;
      applied[spaceId] = entry;
    }
  }
  return { spaces, applied };
}

function childMap(rooms: RoomSummary[]): Map<string, Set<string>> {
  const map = new Map<string, Set<string>>();
  const add = (space: string, child: string) => {
    let set = map.get(space);
    if (!set) map.set(space, (set = new Set()));
    set.add(child);
  };
  for (const room of rooms) {
    for (const child of room.children) add(room.room_id, child);
    for (const parent of room.parent_spaces) add(parent, room.room_id);
  }
  return map;
}

function descendants(spaceId: string, children: Map<string, Set<string>>, spaceIds: Set<string>): Set<string> {
  const out = new Set<string>();
  const seen = new Set<string>([spaceId]);
  const queue = [spaceId];
  for (let at = queue.pop(); at !== undefined; at = queue.pop()) {
    for (const child of children.get(at) ?? []) {
      if (spaceIds.has(child)) {
        if (!seen.has(child)) {
          seen.add(child);
          queue.push(child);
        }
      } else out.add(child);
    }
  }
  return out;
}

export function roomsUnderSpace(spaceId: string, rooms: RoomSummary[]): Set<string> {
  const spaceIds = new Set(rooms.filter((r) => r.space).map((r) => r.room_id));
  return descendants(spaceId, childMap(rooms), spaceIds);
}

export function spaceMutedRoomIds(rooms: RoomSummary[], mutedSpaces: string[]): Set<string> {
  const out = new Set<string>();
  if (mutedSpaces.length === 0) return out;
  const children = childMap(rooms);
  const spaceIds = new Set(rooms.filter((r) => r.space).map((r) => r.room_id));
  for (const id of mutedSpaces) for (const room of descendants(id, children, spaceIds)) out.add(room);
  return out;
}

export function isRoomMuted(room: RoomSummary, spaceMuted: Set<string>): boolean {
  return room.notification_mode === 'Mute' || spaceMuted.has(room.room_id);
}

export function withSpaceMutes(rooms: RoomSummary[], spaceMuted: Set<string>): RoomSummary[] {
  if (spaceMuted.size === 0) return rooms;
  return rooms.map((room) => (spaceMuted.has(room.room_id) && room.notification_mode !== 'Mute' ? { ...room, notification_mode: 'Mute' } : room));
}

export interface RoomChange {
  roomId: string;
  mode: RoomMode | null;
}

export interface Plan {
  state: MutedSpaces;
  changes: RoomChange[];
}

function priorMode(room: RoomSummary): RoomMode | null {
  const mode = room.notification_mode;
  return room.notification_mode_custom && (mode === 'AllMessages' || mode === 'MentionsAndKeywordsOnly') ? mode : null;
}

function claimed(state: MutedSpaces): Set<string> {
  const out = new Set<string>();
  for (const entry of Object.values(state.applied)) for (const roomId of Object.keys(entry)) out.add(roomId);
  return out;
}

function claim(state: MutedSpaces, spaceId: string, targets: RoomSummary[]): Plan {
  const skip = claimed(state);
  const fresh = targets.filter((room) => room.notification_mode !== 'Mute' && room.notification_mode !== null && !skip.has(room.room_id));
  const entry = { ...state.applied[spaceId] };
  for (const room of fresh) entry[room.room_id] = priorMode(room);
  return {
    state: fresh.length === 0 ? state : { ...state, applied: { ...state.applied, [spaceId]: entry } },
    changes: fresh.map((room) => ({ roomId: room.room_id, mode: 'Mute' }))
  };
}

function joinedUnder(spaceId: string, rooms: RoomSummary[]): RoomSummary[] {
  const ids = roomsUnderSpace(spaceId, rooms);
  return rooms.filter((r) => ids.has(r.room_id) && !r.space && r.membership === 'Joined');
}

export function muteSpacePlan(state: MutedSpaces, spaceId: string, rooms: RoomSummary[]): Plan {
  const base = state.spaces.includes(spaceId) ? state : { ...state, spaces: [...state.spaces, spaceId] };
  return claim(base, spaceId, joinedUnder(spaceId, rooms));
}

export function reconcilePlan(state: MutedSpaces, rooms: RoomSummary[]): Plan {
  let current = state;
  const changes: RoomChange[] = [];
  for (const spaceId of state.spaces) {
    const plan = claim(current, spaceId, joinedUnder(spaceId, rooms));
    current = plan.state;
    changes.push(...plan.changes);
  }
  return { state: current, changes };
}

export function unmuteSpacePlan(state: MutedSpaces, spaceId: string, rooms: RoomSummary[]): Plan {
  const spaces = state.spaces.filter((s) => s !== spaceId);
  const { [spaceId]: released = {}, ...applied } = state.applied;
  const next: MutedSpaces = { spaces, applied };
  const byId = new Map(rooms.map((r) => [r.room_id, r]));
  const changes: RoomChange[] = [];
  for (const [roomId, prior] of Object.entries(released)) {
    const room = byId.get(roomId);
    if (!room || room.notification_mode !== 'Mute') continue;
    const heir = spaces.find((s) => roomsUnderSpace(s, rooms).has(roomId));
    if (heir) {
      next.applied = { ...next.applied, [heir]: { ...next.applied[heir], [roomId]: prior } };
      continue;
    }
    changes.push({ roomId, mode: prior });
  }
  return { state: next, changes };
}

export function mutingSpace(state: MutedSpaces, rooms: RoomSummary[], roomId: string): string | null {
  return state.spaces.find((id) => roomsUnderSpace(id, rooms).has(roomId)) ?? null;
}
