"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 __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;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.bulkImport = exports.destroy = exports.save = void 0;
const linkedRows_1 = require("../../../db/linkedRows");
const utils_1 = require("../../../db/utils");
const constants_1 = require("../../../constants");
const utils_2 = require("./utils");
const { getAppDB } = require("@devlego/backend-core/context");
const environment_1 = require("../../../environment");
const rowProcessor_1 = require("../../../utilities/rowProcessor");
const bulkFormula_1 = require("./bulkFormula");
const pro_1 = require("@devlego/pro");
function checkAutoColumns(table, oldTable) {
    if (!table.schema) {
        return table;
    }
    for (let [key, schema] of Object.entries(table.schema)) {
        if (!schema.autocolumn || schema.subtype) {
            continue;
        }
        const oldSchema = oldTable && oldTable.schema[key];
        if (oldSchema && oldSchema.subtype) {
            table.schema[key].subtype = oldSchema.subtype;
        }
        else {
            table.schema[key] = (0, rowProcessor_1.fixAutoColumnSubType)(schema);
        }
    }
    return table;
}
function save(ctx) {
    return __awaiter(this, void 0, void 0, function* () {
        const db = getAppDB();
        const _a = ctx.request.body, { dataImport } = _a, rest = __rest(_a, ["dataImport"]);
        let tableToSave = Object.assign({ type: "table", _id: (0, utils_1.generateTableID)(), views: {} }, rest);
        // if the table obj had an _id then it will have been retrieved
        let oldTable;
        if (ctx.request.body && ctx.request.body._id) {
            oldTable = yield db.get(ctx.request.body._id);
        }
        // check all types are correct
        if ((0, utils_2.hasTypeChanged)(tableToSave, oldTable)) {
            ctx.throw(400, "A column type has changed.");
        }
        // check that subtypes have been maintained
        tableToSave = checkAutoColumns(tableToSave, oldTable);
        // saving a table is a complex operation, involving many different steps, this
        // has been broken out into a utility to make it more obvious/easier to manipulate
        const tableSaveFunctions = new utils_2.TableSaveFunctions({
            user: ctx.user,
            oldTable,
            dataImport,
        });
        tableToSave = yield tableSaveFunctions.before(tableToSave);
        // make sure that types don't change of a column, have to remove
        // the column if you want to change the type
        if (oldTable && oldTable.schema) {
            for (let propKey of Object.keys(tableToSave.schema)) {
                let oldColumn = oldTable.schema[propKey];
                if (oldColumn && oldColumn.type === "internal") {
                    oldColumn.type = "auto";
                }
            }
        }
        // Don't rename if the name is the same
        let { _rename } = tableToSave;
        /* istanbul ignore next */
        if (_rename && _rename.old === _rename.updated) {
            _rename = null;
            delete tableToSave._rename;
        }
        // rename row fields when table column is renamed
        /* istanbul ignore next */
        if (_rename && tableToSave.schema[_rename.updated].type === constants_1.FieldTypes.LINK) {
            ctx.throw(400, "Cannot rename a linked column.");
        }
        tableToSave = yield tableSaveFunctions.mid(tableToSave);
        // update schema of non-statistics views when new columns are added
        for (let view in tableToSave.views) {
            const tableView = tableToSave.views[view];
            if (!tableView)
                continue;
            if (tableView.schema.group || tableView.schema.field)
                continue;
            tableView.schema = tableToSave.schema;
        }
        // update linked rows
        try {
            const linkResp = yield (0, linkedRows_1.updateLinks)({
                eventType: oldTable ? linkedRows_1.EventType.TABLE_UPDATED : linkedRows_1.EventType.TABLE_SAVE,
                table: tableToSave,
                oldTable: oldTable,
            });
            if (linkResp != null && linkResp._rev) {
                tableToSave._rev = linkResp._rev;
            }
        }
        catch (err) {
            ctx.throw(400, err);
        }
        // don't perform any updates until relationships have been
        // checked by the updateLinks function
        const updatedRows = tableSaveFunctions.getUpdatedRows();
        if (updatedRows && updatedRows.length !== 0) {
            yield db.bulkDocs(updatedRows);
        }
        const result = yield db.put(tableToSave);
        tableToSave._rev = result.rev;
        tableToSave = yield tableSaveFunctions.after(tableToSave);
        // has to run after, make sure it has _id
        yield (0, bulkFormula_1.runStaticFormulaChecks)(tableToSave, { oldTable, deletion: null });
        return tableToSave;
    });
}
exports.save = save;
function destroy(ctx) {
    return __awaiter(this, void 0, void 0, function* () {
        const db = getAppDB();
        const tableToDelete = yield db.get(ctx.params.tableId);
        // Delete all rows for that table
        const rows = yield db.allDocs((0, utils_1.getRowParams)(ctx.params.tableId, null, {
            include_docs: true,
        }));
        yield db.bulkDocs(rows.rows.map((row) => (Object.assign(Object.assign({}, row.doc), { _deleted: true }))));
        yield pro_1.quotas.removeRows(rows.rows.length);
        // update linked rows
        yield (0, linkedRows_1.updateLinks)({
            eventType: linkedRows_1.EventType.TABLE_DELETE,
            table: tableToDelete,
        });
        // don't remove the table itself until very end
        yield db.remove(tableToDelete);
        // remove table search index
        if (!(0, environment_1.isTest)()) {
            const currentIndexes = yield db.getIndexes();
            const existingIndex = currentIndexes.indexes.find((existing) => existing.name === `search:${ctx.params.tableId}`);
            if (existingIndex) {
                yield db.deleteIndex(existingIndex);
            }
        }
        // has to run after, make sure it has _id
        yield (0, bulkFormula_1.runStaticFormulaChecks)(tableToDelete, {
            oldTable: null,
            deletion: true,
        });
        yield (0, rowProcessor_1.cleanupAttachments)(tableToDelete, { rows });
        return tableToDelete;
    });
}
exports.destroy = destroy;
function bulkImport(ctx) {
    return __awaiter(this, void 0, void 0, function* () {
        const table = yield (0, utils_2.getTable)(ctx.params.tableId);
        const { dataImport } = ctx.request.body;
        yield (0, utils_2.handleDataImport)(ctx.user, table, dataImport);
        return table;
    });
}
exports.bulkImport = bulkImport;
