/**
 * Instance Factory Loader
 *
 * Loads instance factory definitions from YAML files
 * and validates them against the JSON Schema.
 */
import { readFile } from 'fs/promises';
import { glob } from 'glob';
import path from 'path';
import yaml from 'yaml';
import AjvModule from 'ajv';
import addFormatsModule from 'ajv-formats';
const Ajv = AjvModule.default || AjvModule;
const addFormats = addFormatsModule.default || addFormatsModule;
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Load JSON Schema for validation
let instanceFactorySchema;
async function loadSchema() {
    if (instanceFactorySchema !== undefined)
        return instanceFactorySchema;
    // Load schema from engine-entities (single source of truth)
    // Falls back to local copy if engine-entities not available
    // Returns null if schema can't be found (validation will be skipped)
    const candidates = [];
    try {
        const { createRequire } = await import('module');
        const require = createRequire(import.meta.url);
        const entitiesPkg = path.dirname(require.resolve('@specverse/entities/package.json'));
        candidates.push(path.join(entitiesPkg, 'schema', 'SPECVERSE-SCHEMA.json'));
    }
    catch { /* entities not resolvable */ }
    // Fallback: local copy relative to engines package root
    candidates.push(path.join(__dirname, '../../schema/SPECVERSE-SCHEMA.json'));
    candidates.push(path.join(__dirname, '../../../schema/SPECVERSE-SCHEMA.json'));
    for (const schemaPath of candidates) {
        try {
            const schemaContent = await readFile(schemaPath, 'utf-8');
            const fullSchema = JSON.parse(schemaContent);
            instanceFactorySchema = fullSchema.$defs?.InstanceFactoryDefinition || null;
            return instanceFactorySchema;
        }
        catch { /* try next candidate */ }
    }
    // Schema not found — validation will be skipped, factories still load
    instanceFactorySchema = null;
    return null;
}
/**
 * Load a single instance factory from a YAML file
 */
export async function loadInstanceFactory(filePath, options = {}) {
    const validate = options.validate !== false;
    // Read and parse YAML
    const content = await readFile(filePath, 'utf-8');
    const parsed = yaml.parse(content);
    // Validate against schema if requested
    if (validate) {
        const schema = await loadSchema();
        // Skip validation if no schema available (schema was consolidated)
        if (schema) {
            const ajv = new Ajv({ allErrors: true, strict: false });
            addFormats(ajv); // Add support for format validators like "uri"
            const validateFn = ajv.compile(schema);
            if (!validateFn(parsed)) {
                const errors = validateFn.errors?.map((err) => {
                    const path = err.instancePath || '/';
                    return `${path}: ${err.message}`;
                }) || ['Unknown validation error'];
                throw new Error(`Instance factory validation failed for ${filePath}:\n` +
                    errors.map((e) => `  - ${e}`).join('\n'));
            }
        }
    }
    const warnings = [];
    // Check for optional but recommended fields
    if (!parsed.description) {
        warnings.push('Missing recommended field: description');
    }
    if (!parsed.metadata?.author) {
        warnings.push('Missing recommended metadata: author');
    }
    return {
        type: parsed,
        filePath,
        warnings: warnings.length > 0 ? warnings : undefined
    };
}
/**
 * Load all instance factories from a directory
 */
export async function loadInstanceFactories(options) {
    const { baseDir, pattern = '**/*.yaml', validate = true, ignoreInvalid = false } = options;
    const types = [];
    const errors = [];
    // Find all YAML files matching the pattern
    const files = await glob(pattern, {
        cwd: baseDir,
        absolute: true,
        nodir: true
    });
    // Load each file
    for (const filePath of files) {
        try {
            const result = await loadInstanceFactory(filePath, { validate });
            types.push(result);
        }
        catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            if (ignoreInvalid) {
                errors.push({
                    filePath,
                    errors: [errorMessage]
                });
            }
            else {
                throw error;
            }
        }
    }
    return { types, errors };
}
/**
 * Load instance factories from multiple directories
 *
 * Directories are searched in order, with earlier directories
 * taking precedence for duplicate type names.
 */
export async function loadFromMultipleSources(sources, options = {}) {
    const allTypes = [];
    const allErrors = [];
    const seenTypes = new Set();
    for (const source of sources) {
        const result = await loadInstanceFactories({
            baseDir: source.path,
            pattern: source.pattern,
            validate: options.validate,
            ignoreInvalid: options.ignoreInvalid
        });
        // Filter out duplicate types (first source wins)
        for (const loadResult of result.types) {
            const typeName = loadResult.type.name;
            if (!seenTypes.has(typeName)) {
                seenTypes.add(typeName);
                allTypes.push(loadResult);
            }
            else {
                // Add warning about duplicate
                const existing = allTypes.find(t => t.type.name === typeName);
                if (existing) {
                    existing.warnings = existing.warnings || [];
                    existing.warnings.push(`Duplicate type "${typeName}" found in ${loadResult.filePath} (ignored)`);
                }
            }
        }
        allErrors.push(...result.errors);
    }
    return {
        types: allTypes,
        errors: allErrors
    };
}
//# sourceMappingURL=loader.js.map