import assert from 'node:assert/strict';
import { test } from 'node:test';
import { BackStack, type HistoryLike } from '../src/lib/back-stack.ts';

class FakeHistory implements HistoryLike {
  entries: unknown[] = [{ roomId: 'a' }];
  index = 0;
  onPop: (state: unknown) => void = () => undefined;
  get state(): unknown {
    return this.entries[this.index];
  }
  pushState(state: unknown): void {
    this.entries.splice(this.index + 1, Infinity, state);
    this.index++;
  }
  replaceState(state: unknown): void {
    this.entries[this.index] = state;
  }
  back(): void {
    if (this.index === 0) return;
    this.index--;
    queueMicrotask(() => this.onPop(this.state));
  }
}

function setup() {
  const history = new FakeHistory();
  const stack = new BackStack(history);
  const routed: unknown[] = [];
  history.onPop = (state) => {
    if (!stack.pop(state)) routed.push(state);
  };
  return { history, stack, routed };
}

const tick = () => new Promise((resolve) => setTimeout(resolve, 0));

test('opening a layer adds one entry and back closes it', async () => {
  const { history, stack, routed } = setup();
  let open = true;
  stack.sync('card', true, () => (open = false));
  assert.equal(history.entries.length, 2);
  history.back();
  await tick();
  assert.equal(open, false);
  assert.equal(history.index, 0);
  assert.deepEqual(routed, []);
});

test('closing through the UI pops its entry and swallows the popstate', async () => {
  const { history, stack, routed } = setup();
  stack.sync('card', true, () => undefined);
  stack.sync('card', false, () => undefined);
  await tick();
  assert.equal(history.index, 0);
  assert.deepEqual(routed, []);
  assert.equal(stack.depth, 0);
});

test('back closes only the topmost of several layers', async () => {
  const { history, stack } = setup();
  const closed: string[] = [];
  stack.sync('drawer', true, () => closed.push('drawer'));
  stack.sync('card', true, () => closed.push('card'));
  history.back();
  await tick();
  assert.deepEqual(closed, ['card']);
  assert.equal(stack.depth, 1);
});

test('a route recorded under an open layer replaces its entry', async () => {
  const { history, stack, routed } = setup();
  stack.sync('drawer', true, () => undefined);
  stack.route({ roomId: 'b' }, '/?room=b');
  stack.sync('drawer', false, () => undefined);
  await tick();
  assert.deepEqual(history.entries, [{ roomId: 'a' }, { roomId: 'b' }]);
  history.back();
  await tick();
  assert.deepEqual(routed, [{ roomId: 'a' }]);
});

test('closing a layer then pressing back does not skip a room', async () => {
  const { history, stack, routed } = setup();
  stack.route({ roomId: 'b' }, '/?room=b');
  stack.sync('card', true, () => undefined);
  stack.sync('card', false, () => undefined);
  await tick();
  assert.deepEqual(history.state, { roomId: 'b' });
  history.back();
  await tick();
  assert.deepEqual(routed, [{ roomId: 'a' }]);
});

test('a route recorded right after a UI close waits for the pop', async () => {
  const { history, stack } = setup();
  stack.sync('menu', true, () => undefined);
  stack.sync('menu', false, () => undefined);
  stack.route({ roomId: 'b' }, '/?room=b');
  await tick();
  assert.deepEqual(history.entries, [{ roomId: 'a' }, { roomId: 'b' }]);
});

test('a layer closed under another leaves a stale entry that back skips', async () => {
  const { history, stack, routed } = setup();
  let dialog = true;
  stack.sync('menu', true, () => undefined);
  stack.sync('dialog', true, () => (dialog = false));
  stack.sync('menu', false, () => undefined);
  history.back();
  await tick();
  assert.equal(dialog, false);
  assert.equal(history.index, 0);
  assert.equal(routed.length, 0);
});

test('init strips a layer marker left behind by a reload', () => {
  const { history, stack } = setup();
  history.entries = [{ roomId: 'a' }, { roomId: 'a', backLayer: 'card' }];
  history.index = 1;
  stack.init();
  assert.deepEqual(history.state, { roomId: 'a' });
});

test('a settle timeout releases a pop whose popstate never arrives', async () => {
  const { history, stack } = setup();
  stack.sync('x', true, () => undefined);
  history.back = () => undefined;
  stack.sync('x', false, () => undefined);
  stack.route({ roomId: 'b' }, '/?room=b');
  await new Promise((resolve) => setTimeout(resolve, 500));
  assert.deepEqual(history.state, { roomId: 'b' });
});
