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

// UT-068: `pushCatalogVisibility` resolves `serverReachable || showCatalog` and
// pushes the result to the backend whenever either input changes.
// See docs/specs/offline-downloaded-only-filter.md (DR-078/DR-079).
//
// `isConnected` now tracks server reachability alone (DR-079), so from this
// service's perspective its input is "is the server reachable". We drive it and
// the `showServerCatalog` toggle and assert what gets pushed via
// `commands.setShowServerCatalog`.

const h = vi.hoisted(() => {
  function shim<T>(initial: T) {
    let value = initial;
    const subs = new Set<(v: T) => void>();
    return {
      set(v: T) {
        value = v;
        subs.forEach((fn) => fn(value));
      },
      subscribe(fn: (v: T) => void) {
        subs.add(fn);
        fn(value);
        return () => subs.delete(fn);
      },
      // Drop subscribers left behind by module instances discarded via
      // `vi.resetModules()`. Without this, every previously-imported copy of the
      // service still reacts to `set()` and pushes its own visibility value.
      reset(v: T) {
        subs.clear();
        value = v;
      },
    };
  }
  return {
    isConnectedStore: shim(true),
    setShowServerCatalog: vi.fn(async () => {}),
  };
});

// Prime the module graph once, at collection time, instead of inside a test.
//
// Every test re-imports the service after `vi.resetModules()` so it gets a fresh
// set of module-level subscriptions. The *first* of those imports also pays to
// transform the service and its dependency graph — around a second of real
// wall-clock work with a cold Vite cache. Charged to a test body that cost sat
// close enough to vitest's 5s default that suite-wide contention (many workers
// transforming at once) tipped this file into a timeout, while running the file
// alone always passed. Warming here moves the compile out of the timed region;
// the per-test re-imports that follow are cached and cost ~30ms.
//
// The timeout is deliberately left at the default: the point is to stop timing
// the compiler, not to give it a bigger budget.
await import("./offlineCatalog");

vi.mock("$lib/stores/connectivity", () => ({
  isConnected: { subscribe: h.isConnectedStore.subscribe },
}));

vi.mock("$lib/api/bindings", () => ({
  commands: {
    setShowServerCatalog: h.setShowServerCatalog,
    syncFullCatalog: vi.fn(),
    resumeQueuedDownloads: vi.fn(),
    catalogSyncStatus: vi.fn(),
  },
}));

vi.mock("$lib/stores/auth", () => ({
  auth: { getRepository: () => ({ getHandle: () => "handle-1" }) },
}));

describe("pushCatalogVisibility resolves reachable || showCatalog (UT-068)", () => {
  beforeEach(() => {
    h.isConnectedStore.reset(true);
    h.setShowServerCatalog.mockClear();
    vi.resetModules();
  });

  it("pushes include=true while the server is reachable (initial subscribe)", async () => {
    const { showServerCatalog } = await import("./offlineCatalog");
    // Initial subscription with reachable=true, showCatalog=false ⇒ include=true.
    expect(h.setShowServerCatalog).toHaveBeenLastCalledWith(true);
    // Keep the import binding referenced so tree-shaking never elides it.
    expect(showServerCatalog).toBeDefined();
  });

  it("pushes include=false when unreachable and the toggle is off", async () => {
    h.isConnectedStore.set(false);
    await import("./offlineCatalog");
    // Fresh module subscribes with reachable=false, showCatalog=false ⇒ false.
    expect(h.setShowServerCatalog).toHaveBeenLastCalledWith(false);
  });

  it("re-pushes include=true when the toggle flips on while unreachable", async () => {
    h.isConnectedStore.set(false);
    const { showServerCatalog } = await import("./offlineCatalog");
    h.setShowServerCatalog.mockClear();

    showServerCatalog.set(true); // showCatalog input changes ⇒ include flips to true
    expect(h.setShowServerCatalog).toHaveBeenLastCalledWith(true);

    h.setShowServerCatalog.mockClear();
    showServerCatalog.set(false); // back off ⇒ include flips to false
    expect(h.setShowServerCatalog).toHaveBeenLastCalledWith(false);
  });

  it("does not re-push when the resolved value is unchanged", async () => {
    // reachable=true ⇒ include already true. Turning the toggle on keeps it true.
    const { showServerCatalog } = await import("./offlineCatalog");
    h.setShowServerCatalog.mockClear();

    showServerCatalog.set(true); // include stays true (true || true) ⇒ no push
    expect(h.setShowServerCatalog).not.toHaveBeenCalled();
  });
});
