import * as fs from 'node:fs';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';
import { getGlobalDataDir } from '../global-config.js';
import { parseSchema, SchemaValidationError } from './schema.js';
/**
 * Error thrown when loading a schema fails.
 */
export class SchemaLoadError extends Error {
    schemaPath;
    cause;
    constructor(message, schemaPath, cause) {
        super(message);
        this.schemaPath = schemaPath;
        this.cause = cause;
        this.name = 'SchemaLoadError';
    }
}
/**
 * Gets the package's built-in schemas directory path.
 * Uses import.meta.url to resolve relative to the current module.
 */
export function getPackageSchemasDir() {
    const currentFile = fileURLToPath(import.meta.url);
    // Navigate from dist/core/artifact-graph/ to package root's schemas/
    return path.join(path.dirname(currentFile), '..', '..', '..', 'schemas');
}
/**
 * Gets the user's schema override directory path.
 */
export function getUserSchemasDir() {
    return path.join(getGlobalDataDir(), 'schemas');
}
/**
 * Gets the project-local schemas directory path.
 * @param projectRoot - The project root directory
 * @returns The path to the project's schemas directory
 */
export function getProjectSchemasDir(projectRoot) {
    return path.join(projectRoot, 'openspec', 'schemas');
}
/**
 * Resolves a schema name to its directory path.
 *
 * Resolution order (when projectRoot is provided):
 * 1. Project-local: <projectRoot>/openspec/schemas/<name>/schema.yaml
 * 2. User override: ${XDG_DATA_HOME}/openspec/schemas/<name>/schema.yaml
 * 3. Package built-in: <package>/schemas/<name>/schema.yaml
 *
 * When projectRoot is not provided, only user override and package built-in are checked
 * (backward compatible behavior).
 *
 * @param name - Schema name (e.g., "spec-driven")
 * @param projectRoot - Optional project root directory for project-local schema resolution
 * @returns The path to the schema directory, or null if not found
 */
export function getSchemaDir(name, projectRoot) {
    // 1. Check project-local directory (if projectRoot provided)
    if (projectRoot) {
        const projectDir = path.join(getProjectSchemasDir(projectRoot), name);
        const projectSchemaPath = path.join(projectDir, 'schema.yaml');
        if (fs.existsSync(projectSchemaPath)) {
            return projectDir;
        }
    }
    // 2. Check user override directory
    const userDir = path.join(getUserSchemasDir(), name);
    const userSchemaPath = path.join(userDir, 'schema.yaml');
    if (fs.existsSync(userSchemaPath)) {
        return userDir;
    }
    // 3. Check package built-in directory
    const packageDir = path.join(getPackageSchemasDir(), name);
    const packageSchemaPath = path.join(packageDir, 'schema.yaml');
    if (fs.existsSync(packageSchemaPath)) {
        return packageDir;
    }
    return null;
}
/**
 * Resolves a schema name to a SchemaYaml object.
 *
 * Resolution order (when projectRoot is provided):
 * 1. Project-local: <projectRoot>/openspec/schemas/<name>/schema.yaml
 * 2. User override: ${XDG_DATA_HOME}/openspec/schemas/<name>/schema.yaml
 * 3. Package built-in: <package>/schemas/<name>/schema.yaml
 *
 * When projectRoot is not provided, only user override and package built-in are checked
 * (backward compatible behavior).
 *
 * @param name - Schema name (e.g., "spec-driven")
 * @param projectRoot - Optional project root directory for project-local schema resolution
 * @returns The resolved schema object
 * @throws Error if schema is not found in any location
 */
export function resolveSchema(name, projectRoot) {
    // Normalize name (remove .yaml extension if provided)
    const normalizedName = name.replace(/\.ya?ml$/, '');
    const schemaDir = getSchemaDir(normalizedName, projectRoot);
    if (!schemaDir) {
        const availableSchemas = listSchemas(projectRoot);
        throw new Error(`Schema '${normalizedName}' not found. Available schemas: ${availableSchemas.join(', ')}`);
    }
    const schemaPath = path.join(schemaDir, 'schema.yaml');
    // Load and parse the schema
    let content;
    try {
        content = fs.readFileSync(schemaPath, 'utf-8');
    }
    catch (err) {
        const ioError = err instanceof Error ? err : new Error(String(err));
        throw new SchemaLoadError(`Failed to read schema at '${schemaPath}': ${ioError.message}`, schemaPath, ioError);
    }
    try {
        return parseSchema(content);
    }
    catch (err) {
        if (err instanceof SchemaValidationError) {
            throw new SchemaLoadError(`Invalid schema at '${schemaPath}': ${err.message}`, schemaPath, err);
        }
        const parseError = err instanceof Error ? err : new Error(String(err));
        throw new SchemaLoadError(`Failed to parse schema at '${schemaPath}': ${parseError.message}`, schemaPath, parseError);
    }
}
/**
 * Lists all available schema names.
 * Combines project-local, user override, and package built-in schemas.
 *
 * @param projectRoot - Optional project root directory for project-local schema resolution
 */
