import assert from "node:assert/strict";
import test from "node:test";
import { HostTabSync } from "../src/host/tab-sync.ts";

class FakeBus {
  channels = new Set();
  messages = [];

  createChannel() {
    const channel = new FakeChannel(this);
    this.channels.add(channel);
    return channel;
  }

  deliver(sender, message) {
    const copy = structuredClone(message);
    this.messages.push(copy);
    for (const channel of this.channels) {
      if (channel !== sender) channel.receive(copy);
    }
  }

  replay(message) {
    for (const channel of this.channels) channel.receive(structuredClone(message));
  }
}

class FakeChannel {
  listeners = new Set();

  constructor(bus) {
    this.bus = bus;
  }

  postMessage(message) {
    this.bus.deliver(this, message);
  }

  addEventListener(type, listener) {
    if (type === "message") this.listeners.add(listener);
  }

  removeEventListener(type, listener) {
    if (type === "message") this.listeners.delete(listener);
  }

  receive(data) {
    for (const listener of this.listeners) listener({ data });
  }

  close() {
    this.bus.channels.delete(this);
    this.listeners.clear();
  }
}

test("new tabs request and receive the current Host state", () => {
  const bus = new FakeBus();
  const firstState = hostState("http://runtime-a.test");
  const received = [];
  const first = new HostTabSync({
    tabId: "tab-a",
    channel: bus.createChannel(),
    getState: () => firstState,
    applyState: () => {},
  });
  const second = new HostTabSync({
    tabId: "tab-b",
    channel: bus.createChannel(),
    getState: () => hostState("http://runtime-b.test"),
    applyState: (state) => received.push(state),
  });

  assert.deepEqual(received, [firstState]);
  first.close();
  second.close();
});

test("publishes Host state but rejects self, stale and malformed messages", () => {
  const bus = new FakeBus();
  const received = [];
  const first = new HostTabSync({
    tabId: "tab-a",
    channel: bus.createChannel(),
    getState: () => hostState(""),
    applyState: (state) => received.push(state),
  });
  const second = new HostTabSync({
    tabId: "tab-b",
    channel: bus.createChannel(),
    getState: () => hostState(""),
    applyState: () => {},
  });
  received.length = 0;

  const update = hostState("http://shared.test");
  second.publish(update);
  assert.deepEqual(received, [update]);

  const stateMessage = bus.messages.findLast(
    (message) => message.type === "state" && message.sender === "tab-b",
  );
  bus.replay(stateMessage);
  bus.replay({
    ...stateMessage,
    sequence: stateMessage.sequence + 1,
    state: { ...stateMessage.state, connection: "invalid" },
  });
  assert.deepEqual(received, [update]);

  first.close();
  second.close();
});

function hostState(apiBase) {
  return {
    snapshot: null,
    connection: "online",
    apiBase,
    runtimeAction: null,
    serviceAction: null,
    deploymentAction: null,
  };
}
