/**
 * Derive searchable Operation[] from a native OpenAPI 3.x document.
 */
import { INLINE_OPENAPI_REQUEST_BODY } from "./operation-types.js";
const HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head", "options"];
export function operationsFromOpenAPI(doc) {
    const paths = doc.paths ?? {};
    const ops = [];
    for (const [pathKey, pathItem] of Object.entries(paths)) {
        if (!pathItem || typeof pathItem !== "object")
            continue;
        const pi = pathItem;
        for (const method of HTTP_METHODS) {
            const op = pi[method];
            if (!op || typeof op !== "object")
                continue;
            const operation = op;
            const operationId = typeof operation.operationId === "string" && operation.operationId.length > 0
                ? operation.operationId
                : `${method}_${slugPath(pathKey)}`;
            const parameters = mapOpenAPIParameters(operation.parameters);
            const requestBody = extractRequestBodySchemaName(operation.requestBody);
            const responseBody = extractResponseSchemaName(operation.responses);
            const scopes = extractScopes(operation.security, doc);
            ops.push({
                id: operationId,
                method: method.toUpperCase(),
                path: pathKey.startsWith("/") ? pathKey.slice(1) : pathKey,
                flatPath: pathKey.startsWith("/") ? pathKey.slice(1) : pathKey,
                description: (typeof operation.summary === "string" && operation.summary) ||
                    (typeof operation.description === "string" && operation.description) ||
                    "",
                resource: inferResource(pathKey),
                parameters,
                scopes,
                requestBody,
                responseBody,
            });
        }
    }
    return ops;
}
function slugPath(pathKey) {
    return pathKey
        .replace(/^\//, "")
        .replace(/[^a-zA-Z0-9]+/g, "_")
        .replace(/^_|_$/g, "")
        .slice(0, 80);
}
function inferResource(pathKey) {
    const parts = pathKey.split("/").filter(Boolean);
    const last = parts[parts.length - 1] ?? "api";
    const cleaned = last.replace(/\{[^}]+\}/g, "").replace(/[:]/g, "");
    return cleaned.length > 0 ? cleaned : "resource";
}
function mapOpenAPIParameters(params) {
    const out = {};
    if (!Array.isArray(params))
        return out;
    for (const p of params) {
        if (!p || typeof p !== "object")
            continue;
        const param = p;
        const loc = param.in;
        if (loc !== "path" && loc !== "query")
            continue;
        const name = String(param.name ?? "");
        if (!name)
            continue;
        const schema = param.schema || {};
        const type = typeof schema.type === "string" ? schema.type : "string";
        out[name] = {
            type,
            location: loc,
            required: Boolean(param.required),
            description: typeof param.description === "string" ? param.description : "",
        };
    }
    return out;
}
function schemaRefToName(schema) {
    if (!schema || typeof schema !== "object")
        return undefined;
    const s = schema;
    if (typeof s.$ref !== "string")
        return undefined;
    const m = s.$ref.match(/#\/components\/schemas\/(.+)$/);
    return m ? m[1] : undefined;
}
function extractRequestBodySchemaName(requestBody) {
    if (!requestBody || typeof requestBody !== "object")
        return undefined;
    const rb = requestBody;
    const content = rb.content;
    if (!content)
        return undefined;
    for (const ct of [
        "application/json",
        "application/hal+json",
        "multipart/form-data",
        "application/x-www-form-urlencoded",
    ]) {
        const media = content[ct];
        if (!media?.schema)
            continue;
        const name = schemaRefToName(media.schema);
        if (name)
            return name;
        return INLINE_OPENAPI_REQUEST_BODY;
    }
    return undefined;
}
function extractResponseSchemaName(responses) {
    if (!responses || typeof responses !== "object")
        return undefined;
    const r = responses;
    for (const code of ["200", "201", "202", "204", "default"]) {
        const resp = r[code];
        if (!resp || typeof resp !== "object")
            continue;
        const content = resp.content;
        if (!content)
            continue;
        for (const ct of [
            "application/json",
            "application/hal+json",
            "*/*",
            "application/octet-stream",
        ]) {
            const media = content[ct];
            if (!media?.schema)
                continue;
            const name = schemaRefToName(media.schema);
            if (name)
                return name;
        }
    }
    return undefined;
}
function extractScopes(operationSecurity, doc) {
    const scopes = new Set();
    const collect = (sec) => {
        if (!Array.isArray(sec))
            return;
        for (const item of sec) {
            if (!item || typeof item !== "object")
                continue;
            for (const [name, arr] of Object.entries(item)) {
                if (Array.isArray(arr)) {
                    for (const s of arr) {
                        if (typeof s === "string")
                            scopes.add(s);
                    }
                }
                else if (name && !scopes.size) {
                    scopes.add(name);
                }
            }
        }
    };
    collect(operationSecurity);
    if (scopes.size === 0)
        collect(doc.security);
    return [...scopes];
}
