/**
 * Phase E2 (olam-dogfood-vision): LocalWorldsSource implementation.
 *
 * Wraps host-cp's existing dockerode-driven world enumeration in a
 * WorldsSource-shaped object so E4's composition layer can fan out
 * across multiple sources (local + future Pylon cloud) and merge.
 *
 * The class deliberately takes its dependencies via factory function
 * injection rather than reaching into server.mjs's module-level state
 * directly. Two reasons:
 *   1. Testability — vitest can pass mocked getWorldsRegistry +
 *      fetchWorldServices without spinning up the full host-cp
 *      server.mjs.
 *   2. Module-cycle avoidance — server.mjs imports this module, so
 *      this module CANNOT import server.mjs back without a cycle.
 *
 * Returns the same shape as the pre-E2 GET /api/worlds response with
 * a single addition: `source: 'local'` on every entry.
 *
 * @typedef {import('./worlds-source.mjs').WorldsSource} WorldsSource
 * @typedef {import('./worlds-source.mjs').WorldSummary} WorldSummary
 * @typedef {import('./worlds-source.mjs').ServiceInfo} ServiceInfo
 */

/**
 * @typedef {object} LocalWorldsSourceDeps
 * @property {() => Record<string, number>} getWorldsRegistry
 *   Returns current WORLDS map (worldId → host_port). Called fresh
 *   per list() so post-list registry mutations are visible immediately.
 * @property {(worldId: string) => string | null} getWorldName
 *   Returns the operator-set friendly name OR null if absent.
 * @property {(worldId: string) => Promise<ServiceInfo[]>} fetchWorldServices
 *   Probes per-world services (atlas-core, diner-app, ttyd, per-world CP).
 *   Same function the pre-E2 handler called inline.
 */

/**
 * @param {LocalWorldsSourceDeps} deps
 * @returns {WorldsSource}
 */
export function createLocalWorldsSource(deps) {
  return {
    name: 'local',
    async list() {
      const registry = deps.getWorldsRegistry();
      const entries = Object.entries(registry);
      const worlds = await Promise.all(
        entries.map(async ([id, host_port]) => {
          const services = await deps.fetchWorldServices(id);
          // World status mirrors pre-E2 behavior:
          //   - running:  >=1 service responds to a probe
          //   - starting: container has port bindings but nothing answers
          //   - unknown:  no port bindings at all (container down/missing)
          const liveCount = services.filter((s) => s.live).length;
          /** @type {'running' | 'starting' | 'unknown'} */
          const status =
            services.length === 0
              ? 'unknown'
              : liveCount > 0
                ? 'running'
                : 'starting';
          /** @type {WorldSummary} */
          const summary = {
            id,
            name: deps.getWorldName(id),
            status,
            services,
            source: 'local',
          };
          // Preserve the pre-E2 host_port field so SPA + CLI consumers
          // that depend on it don't break. WorldSummary type doesn't
          // declare host_port (it's local-source-specific metadata),
          // but extra fields on the object are tolerated by the type.
          return /** @type {WorldSummary & {host_port: number}} */ ({
            ...summary,
            host_port,
          });
        }),
      );
      return worlds;
    },
  };
}
