import fs from 'fs';
import { customResource } from '../modules/custom-resource';
import { transform } from '../Value';
export class TemplateCreator {
    logicalNames = [];
    resources = [];
    logicalName(prefix) {
        const _prefix = prefix.substring(0, 60);
        const prefixed = this.logicalNames.filter(it => it.startsWith(_prefix));
        if (prefixed.length === 0) {
            this.logicalNames.push(_prefix);
            return prefix;
        }
        else {
            let index = prefixed.length + 1;
            while (this.logicalNames.find(it => it === _prefix + index)) {
                index++;
            }
            this.logicalNames.push(_prefix + index);
            return _prefix + index;
        }
    }
    // eslint-disable-next-line @typescript-eslint/ban-types
    modify(a) {
        return ((...args) => {
            const resource = a(...args);
            const prefix = resource.type.replace(/::/g, '.').split('.').pop();
            const logicalName = resource.logicalName ?? this.logicalName(prefix);
            if (this.resources.find(it => it.logicalName === logicalName)) {
                throw Error(`Logical Name ${logicalName} for resource type ${resource.type} is already being used, try updating it to something else.`);
            }
            const attributes = resource.attributes;
            resource.attributes = Object.keys(attributes || []).reduce((prev, attribute) => ({ ...prev, [attribute]: { 'Fn::GetAtt': [logicalName, attributes[attribute].replace(/_/g, '.')] } }), {});
            resource.logicalName = logicalName;
            this.resources.push(resource);
            return resource;
        });
    }
    static create(aws, builder, file = "template.json", templateTransform = template => JSON.stringify(template, null, 2)) {
        return TemplateCreator.createWithParams(aws, {}, {}, builder, file, "parameters.json", templateTransform);
    }
    static createWithParams(aws, parameters, conditions, builder, file = "template.json", paramsFile = "parameters.json", templateTransform = template => JSON.stringify(template, null, 2), transForms) {
        const template = new TemplateCreator();
        const logicalNameFunction = (prefix) => template.logicalName(prefix);
        const builderAws = Object.keys(aws).reduce((prev1, next1) => ({
            ...prev1, [next1]: Object.keys(aws[next1])
                .filter(name => name !== 'logicalName')
                .reduce((prev, key) => ({ ...prev, [key]: template.modify(aws[next1][key]) }), {})
        }), { logicalName: logicalNameFunction });
        const parameterFunctions = Object.keys(parameters).reduce((prev, parameter) => ({ ...prev, [parameter]: () => ({ 'Ref': parameter }) }), {});
        const outputs = builder({ ...builderAws, customResource: template.modify(customResource) }, parameterFunctions, ((condition, resource) => { resource._condition = condition; return resource; }));
        const envParams = Object.keys(parameters).reduce((prev, param) => {
            const parameter = parameters[param];
            if (parameter.type === 'Future')
                return { ...prev, [param]: { envName: parameter.environmentName } };
            return prev;
        }, {});
        const output = {
            ...(Object.keys(conditions ?? {}).length ? { Conditions: conditions } : {}),
            Parameters: Object.keys(parameters).reduce((prev, param) => {
                const parameter = parameters[param];
                return {
                    ...prev,
                    [param]: {
                        Type: parameter.type === 'Future' ? 'String' : parameter.type,
                        Default: parameter.default,
                        AllowedValues: parameter.allowedValues,
                        Description: parameter.description
                    }
                };
            }, {}),
            Resources: template.resources.reduce((prev, resource) => ({
                ...prev,
                [resource.logicalName]: resource.template(),
            }), {}),
            Transform: transForms,
            Outputs: (outputs && outputs.outputs) ? Object.keys(outputs.outputs).reduce((prev, logicalId) => {
                const output = outputs.outputs[logicalId];
                return ({
                    ...prev,
                    [logicalId]: {
                        Description: output.description,
                        Value: transform(output.value),
                        Export: output.export ? { Name: output.export.name } : undefined
                    }
                });
            }, {}) : undefined
        };
        if (file) {
            const location = file;
            const dir = location.substring(0, location.lastIndexOf('/') + 1);
            if (dir.endsWith('/')) {
                fs.mkdirSync(dir, { recursive: true });
            }
            fs.writeFileSync(file, templateTransform(output));
            fs.writeFileSync(paramsFile, JSON.stringify(envParams));
        }
        return { template: output, outputs: outputs || undefined };
    }
}
