import { test } from 'node:test';
import assert from 'node:assert/strict';
import { createStore } from 'jotai';
import { enableMapSet } from 'immer';
import { roomToParentsAtom } from './roomToParents';
import { RoomToParents } from '../../../types/matrix/room';

// `roomToParentsAtom` maps childRoomId -> Set<parentRoomId>. The reducer handles
// INITIALIZE (replace the whole map), PUT (mapParentWithChildren: register a
// parent for each given child, skipping cycles) and DELETE (drop the room as a
// parent map entry, strip it from every child's parent-set, then prune children
// left with zero parents). The reducers `produce` over an immer-managed Map/Set,
// so we enable that the same way the app does at startup (src/index.tsx).
// The React `useBindRoomToParentsAtom` hook wiring is not covered here.
enableMapSet();

const get = (store: ReturnType<typeof createStore>): RoomToParents => store.get(roomToParentsAtom);

const parentsOf = (store: ReturnType<typeof createStore>, child: string): string[] =>
  Array.from(get(store).get(child) ?? []).sort();

test('starts as an empty map', () => {
  const store = createStore();
  assert.equal(get(store).size, 0);
});

test('INITIALIZE replaces the whole map', () => {
  const store = createStore();
  const seed: RoomToParents = new Map([['!child:s', new Set(['!space:s'])]]);
  store.set(roomToParentsAtom, { type: 'INITIALIZE', roomToParents: seed });
  assert.equal(get(store), seed);
  assert.deepEqual(parentsOf(store, '!child:s'), ['!space:s']);
});

test('PUT registers a parent for each child', () => {
  const store = createStore();
  store.set(roomToParentsAtom, {
    type: 'PUT',
    parent: '!space:s',
    children: ['!c1:s', '!c2:s'],
  });
  assert.deepEqual(parentsOf(store, '!c1:s'), ['!space:s']);
  assert.deepEqual(parentsOf(store, '!c2:s'), ['!space:s']);
});

test('PUT accumulates multiple parents for the same child', () => {
  const store = createStore();
  store.set(roomToParentsAtom, { type: 'PUT', parent: '!p1:s', children: ['!c:s'] });
  store.set(roomToParentsAtom, { type: 'PUT', parent: '!p2:s', children: ['!c:s'] });
  assert.deepEqual(parentsOf(store, '!c:s'), ['!p1:s', '!p2:s']);
});

test('PUT skips a child that would create a cycle', () => {
  const store = createStore();
  // !b is a child of !a.
  store.set(roomToParentsAtom, { type: 'PUT', parent: '!a:s', children: ['!b:s'] });
  // Now make !a a child of !b -> !a's parents include !b, but !b already has !a
  // as an ancestor, so the cycle branch skips re-registering.
  store.set(roomToParentsAtom, { type: 'PUT', parent: '!b:s', children: ['!a:s'] });

  assert.deepEqual(parentsOf(store, '!b:s'), ['!a:s']);
  // !a was not registered as a child of !b because that closes a cycle.
  assert.equal(get(store).has('!a:s'), false);
});

test('DELETE removes the room as a parent entry', () => {
  const store = createStore();
  store.set(roomToParentsAtom, { type: 'PUT', parent: '!space:s', children: ['!c:s'] });
  // Register !space itself as a child of a grandparent so it has its own entry.
  store.set(roomToParentsAtom, { type: 'PUT', parent: '!grand:s', children: ['!space:s'] });
  assert.equal(get(store).has('!space:s'), true);

  store.set(roomToParentsAtom, { type: 'DELETE', roomId: '!space:s' });
  // !space's own entry (as a child of !grand) is gone.
  assert.equal(get(store).has('!space:s'), false);
});

test('DELETE strips the room from every child parent-set', () => {
  const store = createStore();
  store.set(roomToParentsAtom, { type: 'PUT', parent: '!p1:s', children: ['!c:s'] });
  store.set(roomToParentsAtom, { type: 'PUT', parent: '!p2:s', children: ['!c:s'] });

  store.set(roomToParentsAtom, { type: 'DELETE', roomId: '!p1:s' });
  // !c keeps !p2 as a parent.
  assert.deepEqual(parentsOf(store, '!c:s'), ['!p2:s']);
});

