/**
 * Profile completeness — every profile's transitive closure must be installable.
 *
 * A profile is a promise: "install `developer` and you get a working set". That promise is
 * only kept if every component the profile names resolves, AND every dependency of those
 * components resolves, recursively. A profile that names a live component which depends on a
 * dead one is just as broken as a profile that names the dead one directly — the install
 * fails at the same point, one level deeper.
 *
 * `validate-registry.sh` checks NEITHER half of this: it never reads `.profiles.*` at all
 * (see reference-resolution.test.ts), which is how `context:context-system/*` has sat in the
 * `advanced` profile expanding to zero components while the validator prints 244/244 green.
 */

import { describe, it, expect } from "vitest";
import {
  format,
  loadRegistry,
  profileReferences,
  resolveAll,
  type Reference,
  type Registry,
} from "../../support/references.js";
import { importPendingSymbols, repoPath } from "../../support/pending.js";

const PROFILES = ["advanced", "business", "developer", "essential", "full"] as const;

/** The one profile ref that does not resolve today. See reference-resolution.test.ts. */
const KNOWN_DEAD_PROFILE_REF = "context:context-system/*";

function refsFor(profile: string, registry: Registry): Reference[] {
  return profileReferences(registry).filter((reference) =>
    reference.source.endsWith(`profiles.${profile}.components`)
  );
}

/**
 * Expand a profile to its transitive closure of registry ids, following each component's
 * declared `dependencies`. Cycles terminate on the `seen` set.
 */
function closure(profile: string, registry: Registry): { ids: Set<string>; refs: Reference[] } {
  const byRef = new Map<string, { dependencies?: string[] }>();
  for (const [category, components] of Object.entries(registry.components)) {
    // agents -> agent, contexts -> context, ... and `config` (already singular) stays put.
    const type = category.replace(/s$/, "");
    for (const component of components) byRef.set(`${type}:${component.id}`, component);
  }

  const ids = new Set<string>();
  const refs: Reference[] = [];
  const queue = refsFor(profile, registry);

  while (queue.length > 0) {
    const reference = queue.shift()!;
    if (ids.has(reference.ref)) continue;
    ids.add(reference.ref);
    refs.push(reference);

    for (const dependency of byRef.get(reference.ref)?.dependencies ?? []) {
      queue.push({
        ref: dependency,
        source: `${reference.source} -> ${reference.ref} depends on`,
      });
    }
  }

  return { ids, refs };
}

// ============================================================================
// Green today — the profile corpus, and the closure minus the one known hole
// ============================================================================

describe("profiles", () => {
  it("registry carries exactly the 5 profiles on disk", () => {
    expect(Object.keys(loadRegistry().profiles).sort()).toEqual([...PROFILES].sort());
  });

  it.each(PROFILES)("%s names at least one component", (profile) => {
    expect(refsFor(profile, loadRegistry()).length).toBeGreaterThan(0);
  });

  it.each(PROFILES)("%s resolves every component it names, except the known dead ref", (profile) => {
    const registry = loadRegistry();
    const dead = resolveAll(refsFor(profile, registry), registry)
      .filter((resolution) => resolution.status !== "ok")
      .filter((resolution) => resolution.ref !== KNOWN_DEAD_PROFILE_REF);

    expect(dead, `${profile} names components that do not exist:\n${format(dead)}`).toEqual([]);
  });

  it.each(PROFILES)("%s has an installable transitive closure, except the known dead ref", (profile) => {
    const registry = loadRegistry();
    const { refs } = closure(profile, registry);
    const dead = resolveAll(refs, registry)
      .filter((resolution) => resolution.status !== "ok")
      .filter((resolution) => resolution.ref !== KNOWN_DEAD_PROFILE_REF);

    expect(
      dead,
      `${profile}'s closure reaches components that do not exist — installing it would fail:\n${format(dead)}`
    ).toEqual([]);
  });

  it("advanced is the profile carrying the known dead wildcard", () => {
    const registry = loadRegistry();
    const carriers = PROFILES.filter((profile) =>
      refsFor(profile, registry).some((r) => r.ref === KNOWN_DEAD_PROFILE_REF)
    );

    expect(carriers).toEqual(["advanced"]);
  });

  it("the closure is strictly larger than the named set for at least one profile", () => {
    // Guards the test itself: if dependency-following silently did nothing, every closure
    // would equal its profile's own list and these tests would prove much less than they read.
    const registry = loadRegistry();
    const grew = PROFILES.some(
      (profile) => closure(profile, registry).ids.size > refsFor(profile, registry).length
    );

    expect(grew, "closure() followed no dependencies — it is not testing transitivity").toBe(true);
  });
});

// ============================================================================
// RED — the shipped profile loader (subtask 05)
// ============================================================================

const OWED_BY = "subtask 05 (src/core/ProfileLoader.ts)";

describe("ProfileLoader (shipped)", () => {
  it("loads all 5 profiles", async () => {
    const { ProfileLoader } = await importPendingSymbols<{
      ProfileLoader: new (root: string) => { list(): Promise<string[]> };
    }>(
      "src/core/ProfileLoader.ts",
      ["ProfileLoader"],
      OWED_BY,
      "the build can enumerate the 5 profiles rather than hard-coding them"
    );

    expect((await new ProfileLoader(repoPath()).list()).sort()).toEqual([...PROFILES].sort());
  });

  it.each(PROFILES)("reports %s's closure as installable", async (profile) => {
    const { ProfileLoader } = await importPendingSymbols<{
      ProfileLoader: new (root: string) => {
        resolveClosure(profile: string): Promise<{ missing: { ref: string }[] }>;
      };
    }>(
      "src/core/ProfileLoader.ts",
      ["ProfileLoader"],
      OWED_BY,
      `every component in ${profile}'s transitive closure resolves, so installing it works`
    );

    const { missing } = await new ProfileLoader(repoPath()).resolveClosure(profile);
    const unexpected = missing.filter((entry) => entry.ref !== KNOWN_DEAD_PROFILE_REF);

    expect(unexpected).toEqual([]);
  });
});
