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

import {
  type ToolAccessExtractor,
  ToolAccessExtractorRegistry,
} from "#src/tool-access-extractor-registry";

const noopExtractor: ToolAccessExtractor = () => "/tmp/x";

describe("ToolAccessExtractorRegistry", () => {
  describe("register", () => {
    test("stores an extractor so get() returns it", () => {
      const registry = new ToolAccessExtractorRegistry();
      registry.register("my-tool", noopExtractor);
      expect(registry.get("my-tool")).toBe(noopExtractor);
    });

    test("returns a disposer that removes the extractor", () => {
      const registry = new ToolAccessExtractorRegistry();
      const dispose = registry.register("my-tool", noopExtractor);
      dispose();
      expect(registry.get("my-tool")).toBeUndefined();
    });

    test("throws when an extractor is already registered for the same tool name", () => {
      const registry = new ToolAccessExtractorRegistry();
      registry.register("my-tool", noopExtractor);
      expect(() => registry.register("my-tool", () => undefined)).toThrow(
        "my-tool",
      );
    });

    test("allows registering different tool names independently", () => {
      const registry = new ToolAccessExtractorRegistry();
      const extractorA: ToolAccessExtractor = () => "/a";
      const extractorB: ToolAccessExtractor = () => "/b";
      registry.register("tool-a", extractorA);
      registry.register("tool-b", extractorB);
      expect(registry.get("tool-a")).toBe(extractorA);
      expect(registry.get("tool-b")).toBe(extractorB);
    });
  });

  describe("disposer identity guard", () => {
    test("stale disposer does not evict a later registration", () => {
      const registry = new ToolAccessExtractorRegistry();
      const first: ToolAccessExtractor = () => "/first";
      const second: ToolAccessExtractor = () => "/second";

      const disposeFirst = registry.register("my-tool", first);
      disposeFirst(); // removes first

      registry.register("my-tool", second); // second registration is now valid
      disposeFirst(); // calling stale disposer again — must not remove second

      expect(registry.get("my-tool")).toBe(second);
    });
  });

  describe("get", () => {
    test("returns undefined for an unregistered tool name", () => {
      const registry = new ToolAccessExtractorRegistry();
      expect(registry.get("unknown")).toBeUndefined();
    });

    test("the registered extractor is callable and returns its path", () => {
      const registry = new ToolAccessExtractorRegistry();
      const extractor: ToolAccessExtractor = (input) =>
        typeof input.target === "string" ? input.target : undefined;
      registry.register("ffgrep", extractor);
      expect(registry.get("ffgrep")?.({ target: "/etc/hosts" })).toBe(
        "/etc/hosts",
      );
      expect(registry.get("ffgrep")?.({ other: true })).toBeUndefined();
    });
  });
});
