"use strict";
var _Base_session;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Base = void 0;
const tslib_1 = require("tslib");
const error_1 = require("../lib/error");
const types_1 = require("../lib/clients/types");
class Base {
    static setClassProperties({ Client, config }) {
        this.Client = Client;
        this.config = config;
    }
    static async baseFind({ session, urlIds, params, }) {
        const response = await this.request({
            http_method: 'get',
            operation: 'get',
            session,
            urlIds,
            params,
        });
        return {
            data: this.createInstancesFromResponse(session, response.body),
            headers: response.headers,
            pageInfo: response.pageInfo,
        };
    }
    static async request({ session, http_method, operation, urlIds, params, body, entity, }) {
        const client = new this.Client({
            session,
            apiVersion: this.apiVersion,
        });
        const path = this.getPath({ http_method, operation, urlIds, entity });
        const cleanParams = {};
        if (params) {
            for (const key in params) {
                if (params[key] !== null) {
                    cleanParams[key] = params[key];
                }
            }
        }
        switch (http_method) {
            case 'get':
                return client.get({ path, query: cleanParams });
            case 'post':
                return client.post({
                    path,
                    query: cleanParams,
                    data: body,
                    type: types_1.DataType.JSON,
                });
            case 'put':
                return client.put({
                    path,
                    query: cleanParams,
                    data: body,
                    type: types_1.DataType.JSON,
                });
            case 'delete':
                return client.delete({ path, query: cleanParams });
            default:
                throw new Error(`Unrecognized HTTP method "${http_method}"`);
        }
    }
    static getJsonBodyName() {
        return this.name.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase();
    }
    static getPath({ http_method, operation, urlIds, entity, }) {
        let match = null;
        let specificity = -1;
        const potentialPaths = [];
        this.paths.forEach((path) => {
            if (http_method !== path.http_method ||
                operation !== path.operation ||
                path.ids.length <= specificity) {
                return;
            }
            potentialPaths.push(path);
            let pathUrlIds = { ...urlIds };
            path.ids.forEach((id) => {
                if (!pathUrlIds[id] && entity && entity[id]) {
                    pathUrlIds[id] = entity[id];
                }
            });
            pathUrlIds = Object.entries(pathUrlIds).reduce((acc, [key, value]) => {
                if (value) {
                    acc[key] = value;
                }
                return acc;
            }, {});
            // If we weren't given all of the path's required ids, we can't use it
            const diff = path.ids.reduce((acc, id) => (pathUrlIds[id] ? acc : acc.concat(id)), []);
            if (diff.length > 0) {
                return;
            }
            specificity = path.ids.length;
            match = path.path.replace(/(<([^>]+)>)/g, (_m1, _m2, id) => `${pathUrlIds[id]}`);
        });
        if (!match) {
            const pathOptions = potentialPaths.map((path) => path.path);
            throw new error_1.RestResourceError(`Could not find a path for request. If you are trying to make a request to one of the following paths, ensure all relevant IDs are set. :\n - ${pathOptions.join('\n - ')}`);
        }
        if (this.customPrefix) {
            return `${this.customPrefix}/${match}`;
        }
        else {
            return match;
        }
    }
    static createInstancesFromResponse(session, data) {
        let instances = [];
        this.resourceNames.forEach((resourceName) => {
            const singular = resourceName.singular;
            const plural = resourceName.plural;
            if (data[plural] || Array.isArray(data[singular])) {
                instances = instances.concat((data[plural] || data[singular]).reduce((acc, entry) => acc.concat(this.createInstance(session, entry)), []));
            }
            else if (data[singular]) {
                instances.push(this.createInstance(session, data[singular]));
            }
        });
        return instances;
    }
    static createInstance(session, data, prevInstance) {
        const instance = prevInstance
            ? prevInstance
            : new this({ session });
        if (data) {
            instance.setData(data);
        }
        return instance;
    }
    get session() {
        return tslib_1.__classPrivateFieldGet(this, _Base_session, "f");
    }
    constructor({ session, fromData }) {
        _Base_session.set(this, void 0);
        tslib_1.__classPrivateFieldSet(this, _Base_session, session, "f");
        if (fromData) {
            this.setData(fromData);
        }
    }
    async save({ update = false } = {}) {
        const { primaryKey, resourceNames } = this.resource();
        const method = this[primaryKey] ? 'put' : 'post';
        const data = this.serialize(true);
        const response = await this.resource().request({
            http_method: method,
            operation: method,
            session: this.session,
            urlIds: {},
            body: { [this.resource().getJsonBodyName()]: data },
            entity: this,
        });
        const flattenResourceNames = resourceNames.reduce((acc, obj) => {
            return acc.concat(Object.values(obj));
        }, []);
        const matchResourceName = Object.keys(response.body).filter((key) => flattenResourceNames.includes(key));
        const body = response.body[matchResourceName[0]];
        if (update && body) {
            this.setData(body);
        }
    }
    async saveAndUpdate() {
        await this.save({ update: true });
    }
    async delete() {
        await this.resource().request({
            http_method: 'delete',
            operation: 'delete',
            session: this.session,
            urlIds: {},
            entity: this,
        });
    }
    serialize(saving = false) {
        const { hasMany, hasOne, readOnlyAttributes } = this.resource();
        return Object.entries(this).reduce((acc, [attribute, value]) => {
            if (['#session'].includes(attribute) ||
                (saving && readOnlyAttributes.includes(attribute))) {
                return acc;
            }
            if (attribute in hasMany && value) {
                acc[attribute] = value.reduce((attrAcc, entry) => {
                    return attrAcc.concat(this.serializeSubAttribute(entry, saving));
                }, []);
            }
            else if (attribute in hasOne && value) {
                acc[attribute] = this.serializeSubAttribute(value, saving);
            }
            else {
                acc[attribute] = value;
            }
            return acc;
        }, {});
    }
    toJSON() {
        return this.serialize();
    }
    request(args) {
        return this.resource().request(args);
    }
    setData(data) {
        const { hasMany, hasOne } = this.resource();
        Object.entries(data).forEach(([attribute, val]) => {
            if (attribute in hasMany) {
                const HasManyResource = hasMany[attribute];
                this[attribute] = [];
                val.forEach((entry) => {
                    this[attribute].push(new HasManyResource({ session: this.session, fromData: entry }));
                });
            }
            else if (attribute in hasOne) {
                const HasOneResource = hasOne[attribute];
                this[attribute] = new HasOneResource({
                    session: this.session,
                    fromData: val,
                });
            }
            else {
                this[attribute] = val;
            }
        });
    }
    resource() {
        return this.constructor;
    }
    serializeSubAttribute(attribute, saving) {
        return attribute.serialize
            ? attribute.serialize(saving)
            : this.resource()
                .createInstance(this.session, attribute)
                .serialize(saving);
    }
}
exports.Base = Base;
_Base_session = new WeakMap();
Base.resourceNames = [];
Base.primaryKey = 'id';
Base.customPrefix = null;
Base.readOnlyAttributes = [];
Base.hasOne = {};
Base.hasMany = {};
Base.paths = [];
//# sourceMappingURL=base.js.map