/**
 * skill-installs.mjs — shared skill install surfaces per runtime (BUG-022).
 *
 * Source of truth for "what does the framework have installed on this
 * project, per runtime, and is it current?" — the post-022 implementation
 * is manifest-aware:
 *
 *   1. Per surface, prefer `apt-file-manifest.json` (the v0.6.7+ runtime
 *      manifest). When present and valid, `framework_version` is the
 *      authoritative answer.
 *   2. Manifest present but malformed → reported as `installed: true`,
 *      `stale: true`, `source: 'manifest-malformed'`. Don't silently
 *      report healthy.
 *   3. Else fall back to `legacy_version_file` (the pre-manifest
 *      `.apt-version` stamp; only defined for codex via
 *      `LEGACY_INSTALL_PATHS` today). `source: 'legacy_version_file'`.
 *   4. Else → not installed for this surface.
 *
 * The surface array is derived from `RUNTIMES` (single source of truth in
 * `runtime-detect.mjs`) so adding a new runtime there auto-extends every
 * downstream diagnostic. The legacy fallback path is only populated for
 * runtimes that declare a legacy topology in `LEGACY_INSTALL_PATHS`.
 *
 * Backward-compat: `host` field stays `'claude-code'` for the claude
 * runtime (the statusline chip + several other consumers key on this
 * exact string). The new `runtime` field exposes the descriptor id
 * (`'claude'`, `'codex'`, …) for callers that want the canonical name.
 */
import { existsSync, readFileSync, statSync } from 'node:fs';
import { join, resolve } from 'node:path';
import { LEGACY_INSTALL_PATHS } from '../install/legacy-paths.mjs';
import { MANIFEST_FILENAME, readManifest } from '../install/manifest.mjs';
import { RUNTIMES } from '../install/runtime-detect.mjs';
/**
 * Map a runtime descriptor id to the `host` value historical consumers
 * expect. The Claude runtime is the only one with a non-identity mapping
 * (`'claude' → 'claude-code'`) — every other id is its own host string.
 *
 * @param {string} runtimeId
 * @returns {string}
 */
function hostFor(runtimeId) {
    return runtimeId === 'claude' ? 'claude-code' : runtimeId;
}
/**
 * @typedef {Object} SkillInstallSurface
 * @property {string} host                       Backward-compat host id.
 * @property {string} runtime                    Canonical runtime id (from RUNTIMES).
 * @property {string} install_root               Absolute path of the runtime install root.
 * @property {string} manifest_path              Absolute path of apt-file-manifest.json.
 * @property {string|null} legacy_version_file   Absolute path of the legacy .apt-version stamp, or null.
 */
/**
 * Return one install-surface descriptor per runtime in `RUNTIMES`.
 *
 * @param {string} projectDir
 * @returns {SkillInstallSurface[]}
 */
export function getSkillInstallSurfaces(projectDir) {
    const root = resolve(projectDir);
    return RUNTIMES.map((descriptor) => {
        const install_root = join(root, descriptor.installRoot);
        const legacyTopology = LEGACY_INSTALL_PATHS[descriptor.id];
        const legacy_version_file = legacyTopology ? join(root, legacyTopology.version_file) : null;
        return {
            host: hostFor(descriptor.id),
            runtime: descriptor.id,
            install_root,
            manifest_path: join(install_root, MANIFEST_FILENAME),
            legacy_version_file,
        };
    });
}
/**
 * @typedef {Object} SkillInstallState
 * @property {string} host
 * @property {string} runtime
 * @property {string} install_root
 * @property {string} manifest_path
 * @property {string|null} legacy_version_file
 * @property {boolean} installed
 * @property {string|null} installed_version
 * @property {boolean} stale
 * @property {'manifest' | 'manifest-malformed' | 'legacy_version_file' | null} source
 */
/**
 * Read each surface's install state. See the file header for the
 * precedence rules.
 *
 * @param {string} projectDir
 * @param {string} frameworkVersion
 * @returns {SkillInstallState[]}
 */
export function readSkillInstallState(projectDir, frameworkVersion) {
    return getSkillInstallSurfaces(projectDir).map((surface) => {
        const manifestExists = existsSync(surface.manifest_path);
        if (manifestExists) {
            const manifest = readManifest(surface.install_root);
            if (manifest && typeof manifest.framework_version === 'string') {
                return {
                    ...surface,
                    installed: true,
                    installed_version: manifest.framework_version,
                    stale: manifest.framework_version !== frameworkVersion,
                    source: /** @type {const} */ ('manifest'),
                };
            }
            // Manifest file present but parser rejected it (malformed JSON,
            // missing required fields, …). Don't silently report healthy.
            return {
                ...surface,
                installed: true,
                installed_version: null,
                stale: true,
                source: /** @type {const} */ ('manifest-malformed'),
            };
        }
        if (surface.legacy_version_file && existsSync(surface.legacy_version_file)) {
            try {
                if (statSync(surface.legacy_version_file).isFile()) {
                    const version = readFileSync(surface.legacy_version_file, 'utf-8').trim();
                    return {
                        ...surface,
                        installed: true,
                        installed_version: version,
                        stale: version !== frameworkVersion,
                        source: /** @type {const} */ ('legacy_version_file'),
                    };
                }
            }
            catch {
                /* fall through to "not installed" */
            }
        }
        return {
            ...surface,
            installed: false,
            installed_version: null,
            stale: false,
            source: null,
        };
    });
}
//# sourceMappingURL=skill-installs.mjs.map