"use strict";
var ApiClientPlusLocal_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.ApiClientPlusLocal = exports.ApiClientPlusLocalConfig = void 0;
const tslib_1 = require("tslib");
const path = require("path");
const inversify_1 = require("inversify");
const _ = require("lodash");
const codiac_common_1 = require("@codiac.io/codiac-common");
const type_safety_utils_1 = require("../../uilogic/type-safety-utils");
const codiac_api_1 = require("../codiac-api");
const codiac_relay_1 = require("../codiac-relay");
const asset_where_entity_merger_1 = require("./asset-where-entity-merger");
const enterprise_state_entity_merger_1 = require("./enterprise-state-entity-merger");
const root_map_entity_merger_1 = require("./root-map-entity-merger");
function asBatchResult(candidate) {
    var _a;
    let br = candidate;
    return (Boolean((_a = br.successes) !== null && _a !== void 0 ? _a : br.failures)) ? br : undefined;
}
function asListResult(candidate) {
    let lr = candidate;
    return Boolean(lr.output && _.isArray(lr.output)) ? lr : undefined;
}
function asSingleResult(candidate) {
    let sr = candidate;
    return Boolean(sr.output && !_.isArray(sr.output)) ? sr : undefined;
}
function asOutputResult(candidate) {
    let sr = candidate;
    return Boolean(sr.output) ? sr : undefined;
}
class ApiClientPlusLocalConfig {
}
exports.ApiClientPlusLocalConfig = ApiClientPlusLocalConfig;
/** Wrapper for the Codiac Api that redirects calls from the remote codiac api
 * to the to the local api endpoints (in the relay),
 * based on whether the entity is flagged as a local entity or not.
 */
