import { Property } from './Property.js';
import { Server } from './Server.js';
import { Thing } from './Thing.js';
import { nanoid } from '../nanoid.js';
import { RequestAuthorization } from './RequestAuthorization.js';
export const Kind = 'Core#Environment';
/**
 * An environment is applied to a project or a folder.
 * It consists of a list of variables and a server configuration.
 * This allows to apply the runtime configuration just by changing the environment.
 */
export class Environment {
    kind = Kind;
    key = '';
    /**
     * The name of the environment.
     */
    info = Thing.fromName('');
    /**
     * The variables added to this project.
     * It overrides application defined variables.
     */
    variables = [];
    /**
     * The server's definition for the environment.
     * When a server is defined for the environment then all request that have no
     * absolute URL will inherit this server configuration.
     */
    server;
    /**
     * When set this configuration does not allow to be extended by the parent object's environment configuration.
     * By default an environment can be extended by the parent object's values.
     *
     * When encapsulation is disabled you can, for example, skip server definition and only overwrite variables.
     */
    encapsulated = false;
    /**
     * The security to be applied to all requests that are going to this server.
     */
    authorization;
    /**
     * Creates a new Environment object from a name.
     *
     * @param name The name to set.
     */
    static fromName(name) {
        const key = nanoid();
        const info = Thing.fromName(name);
        const definition = new Environment({
            key,
            kind: Kind,
            info: info.toJSON(),
            variables: [],
        });
        return definition;
    }
    static fromLegacyVariables(name, variables) {
        const key = nanoid();
        const info = Thing.fromName(name);
        const definition = new Environment({
            key,
            kind: Kind,
            info: info.toJSON(),
            variables: [],
        });
        variables.forEach((i) => {
            const name = i.variable || i.name;
            const property = Property.fromApiType(i);
            property.name = name;
            definition.variables.push(property);
        });
        return definition;
    }
    /**
     * @param input The environment definition used to restore the state.
     */
    constructor(input) {
        let init;
        if (typeof input === 'string') {
            init = JSON.parse(input);
        }
        else if (typeof input === 'object') {
            init = input;
        }
        else {
            init = {
                kind: Kind,
                key: nanoid(),
                info: Thing.fromName('').toJSON(),
                variables: [],
            };
        }
        this.new(init);
    }
    /**
     * Creates a new environment clearing anything that is so far defined.
     *
     * Note, this throws an error when the environment is not an environment.
     */
    new(init) {
        if (!Environment.isEnvironment(init)) {
            throw new Error(`Not an environment.`);
        }
        const { key = nanoid(), variables, info, server, encapsulated = false, authorization } = init;
        this.kind = Kind;
        this.key = key;
        this.encapsulated = encapsulated;
        if (Array.isArray(authorization)) {
            this.authorization = authorization.map((i) => new RequestAuthorization(i));
        }
        else {
            this.authorization = undefined;
        }
        if (Array.isArray(variables)) {
            this.variables = variables.map((i) => new Property(i));
        }
        else {
            this.variables = [];
        }
        if (server) {
            this.server = new Server(server);
        }
        else {
            this.server = undefined;
        }
        if (info) {
            this.info = new Thing(info);
        }
        else {
            this.info = Thing.fromName('');
        }
    }
    /**
     * Checks whether the input is a definition of a environment.
     */
    static isEnvironment(input) {
        const typed = input;
        if (!input || typed.kind !== Kind) {
            return false;
        }
        return true;
    }
    toJSON() {
        const { variables = [], encapsulated = false, server } = this;
        const result = {
            kind: Kind,
            key: this.key,
            info: this.info.toJSON(),
            variables: variables.map((v) => v.toJSON()),
            encapsulated,
        };
        if (server) {
            result.server = server.toJSON();
        }
        if (Array.isArray(this.authorization)) {
            result.authorization = this.authorization.map((i) => i.toJSON());
        }
        return result;
    }
    /**
     * Adds a new variable to the list of variables.
     * It makes sure the variables property is initialized.
     */
    addVariable(variableOrName, value) {
        if (!Array.isArray(this.variables)) {
            this.variables = [];
        }
        let prop;
        if (typeof variableOrName === 'string') {
            prop = Property.fromType(variableOrName, value);
        }
        else {
            prop = new Property(variableOrName);
        }
        this.variables.push(prop);
        return prop;
    }
    /**
     * Reads the server configuration.
     * @param force When set then it creates a server instance when missing.
     */
    getServer(force) {
        if (!this.server && !force) {
            return undefined;
        }
        if (!this.server) {
            this.server = new Server();
        }
        return this.server;
    }
    /**
     * Adds a new server definition.
     * @param uriOrInfo Either URI to create the server from or the definition of the server.
     */
    addServer(uriOrInfo) {
        let srv;
        if (typeof uriOrInfo === 'string') {
            srv = Server.fromUri(uriOrInfo);
        }
        else {
            srv = new Server(uriOrInfo);
        }
        this.server = srv;
        return srv;
    }
    /**
     * Makes a copy of this environment
     * @param opts Cloning options.
     */
    clone(opts = {}) {
        const copy = new Environment(this.toJSON());
        if (!opts.withoutRevalidate) {
            copy.key = nanoid();
        }
        return copy;
    }
    /**
     * Creates a map of variables from this environment.
     *
     * @param readStrategy The read strategy. When `lastWins` (default) then a variable with the same name overwrites
     *                     the previously set variable. The `firstWins` stops overwriting values once a variable is set.
     * @returns An object containing variables defined in this environment
     */
    variablesMap(readStrategy = 'lastWins') {
        const result = {};
        const { variables = [] } = this;
        variables.forEach((item) => {
            const { enabled, name, value } = item;
            if (!enabled || !name || value === undefined) {
                return;
            }
            if (readStrategy === 'firstWins' && name in result) {
                return;
            }
            result[name] = value;
        });
        return result;
    }
    /**
     * @returns A list of unique properties. The last defined property wins (with the same name).
     */
    uniqueProperties() {
        const flatProperties = [];
        if (this.variables) {
            for (const prop of this.variables) {
                const index = flatProperties.findIndex((i) => i.name === prop.name);
                if (index >= 0) {
                    flatProperties.splice(index, 1);
                }
                flatProperties.push(prop);
            }
        }
        return flatProperties;
    }
}
//# sourceMappingURL=Environment.js.map