export function listSchemas(projectRoot) {
    const schemas = new Set();
    // Add package built-in schemas
    const packageDir = getPackageSchemasDir();
    if (fs.existsSync(packageDir)) {
        for (const entry of fs.readdirSync(packageDir, { withFileTypes: true })) {
            if (entry.isDirectory()) {
                const schemaPath = path.join(packageDir, entry.name, 'schema.yaml');
                if (fs.existsSync(schemaPath)) {
                    schemas.add(entry.name);
                }
            }
        }
    }
    // Add user override schemas (may override package schemas)
    const userDir = getUserSchemasDir();
    if (fs.existsSync(userDir)) {
        for (const entry of fs.readdirSync(userDir, { withFileTypes: true })) {
            if (entry.isDirectory()) {
                const schemaPath = path.join(userDir, entry.name, 'schema.yaml');
                if (fs.existsSync(schemaPath)) {
                    schemas.add(entry.name);
                }
            }
        }
    }
    // Add project-local schemas (if projectRoot provided)
    if (projectRoot) {
        const projectDir = getProjectSchemasDir(projectRoot);
        if (fs.existsSync(projectDir)) {
            for (const entry of fs.readdirSync(projectDir, { withFileTypes: true })) {
                if (entry.isDirectory()) {
                    const schemaPath = path.join(projectDir, entry.name, 'schema.yaml');
                    if (fs.existsSync(schemaPath)) {
                        schemas.add(entry.name);
                    }
                }
            }
        }
    }
    return Array.from(schemas).sort();
}
/**
 * Lists all available schemas with their descriptions and artifact lists.
 * Useful for agent skills to present schema selection to users.
 *
 * @param projectRoot - Optional project root directory for project-local schema resolution
 */
export function listSchemasWithInfo(projectRoot) {
    const schemas = [];
    const seenNames = new Set();
    // Add project-local schemas first (highest priority, if projectRoot provided)
    if (projectRoot) {
        const projectDir = getProjectSchemasDir(projectRoot);
        if (fs.existsSync(projectDir)) {
            for (const entry of fs.readdirSync(projectDir, { withFileTypes: true })) {
                if (entry.isDirectory()) {
                    const schemaPath = path.join(projectDir, entry.name, 'schema.yaml');
                    if (fs.existsSync(schemaPath)) {
                        try {
                            const schema = parseSchema(fs.readFileSync(schemaPath, 'utf-8'));
                            schemas.push({
                                name: entry.name,
                                description: schema.description || '',
                                artifacts: schema.artifacts.map((a) => a.id),
                                source: 'project',
                            });
                            seenNames.add(entry.name);
                        }
                        catch {
                            // Skip invalid schemas
                        }
                    }
                }
            }
        }
    }
    // Add user override schemas (if not overridden by project)
    const userDir = getUserSchemasDir();
    if (fs.existsSync(userDir)) {
        for (const entry of fs.readdirSync(userDir, { withFileTypes: true })) {
            if (entry.isDirectory() && !seenNames.has(entry.name)) {
                const schemaPath = path.join(userDir, entry.name, 'schema.yaml');
                if (fs.existsSync(schemaPath)) {
                    try {
                        const schema = parseSchema(fs.readFileSync(schemaPath, 'utf-8'));
                        schemas.push({
                            name: entry.name,
                            description: schema.description || '',
                            artifacts: schema.artifacts.map((a) => a.id),
                            source: 'user',
                        });
                        seenNames.add(entry.name);
                    }
                    catch {
                        // Skip invalid schemas
                    }
                }
            }
        }
    }
    // Add package built-in schemas (if not overridden by project or user)
    const packageDir = getPackageSchemasDir();
    if (fs.existsSync(packageDir)) {
        for (const entry of fs.readdirSync(packageDir, { withFileTypes: true })) {
            if (entry.isDirectory() && !seenNames.has(entry.name)) {
                const schemaPath = path.join(packageDir, entry.name, 'schema.yaml');
                if (fs.existsSync(schemaPath)) {
                    try {
                        const schema = parseSchema(fs.readFileSync(schemaPath, 'utf-8'));
                        schemas.push({
                            name: entry.name,
                            description: schema.description || '',
                            artifacts: schema.artifacts.map((a) => a.id),
                            source: 'package',
                        });
                    }
                    catch {
                        // Skip invalid schemas
                    }
                }
            }
        }
    }
    return schemas.sort((a, b) => a.name.localeCompare(b.name));
}
//# sourceMappingURL=resolver.js.map