import { join, joinWith } from "../Value";
export class Path {
    aws;
    api;
    resources;
    parent;
    methods;
    optionsMethod;
    paths;
    constructor(aws, api, resources, parent, methods = [], optionsMethod, paths = []) {
        this.aws = aws;
        this.api = api;
        this.resources = resources;
        this.parent = parent;
        this.methods = methods;
        this.optionsMethod = optionsMethod;
        this.paths = paths;
    }
    route() {
        const parentPath = this.parent ? (this.parent.route() + '/') : '';
        return parentPath + this.resources.map(it => it.properties.pathPart).join('/');
    }
    path(path) {
        const resources = Path.resources(this.aws, this.api, path, this.resources[this.resources.length - 1]);
        const p = new Path(this.aws, this.api, resources, this).options();
        this.paths.push(p);
        return p;
    }
    longPath(parts, info) {
        if (parts.length === 0) {
            (info.methods ?? []).forEach(method => this.method(method).options());
            if (info.paths) {
                Object.keys(info.paths).map(path => this.paths.push(Path.longPath(this.aws, this.api, path, info.paths[path], this)));
            }
            if (info.options && info.methods && info.methods.length > 0) {
                const { origin, headers, credentials } = info.options;
                this.options(origin, headers, credentials);
            }
            return this;
        }
        else {
            const nextPart = Path.longPath(this.aws, this.api, '/' + parts.join('/'), info, this);
            this.paths.push(nextPart);
            return nextPart;
        }
    }
    pathLogicalNames() {
        const resources = this.resources.map(it => it.properties.pathPart.toString().startsWith('{') ? (it.properties.pathPart.toString().substring(1, it.properties.pathPart.toString().length - 1) + 'Var') : it.properties.pathPart.toString()).join('');
        return (this.parent?.pathLogicalNames() ?? '') + resources;
    }
    logicalName() {
        return this.api.name.replace(/[^\w]+/g, '') + this.pathLogicalNames();
    }
    method(method) {
        const apiMethod = this.aws.apigateway.method({
            httpMethod: method,
            resourceId: this.resources[this.resources.length - 1],
            restApiId: this.api.restApi,
            apiKeyRequired: false,
            ...(this.api.authorizer ? { authorizationType: 'COGNITO_USER_POOLS', authorizerId: this.api.authorizer } : { authorizationType: 'NONE' }),
            ...(this.api.lambdaArn ? { integration: {
                    integrationHttpMethod: 'POST',
                    type: 'AWS_PROXY',
                    uri: joinWith(':', 'arn', { Ref: 'AWS::Partition' }, 'apigateway', { Ref: 'AWS::Region' }, join('lambda:path/2015-03-31/functions/', this.api.lambdaArn, '/invocations')),
                } } : {}),
            methodResponses: [],
            requestParameters: {}
        }).withLogicalName(this.aws.logicalName(`Method${this.logicalName()}${method}`));
        this.methods.push(apiMethod);
        this.api.deployment.withDependency(...(this.api.deployment.dependsOn ?? []), apiMethod);
        return this;
    }
    options(origin = '*', headers = [], credentials = true) {
        const corsOrigin = 'method.response.header.Access-Control-Allow-Origin';
        const corsHeaders = 'method.response.header.Access-Control-Allow-Headers';
        const corsMethods = 'method.response.header.Access-Control-Allow-Methods';
        const corsCredentials = 'method.response.header.Access-Control-Allow-Credentials';
        const methods = this.methods.map(it => it.properties.httpMethod).join();
        if (this.optionsMethod) {
            this.optionsMethod.properties.integration.integrationResponses[0].responseParameters = {
                _nocaps: '',
                [corsOrigin]: `'${origin}'`,
                [corsHeaders]: `'${headers.join() || '*'}'`,
                [corsMethods]: `'${methods}'`,
                [corsCredentials]: `'${credentials}'`
            };
        }
        else {
            this.optionsMethod = this.aws.apigateway.method({
                httpMethod: 'OPTIONS',
                resourceId: this.resources[this.resources.length - 1],
                restApiId: this.api.restApi,
                authorizationType: 'NONE',
                methodResponses: [{
                        statusCode: '200',
                        responseModels: {},
                        responseParameters: {
                            _nocaps: '',
                            [corsOrigin]: true,
                            [corsHeaders]: true,
                            [corsMethods]: true,
                            [corsCredentials]: true
                        }
                    }],
                requestParameters: {},
                integration: {
                    type: 'MOCK',
                    requestTemplates: {
                        _nocaps: '',
                        'application/json': '{statusCode:200}'
                    },
                    contentHandling: 'CONVERT_TO_TEXT',
                    integrationResponses: [
                        {
                            statusCode: '200',
                            responseParameters: {
                                _nocaps: '',
                                [corsOrigin]: `'${origin}'`,
                                [corsHeaders]: `'${headers.join() || '*'}'`,
                                [corsMethods]: `'${methods}'`,
                                [corsCredentials]: `'${credentials}'`
                            },
                            responseTemplates: {
                                _nocaps: '',
                                'application/json': '#set($origin = $input.params("Origin"))\n#if($origin == "") #set($origin = $input.params("origin")) #end\n#if($origin == "*") #set($context.responseOverride.header.Access-Control-Allow-Origin = $origin) #end'
                            }
                        }
                    ]
                }
            }).withLogicalName(this.aws.logicalName(`Method${this.logicalName()}Options`));
        }
        return this;
    }
    static resources(aws, api, path, parent, parentLogicalName) {
        const parts = path.startsWith('/') ? path.substring(1).split('/') : path.split('/');
        let previous = parent;
        let pathName = '';
        const resources = [];
        for (const part of parts) {
            pathName += part.startsWith('{') ? (part.substring(1, part.length - 1) + 'Var') : part;
            const resource = aws.apigateway.resource({
                parentId: previous,
                restApiId: api.restApi,
                pathPart: part,
            }).withLogicalName(aws.logicalName(`Api${api.name.replace(/[^\w]+/g, '')}${parentLogicalName ?? ''}${pathName}`));
            resources.push(resource);
            previous = resource;
        }
        return resources;
    }
    static longPath(aws, api, path, info, parent) {
        const parentLogicalName = parent?.pathLogicalNames();
        const newPathResources = Path.resources(aws, api, path, parent?.resources?.[parent.resources.length - 1] ?? api.restApi.attributes.RootResourceId, parentLogicalName);
        const infoOptions = info?.options;
        const newPath = new Path(aws, api, newPathResources, parent)
            .options(infoOptions?.origin ?? '*', infoOptions?.headers ?? [], infoOptions?.credentials ?? true)
            .longPath([], info);
        newPath.parent = parent;
        return newPath;
    }
    static resource(aws, api, path) {
        const resources = Path.resources(aws, api, path, api.restApi.attributes.RootResourceId);
        return new Path(aws, api, resources).options();
    }
}
export class Api {
    aws;
    name;
    restApi;
    deployment;
    authorizer;
    lambdaArn;
    lambdaPermission;
    basePathMapping;
    paths;
    constructor(aws, name, restApi, deployment, authorizer, lambdaArn, lambdaPermission, basePathMapping, paths = []) {
        this.aws = aws;
        this.name = name;
        this.restApi = restApi;
        this.deployment = deployment;
        this.authorizer = authorizer;
        this.lambdaArn = lambdaArn;
        this.lambdaPermission = lambdaPermission;
        this.basePathMapping = basePathMapping;
        this.paths = paths;
    }
    pathMethodsFrom(path) {
        const mainRoutes = path.methods.map(method => ({ path: path.route(), method: method.properties.httpMethod.toString() }));
        const subRoutes = path.paths.flatMap(this.pathMethodsFrom.bind(this));
        return [...mainRoutes, ...subRoutes];
    }
    definition() {
        return { resources: this.paths.flatMap(this.pathMethodsFrom.bind(this)) };
    }
    /**
     * @param path Can include variables in { } like a/b/{c}
     */
    path(path) {
        const p = Path.resource(this.aws, this, path);
        this.paths.push(p);
        return p;
    }
    mapTo(basePath, domainName) {
        this.basePathMapping = this.aws.apigateway.basePathMapping({
            restApiId: this.restApi,
            stage: this.deployment.properties.stageName,
            basePath,
            domainName
        }).withDependency(this.deployment);
        return this;
    }
    mapToDomain(domainName) {
        this.basePathMapping = this.aws.apigateway.basePathMapping({
            restApiId: this.restApi,
            stage: this.deployment.properties.stageName,
            domainName
        }).withDependency(this.deployment);
        return this;
    }
    apiFrom(pathInfo) {
        this.paths = Object.keys(pathInfo).map(path => Path.longPath(this.aws, this, path, pathInfo[path]));
        return this;
    }
    static create(aws, name, stage, providerArns, lambdaArn, tags) {
        const restApi = aws.apigateway.restApi({ name, tags });
        const authorizer = providerArns && providerArns.length > 0 ? aws.apigateway.authorizer({
            authorizerResultTtlInSeconds: 300,
            providerARNs: providerArns,
            identitySource: 'method.request.header.Authorization',
            type: 'COGNITO_USER_POOLS',
            restApiId: restApi,
            name: `api-auth-${name}`,
        }) : undefined;
        const permission = lambdaArn ? aws.lambda.permission({
            action: 'lambda:InvokeFunction',
            functionName: lambdaArn,
            principal: joinWith('.', 'apigateway', { Ref: 'AWS::URLSuffix' }),
            sourceArn: joinWith(':', 'arn', { Ref: 'AWS::Partition' }, 'execute-api', { Ref: 'AWS::Region' }, { Ref: 'AWS::AccountId' }, join(restApi, '/*/*'))
        }) : undefined;
        const deployment = aws.apigateway.deployment({
            restApiId: restApi,
            stageName: stage
        }).withLogicalName(aws.logicalName('ApiDeployment' + Math.floor(Math.random() * 100000000)));
        return new Api(aws, name, restApi, deployment, authorizer, lambdaArn, permission, undefined);
    }
}
