import { describe, expect, it } from "vitest";

import {
  addBridgeErrorListener,
  addGuidanceListener,
  addTerritoryProgressListener,
  addVoiceStateListener,
} from "./events";
import { createMapmapNavMock } from "./mock";

describe("typed event subscription helpers", () => {
  it("addGuidanceListener delivers guidance events and unsubscribes via .remove()", async () => {
    const mock = createMapmapNavMock({ territoryInstallDurationMs: 5, tickIntervalMs: 5 });
    await mock.init("key");
    await mock.installTerritory("uk");
    await mock.computeRoute({
      profile: "car",
      origin: { latitude: 51.5074, longitude: -0.1278 },
      destination: { latitude: 51.508, longitude: -0.1272 },
      alternatives: 0,
    });

    let callCount = 0;
    const subscription = addGuidanceListener(mock, () => {
      callCount++;
    });

    await mock.startGuidance({
      routeIndex: 0,
      source: { kind: "replay", corpusPath: "unused.drive.jsonl", speedMultiplier: 300 },
    });

    // Let one tick land, then unsubscribe — the count must stop growing.
    await new Promise((resolve) => setTimeout(resolve, 20));
    expect(callCount).toBeGreaterThan(0);
    subscription.remove();
    const countAtUnsubscribe = callCount;

    await new Promise((resolve) => setTimeout(resolve, 100));
    expect(callCount).toBe(countAtUnsubscribe);

    await mock.stopGuidance().catch(() => {
      // Session may already have reached "arrived" and self-cleared; that's fine.
    });
  });

  it("addTerritoryProgressListener / addVoiceStateListener / addBridgeErrorListener wire to the right event names", async () => {
    const mock = createMapmapNavMock({ territoryInstallDurationMs: 5 });
    await mock.init("key");

    const territoryEvents: string[] = [];
    const voiceEvents: boolean[] = [];
    let bridgeErrorCount = 0;

    addTerritoryProgressListener(mock, (event) => territoryEvents.push(event.phase));
    addVoiceStateListener(mock, (event) => voiceEvents.push(event.muted));
    addBridgeErrorListener(mock, () => {
      bridgeErrorCount++;
    });

    await mock.installTerritory("uk");
    await mock.setVoiceMuted(true);

    expect(territoryEvents.length).toBeGreaterThan(0);
    expect(voiceEvents).toContain(true);
    // MapmapNavMock never emits a synthetic bridgeError in these happy paths.
    expect(bridgeErrorCount).toBe(0);
  });

  it("a removed subscription does not throw and multiple listeners on the same event are independent", async () => {
    const mock = createMapmapNavMock({ territoryInstallDurationMs: 5 });
    await mock.init("key");

    let a = 0;
    let b = 0;
    const subA = addTerritoryProgressListener(mock, () => a++);
    const subB = addTerritoryProgressListener(mock, () => b++);

    subA.remove();
    subA.remove(); // idempotent — must not throw

    await mock.installTerritory("uk");

    expect(a).toBe(0);
    expect(b).toBeGreaterThan(0);

    subB.remove();
  });
});
