"use strict";
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;
};
var __spreadArrays = (this && this.__spreadArrays) || function () {
    for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
    for (var r = Array(s), k = 0, i = 0; i < il; i++)
        for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
            r[k] = a[j];
    return r;
};
exports.__esModule = true;
exports.gqlSchema = exports.gqlTypes = void 0;
var graphql_tag_1 = require("graphql-tag");
var fs = require("fs");
var const_1 = require("../const");
var utils_1 = require("../utils");
exports.gqlTypes = function (_a) {
    var dbResources = _a.dbResources, toSchemaScalar = _a.toSchemaScalar, Resources = _a.Resources, supportsReturn = _a.supportsReturn, permissions = _a.permissions;
    var resources = Object.fromEntries(Resources);
    var schema = {
        query: [],
        mutation: []
    };
    for (var _i = 0, _b = Object.keys(dbResources); _i < _b.length; _i++) {
        var name = _b[_i];
        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 report = resources[name].report;
        var hasGeoQueryType = report.search &&
            !!Object.values(report.search).filter(function (_a) {
                var geoqueryType = _a.geoqueryType;
                return !!geoqueryType;
            }).length;
        var ResourceName = utils_1.transformNameforResolver(name);
        schema["type " + ResourceName] = [];
        schema["input keys" + ResourceName] = [];
        schema["input in" + ResourceName] = [];
        schema["input in_arr_" + ResourceName] = [];
        schema["input in_range" + ResourceName] = [];
        if (hasGeoQueryType) {
            schema["input st_" + ResourceName] = [];
        }
        schema["input input" + ResourceName] = [];
        for (var _c = 0, _d = Object.entries(dbResources[name]); _c < _d.length; _c++) {
            var _e = _d[_c], field = _e[0], record = _e[1];
            var notnull = record.notnull, type = record.type, primarykey = record.primarykey;
            var schemaScalar = toSchemaScalar(type);
            // the check for the search -> field is needed because of config `redactedFields`
            var geoType = !!(report.search[field] && report.search[field].geoqueryType);
            schema["input in" + ResourceName].push(field + ": " + schemaScalar);
            schema["input in_arr_" + ResourceName].push(field + ": [" + schemaScalar + "]");
            if (schemaScalar !== "Boolean") {
                schema["input in_range" + ResourceName].push(field + ": " + (schemaScalar === "String" ? "in_range_string" : "in_range_float"));
            }
            if (hasGeoQueryType && geoType) {
                schema["input st_" + ResourceName] = __spreadArrays(schema["input st_" + ResourceName], [
                    "radius_" + field + ": st_radius",
                    "bbox_" + field + ": st_bbox",
                    "polygon_" + field + ": String",
                ]);
                // .push(`${field}: st_radius | st_bbox | String`);
                // .push(`${field}: String`);
            }
            schema["type " + ResourceName].push(field + ": " + schemaScalar + (notnull ? "!" : ""));
            schema["input input" + ResourceName].push(field + ": " + schemaScalar);
            if (primarykey) {
                schema["input keys" + ResourceName].push(field + ": " + schemaScalar);
            }
        }
        var subResourceName = ResourceName.includes(const_1.COMPLEX_RESOLVER_SEPERATOR)
            ? ResourceName.split(const_1.COMPLEX_RESOLVER_SEPERATOR)[1]
            : undefined;
        if (subResourceName) {
            schema["input in_subquery_" + subResourceName] = [
                "payload: in" + subResourceName,
                "context: inputContext",
            ];
        }
        var spatialType = function (st) { return function (str) {
            return st || !str.startsWith("geo");
        }; };
        var searchInterfaces = [
            "equal: in" + ResourceName,
            "gt: in" + ResourceName,
            "gte: in" + ResourceName,
            "lt: in" + ResourceName,
            "lte: in" + ResourceName,
            "not: in" + ResourceName,
            "null: in" + ResourceName,
            "not_null: in" + ResourceName,
            // STRINGS ONLY
            "like: in" + ResourceName,
            // ACCEPT ARRAYS
            // accept multiple values
            "in: in_arr_" + ResourceName,
            "not_in: in_arr_" + ResourceName,
            // accept DEFINED multiple values {object keys}
            "range: in_range" + ResourceName,
            "not_range: in_range" + ResourceName,
            // accept DEFINED multiple values of DEFINED type
            "geo: st_" + ResourceName,
        ].filter(spatialType(hasGeoQueryType));
        schema["input search" + ResourceName] = searchInterfaces;
        var simpleQuery = "\n        Search" + ResourceName + "(\n            payload: search" + ResourceName + "\n            context: inputContext\n            options: serviceInputOptions\n        ): resSearch" + ResourceName + "\n    ";
        var complexQuery = "\n        Search" + ResourceName + "(\n            payload: search" + ResourceName + "\n            context: inputContext\n            options: serviceInputOptions\n            subquery: in_subquery_" + subResourceName + "\n        ): resSearch" + ResourceName + "\n    ";
        if (permit.read) {
            schema.query.push(subResourceName ? complexQuery : simpleQuery);
        }
        if (permit.create) {
            // TODO: can also skip defining the response since it wont be used
            var createResponse = !supportsReturn
                ? "NonReturningSuccessResponse"
                : "resCreate" + ResourceName;
            schema.mutation.push("\n          Create" + ResourceName + "(\n              payload: [input" + ResourceName + "!]!\n          ): " + createResponse + "\n      ");
            schema["type resCreate" + ResourceName] = "\n          sql: String\n          debug: JSONB\n          data: [" + ResourceName + "]\n      ";
        }
        if (permit.read) {
            schema["type resRead" + ResourceName] = "\n          sql: String\n          debug: JSONB\n          data: " + ResourceName + "\n      ";
        }
        if (permit.update) {
            schema["type resUpdate" + ResourceName] = "\n          sql: String\n          debug: JSONB\n          data: " + ResourceName + "\n      ";
        }
        if (permit["delete"]) {
            schema["type resDelete" + ResourceName] = "\n          sql: String\n          debug: JSONB\n          data: Float\n      ";
        }
        if (permit.read) {
            schema["type resSearch" + ResourceName] = "\n          sql: String\n          debug: JSONB\n          count: Float\n          data: [" + ResourceName + "]\n      ";
        }
        var keys = Object.values(dbResources[name]).filter(function (item) { return item.primarykey; });
        // these types if resource is keyed. else delete related defined types
        if (keys.length) {
            if (permit.read) {
                schema.query.push("\n            Read" + ResourceName + "(\n                payload: keys" + ResourceName + "!\n            ): resRead" + ResourceName + "\n        ");
            }
            if (permit.update) {
                // TODO: can also skip defining the response since it wont be used
                var updateResponse = !supportsReturn
                    ? "NonReturningSuccessResponse"
                    : "resUpdate" + ResourceName;
                schema.mutation.push("\n            Update" + ResourceName + "(\n                keys: keys" + ResourceName + "!,\n                payload: in" + ResourceName + "!\n            ): " + updateResponse + "\n        ");
            }
            if (permit["delete"]) {
                schema.mutation.push("\n            Delete" + ResourceName + "(\n                payload: keys" + ResourceName + "!\n            ): resDelete" + ResourceName + "\n        ");
            }
        }
        else {
            delete schema["input keys" + ResourceName];
            delete schema["type resRead" + ResourceName];
            delete schema["type resUpdate" + ResourceName];
            delete schema["type resDelete" + ResourceName];
        }
    }
    return schema;
};
exports.gqlSchema = function (_a) {
    var dbResources = _a.dbResources, Resources = _a.Resources, toSchemaScalar = _a.toSchemaScalar, metadata = _a.metadata, supportsReturn = _a.supportsReturn, permissions = _a.permissions;
    return __awaiter(void 0, void 0, void 0, function () {
        var _b, query, mutation, other, items, typeDefsString, mutationSchema, typeDefs;
        return __generator(this, function (_c) {
            // append the complexQueries to the dbResources -- may need to move upstream. or maybe not as its just for the graphql
            Resources.forEach(function (_a) {
                var name = _a[0], Resource = _a[1];
                if (Resource.hasSubquery) {
                    // append a record to `dbResources`
                    dbResources[name] = dbResources[utils_1.getFirstIfSeperated(name)];
                }
            });
            _b = exports.gqlTypes({
                dbResources: dbResources,
                toSchemaScalar: toSchemaScalar,
                Resources: Resources,
                supportsReturn: supportsReturn,
                permissions: permissions
            }), query = _b.query, mutation = _b.mutation, other = __rest(_b, ["query", "mutation"]);
            items = Object.entries(other).map(function (_a) {
                var name = _a[0], definition = _a[1];
                return "\n      " + name + " {\n          " + (Array.isArray(definition) ? definition.join(const_1.NEW_LINE) : definition) + "\n      }\n    ";
            });
            typeDefsString = "\n        type Query {\n            service_healthz: serviceAppHealthz\n            " + query.join(const_1.NEW_LINE) + "\n        }\n\n        type serviceAppMetadata {\n          appShortName: String\n          title: String\n          description: String\n          termsOfService: String\n          name: String\n          email: String\n          url: String\n          servers: [String]\n          appName: String\n          routerPrefix: String\n        }\n\n        type serviceAppDataBaseInfo {\n          dialect: String\n          version: String\n        }\n\n        type NonReturningSuccessResponseData {\n          success: Boolean\n        }\n\n        type serviceAppDataBaseInfo {\n          dialect: String\n          version: String\n        }\n\n        type NonReturningSuccessResponse {\n          sql: String\n          debug: JSONB\n          data: NonReturningSuccessResponseData\n        }\n\n        type serviceAppHealthz {\n            serviceVersion: String\n            timestamp: Float\n            metadata: serviceAppMetadata\n            db_info: serviceAppDataBaseInfo\n        }\n\n        input in_range_string {\n          min: String\n          max: String\n        }\n\n        input in_range_float {\n          min: Float\n          max: Float\n        }\n\n        input st_radius {\n          long: Float\n          lat: Float\n          meters: Float\n        }\n\n        input st_bbox {\n          xMin: Float\n          yMin: Float\n          xMax: Float\n          yMax: Float\n        }\n\n        input inputContext {\n          seperator: String\n          notWhere: Boolean\n          statementContext: String\n          orderBy: String\n          distinct: Boolean\n          page: Float\n          limit: Float\n        }\n\n\n        input serviceInputOptions {\n          count: Boolean\n        }\n        type serviceResponseBase {\n          count: Float\n          sql: String\n          debug: JSONB\n        }\n\n        scalar serviceCoordinates\n\n        type servicePointGeometry {\n            type: String!\n            coordinates: serviceCoordinates!\n        }\n\n        # these are for jsonb cases where you do not care to fully type\n        scalar JSONB\n\n\n        " + items.join(const_1.NEW_LINE) + "\n\n    ";
            mutationSchema = "\n        type Mutation {\n            " + mutation.join(const_1.NEW_LINE) + "\n        }\n    ";
            typeDefs = null;
            try {
                typeDefs = graphql_tag_1["default"](mutation.length
                    ? [typeDefsString, const_1.NEW_LINE, mutationSchema].join(const_1.NEW_LINE)
                    : typeDefsString);
            }
            catch (err) {
                fs.writeFileSync("schema.error.typeDefsString.txt", typeDefsString);
                fs.writeFileSync("schema.error.json", JSON.stringify({ err: err }));
                throw err;
            }
            return [2 /*return*/, {
                    typeDefsString: typeDefsString,
                    typeDefs: typeDefs
                }];
        });
    });
};
//# sourceMappingURL=schema.js.map