import { test } from 'node:test';
import assert from 'node:assert/strict';
import { createStore } from 'jotai';
import {
  getLocalStorageItem,
  setLocalStorageItem,
  atomWithLocalStorage,
} from './atomWithLocalStorage';

// These helpers read/write the real `localStorage` global, which node lacks, so
// we install a small in-memory mock before each case. `atomWithLocalStorage`
// also registers a `window` storage listener via `onMount`; we only drive the
// pure read/write path through a jotai store (no onMount), so `window` is not
// required here.
const installStorage = (): Map<string, string> => {
  const map = new Map<string, string>();
  (globalThis as { localStorage?: unknown }).localStorage = {
    getItem: (k: string) => (map.has(k) ? map.get(k)! : null),
    setItem: (k: string, v: string) => {
      map.set(k, v);
    },
    removeItem: (k: string) => {
      map.delete(k);
    },
  };
  return map;
};

test('getLocalStorageItem returns the default when the key is absent', () => {
  installStorage();
  assert.deepEqual(getLocalStorageItem('missing', { a: 1 }), { a: 1 });
  assert.equal(getLocalStorageItem('missing', 7), 7);
});

test('getLocalStorageItem maps the literal string "undefined" to undefined', () => {
  const store = installStorage();
  store.set('k', 'undefined');
  assert.equal(getLocalStorageItem('k', 'fallback'), undefined);
});

test('getLocalStorageItem parses stored JSON', () => {
  const store = installStorage();
  store.set('k', JSON.stringify({ nested: [1, 2, 3] }));
  assert.deepEqual(getLocalStorageItem('k', null), { nested: [1, 2, 3] });
});

test('getLocalStorageItem returns the default on malformed JSON', () => {
  const store = installStorage();
  store.set('k', '{ not valid json');
  assert.equal(getLocalStorageItem('k', 'fallback'), 'fallback');
});

test('setLocalStorageItem writes the JSON-serialized value', () => {
  const store = installStorage();
  setLocalStorageItem('k', { hello: 'world' });
  assert.equal(store.get('k'), JSON.stringify({ hello: 'world' }));
});

test('round-trips a value through set + get', () => {
  installStorage();
  setLocalStorageItem('k', [1, 'two', { three: true }]);
  assert.deepEqual(getLocalStorageItem('k', null), [1, 'two', { three: true }]);
});

test('atomWithLocalStorage seeds the atom from getItem on creation', () => {
  installStorage();
  const seeded = atomWithLocalStorage<number>(
    'k',
    () => 42,
    () => undefined,
  );
  const store = createStore();
  assert.equal(store.get(seeded), 42);
});

test('atomWithLocalStorage write-through updates BOTH the atom and storage', () => {
  installStorage();
  const writes: Array<[string, number]> = [];
  const theAtom = atomWithLocalStorage<number>(
    'k',
    () => 0,
    (key, value) => {
      writes.push([key, value]);
    },
  );

  const store = createStore();
  store.set(theAtom, 5);

  // Atom value reflects the write...
  assert.equal(store.get(theAtom), 5);
  // ...and setItem was invoked with the key + new value.
  assert.deepEqual(writes, [['k', 5]]);
});

test('atomWithLocalStorage persists through the real setLocalStorageItem helper', () => {
  const backing = installStorage();
  const theAtom = atomWithLocalStorage<{ count: number }>(
    'k',
    (key) => getLocalStorageItem(key, { count: 0 }),
    (key, value) => setLocalStorageItem(key, value),
  );

  const store = createStore();
  store.set(theAtom, { count: 9 });

  assert.equal(backing.get('k'), JSON.stringify({ count: 9 }));
  assert.deepEqual(store.get(theAtom), { count: 9 });
});
