// Phase F-2-B (B6): workspace + project catalog for host CP.
//
// Reads workspace YAML files from `~/.olam/workspaces/*.yaml` (mounted
// at `/data/workspaces` inside the host-cp container per compose.yaml).
// Provides three endpoints' worth of data:
//
//   1. /api/workspaces                 — list all workspaces (redacted)
//   2. /api/projects                   — deduplicated project union
//   3. POST /api/workspaces/match      — exact set-equality matching
//                                        for D13's project-first
//                                        create-world flow

import fs from 'node:fs';
import path from 'node:path';
import YAML from 'yaml';
import { redactSensitive } from './redact.mjs';

/**
 * @typedef {object} Project
 * @property {string} name
 * @property {string} [url]
 * @property {string} [path]
 * @property {string} [branch]
 */

/**
 * @typedef {object} Workspace
 * @property {string} name
 * @property {Project[]} repos       project list (called `repos` in YAML)
 * @property {Record<string, unknown>} [defaults]
 * @property {Record<string, unknown>} [services]
 * @property {Record<string, unknown>} [image]
 * @property {Record<string, unknown>} [host_ui]
 * @property {number} [updatedAt]
 */

/**
 * Load all workspace YAMLs from a directory. Returns an array, sorted
 * by name. Invalid YAMLs are logged + skipped (don't bring down the
 * whole list because one file is malformed).
 *
 * @param {string} dir
 * @param {(message: string) => void} [log]
 * @returns {Workspace[]}
 */
export function loadWorkspaces(dir, log = console.log) {
  if (!fs.existsSync(dir)) {
    log(`workspace-catalog: directory ${dir} does not exist`);
    return [];
  }
  /** @type {Workspace[]} */
  const out = [];
  for (const entry of fs.readdirSync(dir)) {
    if (!entry.endsWith('.yaml') && !entry.endsWith('.yml')) continue;
    const filePath = path.join(dir, entry);
    try {
      const raw = fs.readFileSync(filePath, 'utf-8');
      const parsed = YAML.parse(raw);
      if (parsed && typeof parsed === 'object' && parsed.name) {
        // Normalize: ensure `repos` is at least an empty array.
        out.push({ ...parsed, repos: parsed.repos ?? [] });
      } else {
        log(`workspace-catalog: skipping ${entry} (no .name field)`);
      }
    } catch (err) {
      log(`workspace-catalog: failed to parse ${entry}: ${err.message}`);
    }
  }
  return out.sort((a, b) => a.name.localeCompare(b.name));
}

/**
 * /api/workspaces response: redacted workspace list.
 *
 * @param {Workspace[]} workspaces
 * @returns {Workspace[]}
 */
export function workspacesForApi(workspaces) {
  return /** @type {Workspace[]} */ (redactSensitive(workspaces));
}

/**
 * /api/projects response: deduplicated project union across all
 * workspaces. Dedup key is project name (case-sensitive — Atlas Core
 * and atlas-core would be distinct, which matches the workspace YAML
 * convention of using kebab-case throughout).
 *
 * Per-project metadata: takes the FIRST occurrence's url/path/branch.
 * Subsequent occurrences with the same name are ignored. This keeps
 * the response stable across reorderings within individual workspace
 * YAMLs.
 *
 * @param {Workspace[]} workspaces
 * @returns {Project[]}
 */
export function projectsFromWorkspaces(workspaces) {
  /** @type {Map<string, Project>} */
  const byName = new Map();
  for (const ws of workspaces) {
    for (const repo of ws.repos ?? []) {
      if (!repo?.name) continue;
      if (!byName.has(repo.name)) {
        byName.set(repo.name, { ...repo });
      }
    }
  }
  return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
}

/**
 * POST /api/workspaces/match request body: { projects: string[] }.
 * Returns workspaces whose project-name set EXACTLY equals the input
 * set (no subset, no superset). Sorted by name for response stability.
 *
 * Algorithm: O(W × P) where W = #workspaces, P = average projects per
 * workspace. Workspaces are small (<10 projects each); fine for direct
 * iteration.
 *
 * @param {Workspace[]} workspaces
 * @param {string[]} projectNames
 * @returns {Workspace[]}
 */
export function matchWorkspacesByProjects(workspaces, projectNames) {
  const target = new Set(projectNames);
  /** @type {Workspace[]} */
  const matches = [];
  for (const ws of workspaces) {
    const wsNames = new Set((ws.repos ?? []).map((r) => r.name).filter(Boolean));
    if (setsEqual(target, wsNames)) {
      matches.push(ws);
    }
  }
  return matches.sort((a, b) => a.name.localeCompare(b.name));
}

/**
 * Set equality. Two sets are equal iff same size + same members.
 *
 * @param {Set<string>} a
 * @param {Set<string>} b
 * @returns {boolean}
 */
function setsEqual(a, b) {
  if (a.size !== b.size) return false;
  for (const x of a) {
    if (!b.has(x)) return false;
  }
  return true;
}