let ApiClientPlusLocal = ApiClientPlusLocal_1 = class ApiClientPlusLocal extends codiac_api_1.CodiacRestClient {
    /** This factory method is just for convenience and hasty backward compatibility with consumers not using ioc. */
    static createPlus(relayClient, logger, localDataRoutePrefix, apiRootUrl, authToken) {
        let settings = localDataRoutePrefix ? { localDataRoutePrefix: localDataRoutePrefix } : undefined;
        return new ApiClientPlusLocal_1(relayClient, logger, { apiBaseUrl: apiRootUrl }, settings, (authToken) ? { apiToken: authToken } : undefined);
    }
    // public static bootstrap(ioc: interfaces.Container, settings?: ApiClientPlusLocalConfig | undefined): void {
    //   let ultimate = _.merge({}, settings);
    //   ioc.bind(ApiClientPlusLocalConfig).toConstantValue(ultimate);
    // }
    constructor(relayClientWithLocalData, logger, remoteApiSettings, settings, credentials) {
        super(logger, remoteApiSettings, credentials);
        this.settings = settings;
        // HACK: Hard cast here because we dont have an interface for the local data api plugin
        this.localDataClient = relayClientWithLocalData;
        this.entityMergers = {
            "assetWhere": new asset_where_entity_merger_1.AssetWhereResponseMerger(),
            "getRootMap": new root_map_entity_merger_1.GetRootMapResponseMerger(),
            "getDeployedState": new enterprise_state_entity_merger_1.DeployedStateEntityMerger()
        };
    }
    tryMakeRestCall(ep, request, options) {
        const _super = Object.create(null, {
            tryMakeRestCall: { get: () => super.tryMakeRestCall }
        });
        var _a, _b, _c, _d;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            let response = { success: false };
            try {
                let epLocal = _.clone(ep);
                if ((_a = this.settings) === null || _a === void 0 ? void 0 : _a.localDataRoutePrefix)
                    epLocal.route = path.posix.join(this.settings.localDataRoutePrefix, ep.route);
                let isLocal = (_b = type_safety_utils_1.TypeSafetyUtils.toTriState(options === null || options === void 0 ? void 0 : options.thisMachine)) !== null && _b !== void 0 ? _b : type_safety_utils_1.TypeSafetyUtils.toTriState(request.thisMachine);
                if (isLocal == true) {
                    // Request made specifically for local entity(ies)
                    response = yield this.localDataClient.tryMakeRestCall(epLocal, request, options);
                }
                else {
                    if (ep.verb == codiac_common_1.httpRestVerb.GET) {
                        // READ OPERATION (Pull both unless filtered on local)
                        let responseRemote = yield _super.tryMakeRestCall.call(this, ep, request, options);
                        // Skip the local if remote call returned an error.
                        if (responseRemote.error)
                            return responseRemote;
                        // Q: Add locals to the output?
                        // A: Yes, unless they specifically wanted locals omitted (that feature doesnt exist yet, so dont worry about it)
                        // A: OR unless theyre asking for a single record, in which case the local shouldnt return one anyway, but its a waste to ask.
                        // A: OR unless the local api doesnt have a method for that endpoint
                        // what if remote fails but not local?  Throw
                        // what if local fails but not remote?  log a warning/error but dont throw
                        let localResponse;
                        if (isLocal != false) /*  Unless they explicitly omitted local data  */ {
                            if (this.localDataClient[ep.entityTypeName]) {
                                localResponse = yield this.localDataClient.tryMakeRestCall(epLocal, request, options);
                            }
                        }
                        response = this.absorbOutput(responseRemote, localResponse);
                    }
                    else {
                        // WRITE OPERATION
                        // TODO: Investigate specifying thisMachine on patch specs and idspecs
                        response = yield _super.tryMakeRestCall.call(this, ep, request, options);
                    }
                }
            }
            catch (err) {
                response.error = err;
                response.message = "Error performing ".concat(ep.op, " ", ep.entityTypeName, ": ", (_c = err.name) !== null && _c !== void 0 ? _c : "", (_d = err.message) !== null && _d !== void 0 ? _d : "");
            }
            finally {
                return response;
            }
        });
    }
    makeAnyRestCall(ep, request, options) {
        const _super = Object.create(null, {
            makeAnyRestCall: { get: () => super.makeAnyRestCall }
        });
        var _a, _b;
        return tslib_1.__awaiter(this, void 0, void 0, function* () {
            try {
                let epLocal = _.clone(ep);
                if ((_a = this.settings) === null || _a === void 0 ? void 0 : _a.localDataRoutePrefix)
                    epLocal.route = path.posix.join(this.settings.localDataRoutePrefix, ep.route);
                let isLocal = (_b = type_safety_utils_1.TypeSafetyUtils.toTriState(options === null || options === void 0 ? void 0 : options.thisMachine)) !== null && _b !== void 0 ? _b : type_safety_utils_1.TypeSafetyUtils.toTriState(request.thisMachine);
                if (isLocal == true) {
                    // Request made specifically for local entity(ies)
                    return yield this.localDataClient.makeAnyRestCall(epLocal, request, options);
                }
                else {
                    if (ep.verb == codiac_common_1.httpRestVerb.GET) {
                        let responseRemote = yield _super.makeAnyRestCall.call(this, ep, request, options);
                        // Skip the local if remote call returned an error.
                        if (responseRemote.error)
                            return responseRemote;
                        let localResponse;
                        if (isLocal != false) /*  Unless they explicitly omitted local data  */ {
                            if (this.localDataClient.Call[ep.op]) {
                                localResponse = yield this.localDataClient.makeAnyRestCall(epLocal, request, options);
                            }
                        }
                        if (localResponse) {
                            const composite = this.merge(ep.op, responseRemote, localResponse);
                            return composite;
                        }
                        else {
                            return responseRemote;
                        }
                    }
                    else {
                        return yield _super.makeAnyRestCall.call(this, ep, request, options);
                    }
                }
                // return response;
            }
            catch (err) {
                throw err;
            }
        });
    }
    /** Both params MUST be of the SAME RESULT TYPE.
     *
     * Ignores additional if original has an error.
     *
     * Mutates the original by appending the output of the additional.
     * NOTE: `error`, `success`, and `message` from the additional ARE IGNORED by default.
     *
     * @param absorbErrors (default: false) When true, error, success, and message from the additional shall be brought forward in the ultimate result.
     */
    absorbOutput(original, additional, absorbErrors = false) {
        var _a, _b, _c, _d, _e, _f;
        try {
            // Ignore additional if the original errored out.
            if (additional == undefined)
                return original;
            if (original.error)
                return original;
            // If we're reading we still want success when original returned none,
            // but additional returned at least one.
            original.success = original.success || additional.success;
            if (absorbErrors == true) {
                if (additional.message && !_.isEmpty(additional.message)) {
                    original.message = ((_a = original.message) !== null && _a !== void 0 ? _a : "").concat("\n", additional.message).trim();
                }
                if (additional.error) {
                    original.error = additional.error;
                }
            }
            // ListResult OUTPUT
            let lrAddl = asListResult(additional);
            if (lrAddl && lrAddl.output && lrAddl.output.length > 0) {
                let lrOrig = original;
                lrOrig.output = (_b = lrOrig.output) !== null && _b !== void 0 ? _b : [];
                lrOrig.output.push(...lrAddl.output);
                lrOrig.count = (_d = (_c = lrOrig.output) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0;
                lrOrig.success = Boolean(lrOrig.output.length > 0);
            }
            else {
                // SingleResult OUTPUT
                let srAddl = asSingleResult(additional);
                if (srAddl) {
                    let srOrig = original;
                    if (srOrig.output) {
                        // find a registered object merger and apply it to merge both objs
                        // eg: RootMap - how do we do this?
                    }
                    else {
                        srOrig.output = srAddl.output; // Take the additional; it's the only one here.
                    }
                    srOrig.success = Boolean(srOrig.output != undefined);
                }
                else {
                    // BatchResult OUTPUT
                    let brOrig = asBatchResult(original);
                    if (brOrig) {
                        let brAddl = asBatchResult(additional);
                        if (brAddl === null || brAddl === void 0 ? void 0 : brAddl.successes) {
                            brOrig.successes = ((_e = brOrig.successes) !== null && _e !== void 0 ? _e : []);
                            brOrig.successes.push(...brAddl.successes);
                        }
                        if (brAddl === null || brAddl === void 0 ? void 0 : brAddl.failures) {
                            brOrig.failures = ((_f = brOrig.failures) !== null && _f !== void 0 ? _f : []);
                            brOrig.failures.push(...brAddl.failures);
                        }
                        brOrig.success = Boolean((brOrig.successes != undefined) && (brOrig.successes.length > 0) && (brOrig.failures == undefined || brOrig.failures.length == 0));
                    }
                }
            }
            return original;
        }
        catch (err) {
            throw err;
        }
    }
    /** Merges the two objects as arrays or based on a custom merger
     * or falling back to a raw object merge. */
    merge(operationId, responseRemote, localResponse) {
        if (responseRemote == undefined || _.isEmpty(responseRemote)) {
            return localResponse; // T may be a nullable type
        }
        else if (localResponse == undefined || _.isEmpty(localResponse)) {
            return responseRemote;
        }
        else {
            let composite;
            if (_.isArray(localResponse)) {
                composite = [...localResponse, ...responseRemote];
                return composite;
            }
            else if (this.entityMergers[operationId]) {
                composite = this.entityMergers[operationId].merge(responseRemote, localResponse);
            }
            else {
                composite = _.merge(responseRemote, localResponse); // <-- We should never fall back on this
            }
            return composite;
        }
    }
};
ApiClientPlusLocal = ApiClientPlusLocal_1 = tslib_1.__decorate([
    (0, inversify_1.injectable)(),
    tslib_1.__param(0, (0, inversify_1.inject)(codiac_relay_1.RelayClient)),
    tslib_1.__param(1, (0, inversify_1.inject)(codiac_common_1.INTERFACES.IBetterLogger)),
    tslib_1.__param(2, (0, inversify_1.inject)(codiac_api_1.CodiacRestClientConfig)),
    tslib_1.__param(3, (0, inversify_1.inject)(ApiClientPlusLocalConfig)),
    tslib_1.__param(4, (0, inversify_1.inject)(codiac_api_1.ApiUserCreds)),
    tslib_1.__metadata("design:paramtypes", [codiac_relay_1.RelayClient, Object, codiac_api_1.CodiacRestClientConfig,
        ApiClientPlusLocalConfig,
        codiac_api_1.ApiUserCreds])
], ApiClientPlusLocal);
exports.ApiClientPlusLocal = ApiClientPlusLocal;
//# sourceMappingURL=api-client-plus-local.js.map