/**
 * manifest-validator.mjs — hand-rolled JSON-Schema subset validator.
 *
 * Validates driver manifests against manifest-schema.json. Implements
 * exactly the JSON-Schema keywords we need:
 *
 *   type / required / properties / items / enum / const / pattern / oneOf
 *
 * Returns { valid: true } or { valid: false, errors: [{ path, message }] }
 * where `path` is a JSON-pointer (`/transport/local_command/binary`).
 *
 * No external deps — keeps the validator installable as `dist/` with the
 * framework package and avoids dragging ajv (or any v5+ JSON-Schema
 * validator) into the runtime surface for ~80 lines of validator code per
 * AC15. We don't need allOf / anyOf / definitions — manifest-schema.json
 * is intentionally a thin contract.
 */
import { readFileSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = dirname(fileURLToPath(import.meta.url));
/** Lazy-loaded JSON-Schema (read once on first call). */
let cachedSchema = null;
/** Read the bundled manifest-schema.json. */
export function loadManifestSchema() {
    if (cachedSchema !== null)
        return cachedSchema;
    const schemaPath = join(__dirname, 'manifest-schema.json');
    cachedSchema = JSON.parse(readFileSync(schemaPath, 'utf-8'));
    return cachedSchema;
}
/**
 * Validate a manifest object against the bundled schema.
 *
 * @param {unknown} manifest — usually JSON.parse(manifest.json contents).
 * @param {object} [schema] — override the bundled schema (used by tests).
 * @returns {{valid: true} | {valid: false, errors: Array<{path: string, message: string}>}}
 */
export function validateManifest(manifest, schema) {
    const root = schema ?? loadManifestSchema();
    const errors = [];
    validateNode(manifest, root, '', errors);
    if (errors.length === 0)
        return { valid: true };
    return { valid: false, errors };
}
/** Type check — JSON-Schema's union of "string", "number", "object", "array", "boolean", "null". */
function checkType(value, type) {
    if (type === 'string')
        return typeof value === 'string';
    if (type === 'number')
        return typeof value === 'number' && !Number.isNaN(value);
    if (type === 'integer')
        return typeof value === 'number' && Number.isInteger(value);
    if (type === 'boolean')
        return typeof value === 'boolean';
    if (type === 'null')
        return value === null;
    if (type === 'array')
        return Array.isArray(value);
    if (type === 'object') {
        return value !== null && typeof value === 'object' && !Array.isArray(value);
    }
    return false;
}
/**
 * Human-readable name for the actual shape of a value. Distinguishes
 * `null`, `array`, and plain `object` — JS `typeof` reports all three as
 * `'object'`, which makes error messages like "expected type object, got
 * object" misleading.
 *
 * @param {unknown} value
 * @returns {string}
 */
function describeType(value) {
    if (value === null)
        return 'null';
    if (Array.isArray(value))
        return 'array';
    return typeof value;
}
/**
 * Recursive validator. `errors` is mutated.
 *
 * @param {unknown} value
 * @param {object} schema
 * @param {string} path — JSON-pointer
 * @param {Array<{path: string, message: string}>} errors
 */
function validateNode(value, schema, path, errors) {
    // const
    if ('const' in schema) {
        if (value !== schema.const) {
            errors.push({
                path: path || '/',
                message: `expected const ${JSON.stringify(schema.const)}, got ${JSON.stringify(value)}`,
            });
            return;
        }
    }
    // type
    if (schema.type !== undefined) {
        const types = Array.isArray(schema.type) ? schema.type : [schema.type];
        if (!types.some((t) => checkType(value, t))) {
            errors.push({
                path: path || '/',
                message: `expected type ${types.join('|')}, got ${describeType(value)}`,
            });
            return;
        }
    }
    // enum
    if (Array.isArray(schema.enum)) {
        if (!schema.enum.includes(value)) {
            errors.push({
                path: path || '/',
                message: `value ${JSON.stringify(value)} not in enum [${schema.enum.join(', ')}]`,
            });
            return;
        }
    }
    // pattern
    if (typeof schema.pattern === 'string' && typeof value === 'string') {
        const re = new RegExp(schema.pattern);
        if (!re.test(value)) {
            errors.push({
                path: path || '/',
                message: `value ${JSON.stringify(value)} does not match pattern ${schema.pattern}`,
            });
            return;
        }
    }
    // oneOf
    if (Array.isArray(schema.oneOf)) {
        let matchCount = 0;
        const branchErrors = [];
        for (let i = 0; i < schema.oneOf.length; i++) {
            const sub = schema.oneOf[i];
            const subErrors = [];
            validateNode(value, sub, path, subErrors);
            if (subErrors.length === 0)
                matchCount++;
            else
                branchErrors.push({ branch: i, errors: subErrors });
        }
        if (matchCount !== 1) {
            errors.push({
                path: path || '/',
                message: matchCount === 0
                    ? `oneOf: value matched no branch (${branchErrors.length} branches tried)`
                    : `oneOf: value matched ${matchCount} branches (must match exactly 1)`,
            });
            return;
        }
    }
    // object validation
    if (checkType(value, 'object')) {
        const obj = /** @type {Record<string, unknown>} */ (value);
        if (Array.isArray(schema.required)) {
            for (const key of schema.required) {
                if (!(key in obj)) {
                    errors.push({ path: `${path}/${key}`, message: `required property "${key}" missing` });
                }
            }
        }
        if (schema.properties && typeof schema.properties === 'object') {
            for (const [key, subSchema] of Object.entries(schema.properties)) {
                if (key in obj) {
                    validateNode(obj[key], /** @type {object} */ (subSchema), `${path}/${key}`, errors);
                }
            }
        }
    }
    // array validation
    if (checkType(value, 'array') && schema.items) {
        const arr = /** @type {unknown[]} */ (value);
        for (let i = 0; i < arr.length; i++) {
            validateNode(arr[i], schema.items, `${path}/${i}`, errors);
        }
    }
}
//# sourceMappingURL=manifest-validator.mjs.map