import path from 'path';
import { merge } from 'moderndash';
import * as OpenApiValidator from 'express-openapi-validator';
import { OpenAPIFramework } from 'express-openapi-validator/dist/framework/index.js';
import { getNodeEnv, isProd, isStaging } from './env.js';
import { getFilesInDir } from './express-app/modules.js';
const notImplementedHandler = (req, res) => {
    res.status(501).json({
        code: 'NotImplemented',
        domain: 'http',
        message: 'This method is not yet implemented',
    });
};
function stripExtension(filename) {
    return filename.slice(0, filename.lastIndexOf('.'));
}
export async function openApi(app, rootDirectory, codepath, pattern, openApiOptions) {
    const apiSpec = path.resolve(rootDirectory, `./api/${app.locals.name}.yaml`);
    app.locals.logger.debug({ apiSpec, codepath }, 'Serving OpenAPI');
    const basePath = path.resolve(rootDirectory, `${codepath}/handlers`);
    // Because of the weirdness of ESM/CJS interop, and the synchronous nature of
    // the OpenAPI resolver, we need to preload all the modules we might need
    const moduleFiles = await getFilesInDir(pattern, path.resolve(rootDirectory, `${codepath}/handlers`));
    const preloadedModules = await Promise.all(moduleFiles.map((file) => {
        const fullPath = path.join(basePath, file);
        return import(fullPath).catch((error) => {
            if (isStaging() || isProd()) {
                app.locals.logger.fatal({ file: fullPath, message: error.message }, 'Could not load potential API handler');
                process.exit(1);
            }
            app.locals.logger.warn({ file: fullPath, message: error.message }, 'Could not load potential API handler');
            return undefined;
        });
    }));
    const modulesByPath = moduleFiles.reduce((acc, file, index) => {
        const m = preloadedModules[index];
        if (m) {
            acc[`/${stripExtension(file)}`] = m;
        }
        return acc;
    }, {});
    // This is nuts, but there are testing frameworks or some other things
    // that seem to set window in Node. The OpenAPI infra will fail under that
    // circumstance.
    const _window = global.window;
    if (_window) {
        delete global.window;
    }
    try {
        app.locals.openApiSpecification = await new OpenAPIFramework({ apiDoc: apiSpec })
            .initialize({ visitApi() { } })
            .then((docs) => docs.apiDoc)
            .catch((error) => {
            app.locals.logger.error(error, 'Failed to parse and load OpenAPI spec');
        });
        const defaultOptions = {
            apiSpec: app.locals.openApiSpecification,
            // We force full dereferencing of the OpenAPI spec so that all `$ref` schemas are
            // inlined before express-openapi-validator builds its request coercion rules.
            // Without this, the coercer sees `schema: { $ref: ... }` for query parameters and
            // cannot resolve the underlying type, so it falls back to treating the parameter
            // as a potentially repeated value (i.e., an array). That causes AJV to reject
            // valid requests with “should be array” errors. Dereferencing ensures the
            // coercer sees the real primitive type/enum and stops misclassifying params.
            //
            // The one downside is that circular references are not supported, so if you
            // need that, you need to be very careful with enums, and override this option.
            //
            // See https://github.com/cdimascio/express-openapi-validator/issues/1119
            $refParser: {
                mode: 'dereference',
            },
            ignoreUndocumented: true,
            validateRequests: {
                allowUnknownQueryParameters: true,
                coerceTypes: 'array',
            },
            operationHandlers: {
                basePath,
                resolver(basePath, route) {
                    const pathKey = route.openApiRoute.substring(route.basePath.length);
                    const modulePath = path.join(basePath, pathKey);
                    try {
                        const module = modulesByPath[pathKey];
                        const method = module
                            ? Object.keys(module).find((m) => m.toUpperCase() === route.method)
                            : undefined;
                        if (!module || !method) {
                            throw new Error(`Could not find a [${route.method}] function in ${modulePath} when trying to route [${route.method} ${route.expressRoute}].`);
                        }
                        return module[method];
                    }
                    catch (error) {
                        app.locals.logger.error({
                            error: error.message,
                            pathKey,
                            modulePath: path.relative(rootDirectory, modulePath),
                        }, 'Failed to load API method handler');
                        return notImplementedHandler;
                    }
                },
            },
        };
        const { routing } = app.locals.config;
        const combinedOptions = {
            // In test mode, validate returned swagger responses. This can easily be disabled
            // by setting validateResponses to false in the config.
            ...(getNodeEnv() === 'test'
                ? {
                    validateResponses: {
                        onError(error, body, req) {
                            console.log('Response body fails validation: ', error);
                            console.log('Emitted from:', req.originalUrl);
                            // eslint-disable-next-line no-console
                            console.debug(body);
                            throw error;
                        },
                    },
                }
                : {}),
            ...(typeof routing.openapi === 'object' ? routing.openapi : {}),
            ...openApiOptions,
        };
        return OpenApiValidator.middleware(merge(defaultOptions, combinedOptions));
    }
    finally {
        if (_window) {
            global.window = _window;
        }
    }
}
//# sourceMappingURL=openapi.js.map