test('DELETE prunes a child left with zero parents (orphan cleanup)', () => {
  const store = createStore();
  store.set(roomToParentsAtom, { type: 'PUT', parent: '!space:s', children: ['!c:s'] });
  assert.equal(get(store).has('!c:s'), true);

  store.set(roomToParentsAtom, { type: 'DELETE', roomId: '!space:s' });
  // !c had only !space as a parent; with that gone it is pruned entirely.
  assert.equal(get(store).has('!c:s'), false);
  assert.equal(get(store).size, 0);
});

test('DELETE removes the room as both a parent and a child', () => {
  const store = createStore();
  // !mid is a child of !top and a parent of !leaf.
  store.set(roomToParentsAtom, { type: 'PUT', parent: '!top:s', children: ['!mid:s'] });
  store.set(roomToParentsAtom, { type: 'PUT', parent: '!mid:s', children: ['!leaf:s'] });

  store.set(roomToParentsAtom, { type: 'DELETE', roomId: '!mid:s' });
  // !mid's own entry is gone (was a child of !top).
  assert.equal(get(store).has('!mid:s'), false);
  // !leaf lost its only parent !mid, so it is pruned.
  assert.equal(get(store).has('!leaf:s'), false);
});

test('DELETE of an unknown room only prunes nothing', () => {
  const store = createStore();
  store.set(roomToParentsAtom, { type: 'PUT', parent: '!p:s', children: ['!c:s'] });
  store.set(roomToParentsAtom, { type: 'DELETE', roomId: '!unknown:s' });
  assert.deepEqual(parentsOf(store, '!c:s'), ['!p:s']);
  assert.equal(get(store).size, 1);
});

test('UNLINK removes ONE parent but keeps the child’s other parents', () => {
  const store = createStore();
  // !c is a child of both !a and !b.
  store.set(roomToParentsAtom, { type: 'PUT', parent: '!a:s', children: ['!c:s'] });
  store.set(roomToParentsAtom, { type: 'PUT', parent: '!b:s', children: ['!c:s'] });

  store.set(roomToParentsAtom, { type: 'UNLINK', parent: '!a:s', child: '!c:s' });
  // Only !a is dropped; !b remains. (DELETE would have wiped both.)
  assert.deepEqual(parentsOf(store, '!c:s'), ['!b:s']);
});

test('UNLINK prunes the child only when it was the last parent', () => {
  const store = createStore();
  store.set(roomToParentsAtom, { type: 'PUT', parent: '!a:s', children: ['!c:s'] });

  store.set(roomToParentsAtom, { type: 'UNLINK', parent: '!a:s', child: '!c:s' });
  // !c had only !a; with that gone its entry is pruned.
  assert.equal(get(store).has('!c:s'), false);
  assert.equal(get(store).size, 0);
});

test('UNLINK does NOT orphan the child’s own descendants', () => {
  const store = createStore();
  // !mid is a child of !top and a parent of !leaf.
  store.set(roomToParentsAtom, { type: 'PUT', parent: '!top:s', children: ['!mid:s'] });
  store.set(roomToParentsAtom, { type: 'PUT', parent: '!mid:s', children: ['!leaf:s'] });

  // Unlink !mid from !top only (one m.space.child removed).
  store.set(roomToParentsAtom, { type: 'UNLINK', parent: '!top:s', child: '!mid:s' });
  // !mid lost its only parent -> its own entry is pruned...
  assert.equal(get(store).has('!mid:s'), false);
  // ...but !leaf MUST still have !mid as a parent (DELETE wrongly stripped this).
  assert.deepEqual(parentsOf(store, '!leaf:s'), ['!mid:s']);
});

test('UNLINK of an unknown parent/child is a no-op', () => {
  const store = createStore();
  store.set(roomToParentsAtom, { type: 'PUT', parent: '!a:s', children: ['!c:s'] });

  store.set(roomToParentsAtom, { type: 'UNLINK', parent: '!nope:s', child: '!c:s' });
  assert.deepEqual(parentsOf(store, '!c:s'), ['!a:s']);
  store.set(roomToParentsAtom, { type: 'UNLINK', parent: '!a:s', child: '!missing:s' });
  assert.deepEqual(parentsOf(store, '!c:s'), ['!a:s']);
  assert.equal(get(store).size, 1);
});
