import assert from 'node:assert/strict';
import { test } from 'node:test';
import {
  isRoomMuted,
  muteSpacePlan,
  mutingSpace,
  parseMutedSpaces,
  reconcilePlan,
  spaceMutedRoomIds,
  unmuteSpacePlan,
  withSpaceMutes,
  type MutedSpaces
} from '../src/lib/mute-model.ts';
import { badgeTotal } from '../src/lib/notification-model.ts';
import type { RoomSummary } from '../src/lib/types.ts';

function room(id: string, extra: Partial<RoomSummary> = {}): RoomSummary {
  return {
    room_id: id,
    name: id,
    membership: 'Joined',
    space: false,
    direct: false,
    unread_messages: 0,
    unread_mentions: 0,
    notification_mode: 'AllMessages',
    notification_mode_custom: false,
    parent_spaces: [],
    children: [],
    ...extra
  } as RoomSummary;
}

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

const rooms = [
  room('!top', { space: true, children: ['!a', '!sub'] }),
  room('!sub', { space: true, parent_spaces: ['!top'], children: ['!b'] }),
  room('!a', { parent_spaces: ['!top'], unread_messages: 3 }),
  room('!b', { parent_spaces: ['!sub'], unread_messages: 2, notification_mode: 'MentionsAndKeywordsOnly', notification_mode_custom: true }),
  room('!c', { notification_mode: 'Mute', notification_mode_custom: true, parent_spaces: ['!top'] }),
  room('!other', { unread_messages: 1 })
];

test('space mute covers nested rooms but not rooms elsewhere', () => {
  assert.deepEqual([...spaceMutedRoomIds(rooms, ['!top'])].sort(), ['!a', '!b', '!c']);
  assert.equal(spaceMutedRoomIds(rooms, []).size, 0);
});

test('space mute silences the badge total and leaves other rooms', () => {
  const muted = withSpaceMutes(rooms, spaceMutedRoomIds(rooms, ['!top']));
  assert.equal(badgeTotal(muted), 1);
  assert.equal(badgeTotal(rooms), 4);
  assert.equal(withSpaceMutes(rooms, new Set()), rooms);
});

test('isRoomMuted covers explicit and inherited mute', () => {
  const inherited = spaceMutedRoomIds(rooms, ['!top']);
  assert.equal(isRoomMuted(rooms[2], inherited), true);
  assert.equal(isRoomMuted(rooms[5], inherited), false);
  assert.equal(isRoomMuted(rooms[4], new Set()), true);
});

test('muting a space claims unmuted children with their prior mode', () => {
  const plan = muteSpacePlan(empty, '!top', rooms);
  assert.deepEqual(plan.state.spaces, ['!top']);
  assert.deepEqual(plan.state.applied['!top'], { '!a': null, '!b': 'MentionsAndKeywordsOnly' });
  assert.deepEqual(plan.changes.map((c) => c.roomId).sort(), ['!a', '!b']);
});

test('unmuting restores prior modes and leaves individual mutes alone', () => {
  const muted = muteSpacePlan(empty, '!top', rooms).state;
  const after = rooms.map((r) => (r.room_id === '!a' || r.room_id === '!b' ? { ...r, notification_mode: 'Mute' as const } : r));
  const plan = unmuteSpacePlan(muted, '!top', after);
  assert.deepEqual(plan.state, empty);
  assert.deepEqual(plan.changes, [
    { roomId: '!a', mode: null },
    { roomId: '!b', mode: 'MentionsAndKeywordsOnly' }
  ]);
  assert.equal(plan.changes.some((c) => c.roomId === '!c'), false);
});

test('a room a user unmuted inside a muted space is not reclaimed', () => {
  const muted = muteSpacePlan(empty, '!top', rooms).state;
  const after = rooms.map((r) => (r.room_id === '!a' ? { ...r, notification_mode: 'AllMessages' as const } : r));
  assert.deepEqual(reconcilePlan(muted, after).changes, []);
});

test('reconcile mutes children that joined later', () => {
  const muted = muteSpacePlan(empty, '!top', rooms).state;
  const joined = [...rooms, room('!new', { parent_spaces: ['!top'] })];
  const plan = reconcilePlan(muted, joined);
  assert.deepEqual(plan.changes, [{ roomId: '!new', mode: 'Mute' }]);
  assert.equal(reconcilePlan(plan.state, joined).changes.length, 0);
});

test('reconcile ignores rooms whose mode has not loaded', () => {
  const muted: MutedSpaces = { spaces: ['!top'], applied: {} };
  const pending = rooms.map((r) => (r.room_id === '!a' ? { ...r, notification_mode: null } : r));
  assert.equal(reconcilePlan(muted, pending).changes.some((c) => c.roomId === '!a'), false);
});

test('overlapping muted spaces hand the room to the remaining space on unmute', () => {
  const both = [...rooms, room('!shared', { parent_spaces: ['!top', '!x'] }), room('!x', { space: true, children: ['!shared'] })];
  let state = muteSpacePlan(empty, '!top', both).state;
  state = muteSpacePlan(state, '!x', both).state;
  const after = both.map((r) => (r.notification_mode === 'AllMessages' ? { ...r, notification_mode: 'Mute' as const } : r));
  const plan = unmuteSpacePlan(state, '!top', after);
  assert.equal(plan.changes.some((c) => c.roomId === '!shared'), false);
  assert.ok('!shared' in (plan.state.applied['!x'] ?? {}));
  assert.equal(mutingSpace(plan.state, after, '!shared'), '!x');
});

test('parseMutedSpaces tolerates junk', () => {
  assert.deepEqual(parseMutedSpaces(null), empty);
  assert.deepEqual(parseMutedSpaces({ spaces: ['!a', 3], applied: { '!a': { '!r': 'Mute', '!q': 'Bogus' }, '!gone': { '!r': null } } }), {
    spaces: ['!a'],
    applied: { '!a': { '!r': 'Mute' } }
  });
});
