import assert from "node:assert/strict";
import test from "node:test";
import { FrameCache } from "../src/host/frame-cache.ts";

test("retains opted-in iframe identities across navigation", () => {
  const cache = new FrameCache("services", {
    inactiveTtlMs: 1_000,
    shouldKeepAlive: (view) => view === "services" || view === "settings",
  });
  const services = cache.snapshot()[0];

  const away = cache.activate("overview");
  assert.deepEqual(
    away.entries.map((entry) => entry.view),
    ["services", "overview"],
  );

  const back = cache.activate("services");
  assert.equal(back.activated, services);
  assert.equal(back.activated.instance, services.instance);
  assert.deepEqual(
    back.removed.map((entry) => entry.view),
    ["overview"],
  );
});

test("recreates non-keep-alive documents after leaving them", () => {
  const cache = new FrameCache("overview", {
    inactiveTtlMs: 1_000,
    shouldKeepAlive: () => false,
  });
  const firstInstance = cache.snapshot()[0].instance;

  cache.activate("activity");
  const transition = cache.activate("overview");

  assert.notEqual(transition.activated.instance, firstInstance);
  assert.deepEqual(
    transition.removed.map((entry) => entry.view),
    ["activity"],
  );
});

test("expires a keep-alive document after its continuous hidden TTL", () => {
  let now = 5_000;
  const cache = new FrameCache("services", {
    inactiveTtlMs: 1_000,
    shouldKeepAlive: () => true,
    now: () => now,
  });
  const servicesInstance = cache.snapshot()[0].instance;

  // Time spent visible does not consume the inactive TTL.
  now = 50_000;
  cache.activate("settings");
  now = 50_999;
  assert.equal(cache.timeUntilExpiration(), 1);
  assert.deepEqual(cache.expireInactive().removed, []);

  now = 51_000;
  const expiration = cache.expireInactive();
  assert.deepEqual(
    expiration.removed.map((entry) => entry.view),
    ["services"],
  );
  assert.deepEqual(
    expiration.entries.map((entry) => entry.view),
    ["settings"],
  );

  const recreated = cache.activate("services");
  assert.notEqual(recreated.activated.instance, servicesInstance);
});

test("revisiting a hidden page before its TTL preserves and resets it", () => {
  let now = 0;
  const cache = new FrameCache("services", {
    inactiveTtlMs: 1_000,
    shouldKeepAlive: () => true,
    now: () => now,
  });
  const services = cache.snapshot()[0];

  cache.activate("settings");
  now = 999;
  const revisited = cache.activate("services");
  assert.equal(revisited.activated, services);

  now = 1_500;
  assert.deepEqual(cache.expireInactive().removed, []);
  assert.equal(cache.timeUntilExpiration(), 499);

  now = 1_999;
  const expiration = cache.expireInactive();
  assert.deepEqual(
    expiration.removed.map((entry) => entry.view),
    ["settings"],
  );
});

test("rejects invalid inactive TTL values", () => {
  for (const inactiveTtlMs of [0, -1, Number.POSITIVE_INFINITY, Number.NaN]) {
    assert.throws(
      () =>
        new FrameCache("overview", {
          inactiveTtlMs,
          shouldKeepAlive: () => true,
        }),
      RangeError,
    );
  }
});
