"use strict";
var __assign = (this && this.__assign) || function () {
    __assign = Object.assign || function(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
            s = arguments[i];
            for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
                t[p] = s[p];
        }
        return t;
    };
    return __assign.apply(this, arguments);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __generator = (this && this.__generator) || function (thisArg, body) {
    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
    function verb(n) { return function (v) { return step([n, v]); }; }
    function step(op) {
        if (f) throw new TypeError("Generator is already executing.");
        while (_) try {
            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
            if (y = 0, t) op = [op[0] & 2, t.value];
            switch (op[0]) {
                case 0: case 1: t = op; break;
                case 4: _.label++; return { value: op[1], done: false };
                case 5: _.label++; y = op[1]; op = [0]; continue;
                case 7: op = _.ops.pop(); _.trys.pop(); continue;
                default:
                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
                    if (t[2]) _.ops.pop();
                    _.trys.pop(); continue;
            }
            op = body.call(thisArg, _);
        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
    }
};
var __rest = (this && this.__rest) || function (s, e) {
    var t = {};
    for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
        t[p] = s[p];
    if (s != null && typeof Object.getOwnPropertySymbols === "function")
        for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
            if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
                t[p[i]] = s[p[i]];
        }
    return t;
};
exports.__esModule = true;
exports.grpcMethodFactory = exports.grpcMethodGenerator = void 0;
var uuid_1 = require("uuid");
var const_1 = require("../const");
var database_1 = require("../database");
var utils_1 = require("../utils");
var utils_2 = require("../utils");
var nonReturningResponse = function (val) {
    return !!val ? const_1.NON_RETURNING_SUCCESS_RESPONSE : const_1.NON_RETURNING_FAILURE_RESPONSE;
};
var apiType = "GRPC";
var DEFAULT_INPUT = function () { return ({
    payload: {},
    context: {},
    options: {},
    subquery: {}
}); };
exports.grpcMethodGenerator = function (resourcesMap) {
    return function (resource, hardDelete) {
        return function (operation) {
            return function (_a, callback) {
                var args = _a.request;
                return __awaiter(void 0, void 0, void 0, function () {
                    var reqId, report, resourceFields, singleRecord, argKeys, input, payload, context, options, keys, subquery, query, subPayload, _serviceResponse, serviceResponse, _records, data, response, _b, seperator, notWhere, statementContext, distinct, fields, _searchCountResult, searchCountResult, sqlSearchCount, count, transformJson_1, jsonToStructs, final, err_1, message;
                    return __generator(this, function (_c) {
                        switch (_c.label) {
                            case 0:
                                reqId = uuid_1.v4();
                                report = resource.report[operation];
                                resourceFields = Object.keys(report);
                                singleRecord = ["read", "update"].includes(operation);
                                argKeys = ["read", "delete"].includes(operation);
                                input = argKeys
                                    ? __assign(__assign({}, DEFAULT_INPUT()), { payload: args }) : __assign(__assign({}, DEFAULT_INPUT()), args);
                                payload = input.payload, context = input.context, options = input.options, keys = input.keys, subquery = input.subquery;
                                context.fields = context.fields
                                    ? utils_1.contextTransformer("fields", context.fields)
                                    : resourceFields;
                                if (!resource.supportsReturn && ["create", "update"].includes(operation)) {
                                    context.fields = [];
                                }
                                if (context.orderBy) {
                                    context.orderBy = utils_1.contextTransformer("orderBy", context.orderBy);
                                }
                                query = {
                                    payload: operation === "update"
                                        ? __assign(__assign({}, payload), keys) : operation === "search"
                                        ? utils_1.gqlParsePayload(payload)
                                        : payload,
                                    context: context,
                                    requestId: reqId,
                                    apiType: apiType,
                                    hardDelete: hardDelete
                                };
                                subPayload = {
                                    payload: utils_1.gqlParsePayload(subquery),
                                    requestId: reqId,
                                    apiType: apiType
                                };
                                _serviceResponse = resource.hasSubquery
                                    ? utils_1.callComplexResource(resourcesMap, resource.name, operation, query, subPayload)
                                    : resource[operation](query);
                                return [4 /*yield*/, _serviceResponse];
                            case 1:
                                serviceResponse = _c.sent();
                                if (!serviceResponse.result) return [3 /*break*/, 9];
                                _c.label = 2;
                            case 2:
                                _c.trys.push([2, 7, , 8]);
                                return [4 /*yield*/, serviceResponse.result.sql];
                            case 3:
                                _records = _c.sent();
                                data = !resource.supportsReturn && ["create", "update"].includes(operation)
                                    ? operation === "update"
                                        ? [nonReturningResponse(_records)]
                                        : nonReturningResponse(_records)
                                    : resource.transformRecords(_records);
                                // TODO: add error logging and `dbCallSuccessful` type flag (like in routers) to prevent count if db call failed
                                // update & delete will one day support search query for bulk mutation (already supported in the class I think)
                                delete serviceResponse.result.sql;
                                response = {
                                    data: singleRecord ? (data.length ? data[0] : null) : data
                                };
                                if (!(operation === "search" && options.count)) return [3 /*break*/, 6];
                                _b = query.context, seperator = _b.seperator, notWhere = _b.notWhere, statementContext = _b.statementContext, distinct = _b.distinct, fields = _b.fields;
                                query.context = { seperator: seperator, notWhere: notWhere, statementContext: statementContext, distinct: distinct, fields: fields };
                                _searchCountResult = resource.hasSubquery
                                    ? utils_1.callComplexResource(resourcesMap, resource.name, operation, query, subPayload)
                                    : resource[operation](query);
                                return [4 /*yield*/, _searchCountResult];
                            case 4:
                                searchCountResult = (_c.sent()).result;
                                sqlSearchCount = database_1.genCountQuery(resource.db, searchCountResult.sql);
                                return [4 /*yield*/, sqlSearchCount];
                            case 5:
                                count = (_c.sent())[0].count;
                                response.count = count;
                                _c.label = 6;
                            case 6:
                                transformJson_1 = function (record) {
                                    var jsonFields = Object.keys(record).filter(function (key) {
                                        return !!(report[key] &&
                                            (report[key].geoqueryType || report[key].jsonType));
                                    });
                                    for (var _i = 0, jsonFields_1 = jsonFields; _i < jsonFields_1.length; _i++) {
                                        var jsonField = jsonFields_1[_i];
                                        record[jsonField] = utils_2.encodeStruct(record[jsonField]);
                                    }
                                    return __assign({}, record);
                                };
                                jsonToStructs = function (_a) {
                                    var _data = _a.data, other = __rest(_a, ["data"]);
                                    var output = __assign(__assign({}, _data), other);
                                    if (_data) {
                                        output = __assign(__assign({}, other), { data: Array.isArray(_data)
                                                ? _data.map(transformJson_1)
                                                : transformJson_1(_data) });
                                    }
                                    return output;
                                };
                                final = void 0;
                                if (!resource.supportsReturn &&
                                    ["create", "update"].includes(operation)) {
                                    final = response.data;
                                }
                                else if (operation === "delete") {
                                    final = { number: response.data };
                                }
                                else if (operation === "read") {
                                    if (!response.data) {
                                        throw new Error(const_1.UNIQUE_RECORD_NOT_FOUND_WITH_KEYS);
                                    }
                                    final = transformJson_1(response.data);
                                }
                                else if (operation === "update") {
                                    final = transformJson_1(response.data);
                                }
                                else if (operation === "create") {
                                    // need to check create one-and-many
                                    final = Array.isArray(response.data)
                                        ? { data: response.data.map(transformJson_1) }
                                        : jsonToStructs(response).data[0];
                                }
                                else if (operation === "search") {
                                    // need to check create one-and-many
                                    final = __assign(__assign({}, response), { data: response.data.map(transformJson_1) });
                                }
                                callback(null, final);
                                return [3 /*break*/, 8];
                            case 7:
                                err_1 = _c.sent();
                                message = err_1.message === const_1.UNIQUE_RECORD_NOT_FOUND_WITH_KEYS
                                    ? "BAD_REQUEST"
                                    : "INTERNAL_SERVER_ERROR";
                                resource.logger.error({
                                    message: message,
                                    reqId: reqId,
                                    err: err_1,
                                    detail: err_1.message
                                }, "grpc method call error");
                                callback(new Error(JSON.stringify({
                                    message: message,
                                    reqId: reqId,
                                    err: err_1,
                                    detail: err_1.message
                                })));
                                return [3 /*break*/, 8];
                            case 8: return [3 /*break*/, 10];
                            case 9:
                                resource.logger.error({
                                    message: "BAD_REQUEST",
                                    detail: serviceResponse,
                                    reqId: reqId
                                }, "grpc method call error: bad request");
                                callback(new Error(JSON.stringify({
                                    message: "BAD_REQUEST",
                                    detail: serviceResponse,
                                    reqId: reqId
                                })));
                                _c.label = 10;
                            case 10: return [2 /*return*/];
                        }
                    });
                });
            };
        };
    };
};
exports.grpcMethodFactory = function (_a) {
    // export const grpcMethodGenerator = (resourcesMap: IClassResourceMap, logger) => (
    //     resource,
    //     hardDelete: boolean,
    //     supportsReturn: boolean
    //   ) => (operation: string) => async (args, callback) => {
    var Resources = _a.Resources, dbResources = _a.dbResources, hardDelete = _a.hardDelete, permissions = _a.permissions;
    var grpcMethods = Resources.reduce(function (methods, _a) {
        var _b;
        var name = _a[0], resource = _a[1];
        var allow = utils_1.permitted(permissions);
        var permit = {
            create: allow(name, "create"),
            read: allow(name, "read"),
            update: allow(name, "update"),
            "delete": allow(name, "delete"),
            any: true
        };
        permit.any = permit.create || permit.read || permit.update || permit["delete"];
        var ResourceName = utils_1.transformNameforResolver(name);
        var resourcesMap = utils_1.genResourcesMap(Resources);
        var resolver = exports.grpcMethodGenerator(resourcesMap)(resource, hardDelete);
        var output = __assign(__assign({}, methods), (_b = {}, _b["Read" + ResourceName] = resolver("read"), _b["Search" + ResourceName] = resolver("search"), _b["Create" + ResourceName] = resolver("create"), _b["Update" + ResourceName] = resolver("update"), _b["Delete" + ResourceName] = resolver("delete"), _b));
        var keys = Object.values(dbResources[utils_1.getFirstIfSeperated(name)]).filter(function (item) { return item.primarykey; });
        // if resource lacks keys -- delete resolvers for unique records
        if (!keys.length) {
            delete output["Read" + ResourceName];
            delete output["Update" + ResourceName];
            delete output["Delete" + ResourceName];
        }
        else {
            if (!permit.read) {
                delete output["Read" + ResourceName];
            }
            if (!permit.update) {
                delete output["Update" + ResourceName];
            }
            if (!permit["delete"]) {
                delete output["Delete" + ResourceName];
            }
        }
        if (!permit.create) {
            delete output["Create" + ResourceName];
        }
        if (!permit.read) {
            delete output["Search" + ResourceName];
        }
        return output;
    }, {});
    return grpcMethods;
};
//# sourceMappingURL=methods.js.map