import { test } from 'node:test';
import assert from 'node:assert/strict';
import type { MatrixClient } from 'matrix-js-sdk';
import {
  byTsOldToNew,
  byOrderKey,
  factoryRoomIdByUnreadCount,
  factoryRoomIdByActivity,
  factoryRoomIdByAtoZ,
  factoryRoomIdByUnread,
} from './sort';
import type { Unread } from '../../types/matrix/room';

test('byTsOldToNew sorts ascending by timestamp', () => {
  assert.ok(byTsOldToNew(1, 2) < 0);
  assert.ok(byTsOldToNew(5, 3) > 0);
  assert.equal(byTsOldToNew(2, 2), 0);
  assert.deepEqual([30, 10, 20].sort(byTsOldToNew), [10, 20, 30]);
});

test('byOrderKey: undefined sorts last, otherwise lexical', () => {
  assert.equal(byOrderKey(undefined, undefined), 0);
  assert.equal(byOrderKey('a', undefined), -1); // defined before undefined
  assert.equal(byOrderKey(undefined, 'a'), 1);
  assert.equal(byOrderKey('a', 'b'), -1);
  assert.equal(byOrderKey('b', 'a'), 1);
  // equal non-empty keys return 1 (not 0) — there is no equality branch for two
  // present keys, so a stable sort keeps input order for equal keys.
  assert.equal(byOrderKey('a', 'a'), 1);
  assert.deepEqual(['c', undefined, 'a', 'b'].sort(byOrderKey), ['a', 'b', 'c', undefined]);
});

test('factoryRoomIdByUnreadCount sorts by unread count descending', () => {
  const counts: Record<string, number> = { r1: 0, r2: 5, r3: 2 };
  const cmp = factoryRoomIdByUnreadCount((id) => counts[id]);
  assert.deepEqual(['r1', 'r2', 'r3'].sort(cmp), ['r2', 'r3', 'r1']);
});

test('factoryRoomIdByActivity sorts most-recently-active first', () => {
  const ts: Record<string, number> = { old: 100, new: 300, mid: 200 };
  const mx = {
    getRoom: (id: string) => (id in ts ? { getLastActiveTimestamp: () => ts[id] } : null),
  } as unknown as MatrixClient;
  const cmp = factoryRoomIdByActivity(mx);
  assert.deepEqual(['old', 'new', 'mid'].sort(cmp), ['new', 'mid', 'old']);
  // a room the client can't resolve sinks to the bottom
  assert.deepEqual(['missing', 'new'].sort(cmp), ['new', 'missing']);
});

test('factoryRoomIdByUnread: unread first, by count, then activity for ties', () => {
  const ts: Record<string, number> = { a: 100, b: 300, c: 200, d: 400 };
  const mx = {
    getRoom: (id: string) => (id in ts ? { getLastActiveTimestamp: () => ts[id] } : null),
  } as unknown as MatrixClient;
  const mkUnread = (total: number): Unread => ({ total, highlight: 0, from: null });
  // a: 2 unread, b: 5 unread, c: read, d: read
  const roomToUnread = new Map<string, Unread>([
    ['a', mkUnread(2)],
    ['b', mkUnread(5)],
    ['c', mkUnread(0)],
    ['d', mkUnread(0)],
  ]);
  const cmp = factoryRoomIdByUnread(roomToUnread, mx);
  // b (5) and a (2) lead by unread; then the read tail c/d breaks by activity
  // (d @400 more recent than c @200) rather than arbitrary order.
  assert.deepEqual(['a', 'b', 'c', 'd'].sort(cmp), ['b', 'a', 'd', 'c']);
});

test('factoryRoomIdByUnread: equal unread counts break by activity', () => {
  const ts: Record<string, number> = { x: 100, y: 500 };
  const mx = {
    getRoom: (id: string) => ({ getLastActiveTimestamp: () => ts[id] ?? 0 }),
  } as unknown as MatrixClient;
  const mkUnread = (total: number): Unread => ({ total, highlight: 0, from: null });
  const roomToUnread = new Map<string, Unread>([
    ['x', mkUnread(3)],
    ['y', mkUnread(3)],
  ]);
  const cmp = factoryRoomIdByUnread(roomToUnread, mx);
  // Same unread count → y (more recent) before x.
  assert.deepEqual(['x', 'y'].sort(cmp), ['y', 'x']);
});

test('factoryRoomIdByAtoZ sorts case-insensitively and ignores leading #', () => {
  const names: Record<string, string> = { a: 'Banana', b: 'apple', c: '#Cherry' };
  const mx = {
    getRoom: (id: string) => ({ name: names[id] ?? '' }),
  } as unknown as MatrixClient;
  const cmp = factoryRoomIdByAtoZ(mx);
  // apple < Banana < Cherry (# stripped, case-insensitive)
  assert.deepEqual(['a', 'b', 'c'].sort(cmp), ['b', 'a', 'c']);
});
