import path from "node:path";
import { parse } from "yaml";
import { S, validate } from "toolcraft-schema";
import { DIAGNOSTIC_CODES, createDiagnostic } from "./diagnostics.js";
export const SUPPORTED_TOOLCRAFT_EDITION = "2026-05-16";
const configSchema = S.Object({
    edition: S.Optional(S.String()),
    environments: S.Optional(S.Record(S.String())),
    client_settings: S.Optional(S.Object({
        idempotency_header: S.Optional(S.String()),
        auth: S.Optional(S.Record(S.Object({
            env: S.Optional(S.String())
        }, { additionalProperties: false })))
    }, { additionalProperties: false })),
    pagination: S.Optional(S.Record(S.Object({
        request: S.Record(S.String()),
        response: S.Record(S.String())
    }, { additionalProperties: false }))),
    retries: S.Optional(S.Object({
        max: S.Optional(S.Number({ jsonType: "integer", minimum: 0 })),
        backoff: S.Optional(S.Enum(["exponential"])),
        retry_on: S.Optional(S.Array(S.Number({ jsonType: "integer", minimum: 100, maximum: 599 })))
    }, { additionalProperties: false })),
    resources: S.Optional(S.Json()),
    readme: S.Optional(S.Json()),
    unspecified_endpoints: S.Optional(S.Array(S.String()))
}, { additionalProperties: false });
export async function readToolcraftConfig(filePath, options = {}) {
    const fs = options.fs ?? (await import("node:fs/promises"));
    const resolvedPath = path.resolve(options.cwd ?? process.cwd(), filePath);
    const source = await fs.readFile(resolvedPath, "utf8");
    const parsed = parseToolcraftYaml(source);
    return validateToolcraftConfig(parsed);
}
function parseToolcraftYaml(source) {
    try {
        return parse(source);
    }
    catch {
        return parse(quoteMethodShorthandValues(source));
    }
}
function quoteMethodShorthandValues(source) {
    let methodsIndent;
    return source
        .split("\n")
        .map((line) => {
        const trimmed = line.trim();
        if (trimmed.length === 0) {
            return line;
        }
        const indent = countLeadingSpaces(line);
        if (methodsIndent !== undefined && indent <= methodsIndent) {
            methodsIndent = undefined;
        }
        if (trimmed === "methods:") {
            methodsIndent = indent;
            return line;
        }
        if (methodsIndent === undefined) {
            return line;
        }
        const separatorIndex = line.indexOf(":");
        if (separatorIndex < 0) {
            return line;
        }
        const value = line.slice(separatorIndex + 1).trim();
        if (!value.includes("{") ||
            value.startsWith("{") ||
            value.startsWith("\"") ||
            value.startsWith("'")) {
            return line;
        }
        return `${line.slice(0, separatorIndex + 1)} ${JSON.stringify(value)}`;
    })
        .join("\n");
}
function countLeadingSpaces(value) {
    let count = 0;
    for (const character of value) {
        if (character !== " ") {
            break;
        }
        count += 1;
    }
    return count;
}
export function validateToolcraftConfig(value) {
    const result = validate(configSchema, value);
    const diagnostics = [];
    if (!isPlainObject(value) || typeof value.edition !== "string") {
        diagnostics.push(createDiagnostic({
            code: DIAGNOSTIC_CODES.invalidEdition,
            severity: "error",
            location: "edition",
            message: `toolcraft.yml must set edition: ${SUPPORTED_TOOLCRAFT_EDITION}.`
        }));
    }
    else if (value.edition !== SUPPORTED_TOOLCRAFT_EDITION) {
        diagnostics.push(createDiagnostic({
            code: DIAGNOSTIC_CODES.invalidEdition,
            severity: "error",
            location: "edition",
            message: `Unsupported edition ${JSON.stringify(value.edition)}. Expected ${SUPPORTED_TOOLCRAFT_EDITION}.`
        }));
    }
    if (!result.ok) {
        diagnostics.push(...result.issues.map((issue) => createDiagnostic({
            code: DIAGNOSTIC_CODES.invalidConfig,
            severity: "error",
            location: issue.path.join("."),
            message: issue.message
        })));
    }
    if (diagnostics.length > 0 || !result.ok) {
        return { diagnostics };
    }
    return {
        config: normalizeToolcraftConfig(result.value),
        diagnostics
    };
}
export function mergeToolcraftConfig(base, override) {
    return deepMerge(base, override);
}
function normalizeToolcraftConfig(value) {
    return {
        ...value,
        resources: normalizeResources(value.resources),
        readme: normalizeReadme(value.readme)
    };
}
function normalizeResources(value) {
    if (!isPlainObject(value)) {
        return undefined;
    }
    return Object.fromEntries(Object.entries(value).map(([name, resource]) => [name, normalizeResource(resource)]));
}
function normalizeResource(value) {
    if (!isPlainObject(value)) {
        return {};
    }
    return {
        methods: normalizeMethods(value.methods),
        subresources: normalizeResources(value.subresources)
    };
}
function normalizeMethods(value) {
    if (!isPlainObject(value)) {
        return undefined;
    }
    return Object.fromEntries(Object.entries(value)
        .map(([name, method]) => [name, normalizeMethod(method)])
        .filter((entry) => entry[1] !== undefined));
}
function normalizeMethod(value) {
    if (typeof value === "string") {
        return parseMethodShorthand(value);
    }
    if (!isPlainObject(value)) {
        return undefined;
    }
    const method = value.method;
    const requestPath = value.path;
    if (!isHttpMethod(method) || typeof requestPath !== "string") {
        return undefined;
    }
    return {
        method,
        path: requestPath,
        ...(typeof value.pagination === "string" ? { pagination: value.pagination } : {}),
        ...(typeof value.idempotent === "boolean" ? { idempotent: value.idempotent } : {})
    };
}
function parseMethodShorthand(value) {
    const parts = value.trim().split(" ").filter((part) => part.length > 0);
    const method = parts[0];
    const requestPath = parts[1];
    if (!isHttpMethod(method) || requestPath === undefined) {
        return undefined;
    }
    const optionsText = parts.slice(2).join(" ");
    const options = parseInlineOptions(optionsText);
    return {
        method,
        path: requestPath,
        ...(typeof options.pagination === "string" ? { pagination: options.pagination } : {}),
        ...(typeof options.idempotent === "boolean" ? { idempotent: options.idempotent } : {})
    };
}
function parseInlineOptions(value) {
    const trimmed = value.trim();
    if (!trimmed.startsWith("{") || !trimmed.endsWith("}")) {
        return {};
    }
    const body = trimmed.slice(1, -1).trim();
    if (body.length === 0) {
        return {};
    }
    const entries = [];
    for (const pair of body.split(",")) {
        const separatorIndex = pair.indexOf(":");
        if (separatorIndex < 0) {
            continue;
        }
        const key = pair.slice(0, separatorIndex).trim();
        const rawValue = pair.slice(separatorIndex + 1).trim();
        entries.push([key, parseInlineScalar(rawValue)]);
    }
    return Object.fromEntries(entries);
}
function parseInlineScalar(value) {
    if (value === "true")
        return true;
    if (value === "false")
        return false;
    return value;
}
function normalizeReadme(value) {
    if (!isPlainObject(value) || !isPlainObject(value.examples)) {
        return undefined;
    }
    const examples = Object.fromEntries(Object.entries(value.examples)
        .filter((entry) => Array.isArray(entry[1]))
        .map(([key, entries]) => [
        key,
        entries
            .filter(isPlainObject)
            .map((entry) => ({
            title: typeof entry.title === "string" ? entry.title : "Example",
            params: isPlainObject(entry.params) ? entry.params : {}
        }))
    ]));
    return { examples };
}
function isHttpMethod(value) {
    return (value === "get" ||
        value === "post" ||
        value === "put" ||
        value === "patch" ||
        value === "delete" ||
        value === "head" ||
        value === "options");
}
function deepMerge(left, right) {
    if (!isPlainObject(left) || !isPlainObject(right)) {
        return cloneMergeValue(right);
    }
    const merged = { ...left };
    for (const [key, value] of Object.entries(right)) {
        merged[key] = key in merged ? deepMerge(merged[key], value) : cloneMergeValue(value);
    }
    return merged;
}
function cloneMergeValue(value) {
    if (Array.isArray(value)) {
        return [...value];
    }
    if (isPlainObject(value)) {
        return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, cloneMergeValue(entry)]));
    }
    return value;
}
function isPlainObject(value) {
    return typeof value === "object" && value !== null && !Array.isArray(value);
}
