"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
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 __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.upsertSingleton = exports.readSingleton = exports.deleteItem = exports.updateItem = exports.readItem = exports.readItems = exports.createItem = void 0;
const database_1 = __importStar(require("../database"));
const run_ast_1 = __importDefault(require("../database/run-ast"));
const get_ast_from_query_1 = __importDefault(require("../utils/get-ast-from-query"));
const PayloadService = __importStar(require("./payload"));
const PermissionsService = __importStar(require("./permissions"));
const ActivityService = __importStar(require("./activity"));
const lodash_1 = require("lodash");
function saveActivityAndRevision(action, collection, item, payload, accountability, knex = database_1.default) {
    return __awaiter(this, void 0, void 0, function* () {
        const activityID = yield knex('directus_activity')
            .insert({
            action,
            collection,
            item,
            ip: accountability.ip,
            user_agent: accountability.userAgent,
            action_by: accountability.user,
        })
            .returning('id');
        if (action !== ActivityService.Action.DELETE) {
            yield knex('directus_revisions').insert({
                activity: activityID[0],
                collection,
                item,
                delta: payload,
                /** @todo make this configurable */
                data: yield readItem(collection, item, { fields: ['*'] }),
                parent: accountability.parent,
            });
        }
    });
}
function createItem(collection, data, accountability) {
    return __awaiter(this, void 0, void 0, function* () {
        const isBatch = Array.isArray(data);
        const primaryKeys = yield database_1.default.transaction((transaction) => __awaiter(this, void 0, void 0, function* () {
            let payloads = isBatch ? data : [data];
            const primaryKeys = yield Promise.all(payloads.map((payload) => __awaiter(this, void 0, void 0, function* () {
                if (accountability && accountability.admin === false) {
                    payload = yield PermissionsService.processValues('create', collection, accountability === null || accountability === void 0 ? void 0 : accountability.role, payload);
                }
                payload = yield PayloadService.processValues('create', collection, payload);
                payload = yield PayloadService.processM2O(collection, payload);
                const primaryKeyField = yield database_1.schemaInspector.primary(collection);
                // Only insert the values that actually save to an existing column. This ensures we ignore aliases etc
                const columns = yield database_1.schemaInspector.columns(collection);
                const payloadWithoutAlias = lodash_1.pick(payload, columns.map(({ column }) => column));
                const primaryKeys = yield transaction(collection)
                    .insert(payloadWithoutAlias)
                    .returning(primaryKeyField);
                // This allows the o2m values to be populated correctly
                payload[primaryKeyField] = primaryKeys[0];
                yield PayloadService.processO2M(collection, payload);
                if (accountability) {
                    yield saveActivityAndRevision(ActivityService.Action.CREATE, collection, primaryKeys[0], payloadWithoutAlias, accountability, transaction);
                }
                return primaryKeys[0];
            })));
            return primaryKeys;
        }));
        if (Array.isArray(data)) {
            return primaryKeys;
        }
        else {
            return primaryKeys[0];
        }
    });
}
exports.createItem = createItem;
exports.readItems = (collection, query, accountability) => __awaiter(void 0, void 0, void 0, function* () {
    let ast = yield get_ast_from_query_1.default(collection, query, accountability);
    if (accountability && accountability.admin === false) {
        ast = yield PermissionsService.processAST(ast, accountability.role);
    }
    const records = yield run_ast_1.default(ast);
    return (yield PayloadService.processValues('read', collection, records));
});
function readItem(collection, primaryKey, query, accountability, operation) {
    return __awaiter(this, void 0, void 0, function* () {
        // We allow overriding the operation, so we can use the item read logic to validate permissions
        // for update and delete as well
        operation = operation || 'read';
        const primaryKeyField = yield database_1.schemaInspector.primary(collection);
        const primaryKeys = (Array.isArray(primaryKey) ? primaryKey : [primaryKey]);
        const isBatch = Array.isArray(primaryKey);
        if (isBatch) {
            query = Object.assign(Object.assign({}, query), { filter: Object.assign(Object.assign({}, (query.filter || {})), { [primaryKeyField]: {
                        _in: primaryKeys,
                    } }) });
        }
        else {
            query = Object.assign(Object.assign({}, query), { filter: Object.assign(Object.assign({}, (query.filter || {})), { [primaryKeyField]: {
                        _eq: primaryKey,
                    } }) });
        }
        let ast = yield get_ast_from_query_1.default(collection, query, accountability, operation);
        if (accountability && accountability.admin === false) {
            ast = yield PermissionsService.processAST(ast, accountability.role, operation);
        }
        const records = yield run_ast_1.default(ast);
        const processedRecords = yield PayloadService.processValues('read', collection, records);
        return isBatch ? processedRecords : processedRecords[0];
    });
}
exports.readItem = readItem;
function updateItem(collection, primaryKey, data, accountability) {
    return __awaiter(this, void 0, void 0, function* () {
        const primaryKeys = (Array.isArray(primaryKey) ? primaryKey : [primaryKey]);
        yield database_1.default.transaction((transaction) => __awaiter(this, void 0, void 0, function* () {
            let payload = lodash_1.clone(data);
            return yield Promise.all(primaryKeys.map((key) => __awaiter(this, void 0, void 0, function* () {
                if (accountability && accountability.admin === false) {
                    yield PermissionsService.checkAccess('update', collection, key, accountability.role);
                    payload = yield PermissionsService.processValues('validate', collection, accountability.role, data);
                }
                payload = yield PayloadService.processValues('update', collection, payload);
                payload = yield PayloadService.processM2O(collection, payload);
                const primaryKeyField = yield database_1.schemaInspector.primary(collection);
                // Only insert the values that actually save to an existing column. This ensures we ignore aliases etc
                const columns = yield database_1.schemaInspector.columns(collection);
                const payloadWithoutAlias = lodash_1.pick(payload, columns.map(({ column }) => column));
                yield transaction(collection)
                    .update(payloadWithoutAlias)
                    .where({ [primaryKeyField]: key });
                if (accountability) {
                    yield saveActivityAndRevision(ActivityService.Action.UPDATE, collection, String(key), payloadWithoutAlias, accountability, transaction);
                }
                return primaryKey;
            })));
        }));
        return primaryKey;
    });
}
exports.updateItem = updateItem;
exports.deleteItem = (collection, pk, accountability) => __awaiter(void 0, void 0, void 0, function* () {
    const primaryKeyField = yield database_1.schemaInspector.primary(collection);
    const primaryKeys = Array.isArray(pk) ? pk : [pk];
    yield database_1.default.transaction((transaction) => __awaiter(void 0, void 0, void 0, function* () {
        yield Promise.all(primaryKeys.map((key) => __awaiter(void 0, void 0, void 0, function* () {
            if (accountability && accountability.admin === false) {
                yield PermissionsService.checkAccess('delete', collection, key, accountability.role);
            }
            yield transaction(collection)
                .where({ [primaryKeyField]: key })
                .delete();
            if (accountability) {
                yield saveActivityAndRevision(ActivityService.Action.DELETE, collection, String(key), {}, accountability, transaction);
            }
        })));
    }));
    return pk;
});
exports.readSingleton = (collection, query, accountability) => __awaiter(void 0, void 0, void 0, function* () {
    query.limit = 1;
    const records = yield exports.readItems(collection, query, accountability);
    const record = records[0];
    if (!record) {
        const columns = yield database_1.schemaInspector.columnInfo(collection);
        const defaults = {};
        for (const column of columns) {
            defaults[column.name] = column.default_value;
        }
        return defaults;
    }
    return record;
});
exports.upsertSingleton = (collection, data, accountability) => __awaiter(void 0, void 0, void 0, function* () {
    const primaryKeyField = yield database_1.schemaInspector.primary(collection);
    const record = yield database_1.default.select(primaryKeyField).from(collection).limit(1).first();
    if (record) {
        return yield updateItem(collection, record.id, data, accountability);
    }
    return yield createItem(collection, data, accountability);
});
