"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());
    });
};
const { FieldTypes, FormulaTypes } = require("../../../constants");
const { getAllInternalTables, clearColumns } = require("./utils");
const { doesContainStrings } = require("@devlego/string-templates");
const { cloneDeep } = require("lodash/fp");
const { isEqual, uniq } = require("lodash");
const { updateAllFormulasInTable } = require("../row/staticFormula");
const { getAppDB } = require("@devlego/backend-core/context");
function isStaticFormula(column) {
    return (column.type === FieldTypes.FORMULA &&
        column.formulaType === FormulaTypes.STATIC);
}
/**
 * This retrieves the formula columns from a table schema that use a specified column name
 * in the formula.
 */
function getFormulaThatUseColumn(table, columnNames) {
    var _a;
    let formula = [];
    columnNames = Array.isArray(columnNames) ? columnNames : [columnNames];
    for (let column of Object.values(table.schema)) {
        // not a static formula, or doesn't contain a relationship
        if (!isStaticFormula(column)) {
            continue;
        }
        if (!doesContainStrings((_a = column === null || column === void 0 ? void 0 : column.formula) !== null && _a !== void 0 ? _a : "", columnNames)) {
            continue;
        }
        formula.push(column.name);
    }
    return formula;
}
/**
 * This functions checks for when a related table, column or related column is deleted, if any
 * tables need to have the formula column removed.
 */
function checkIfFormulaNeedsCleared(table, { oldTable, deletion }) {
    return __awaiter(this, void 0, void 0, function* () {
        // start by retrieving all tables, remove the current table from the list
        const tables = (yield getAllInternalTables()).filter(tbl => tbl._id !== table._id);
        const schemaToUse = oldTable ? oldTable.schema : table.schema;
        let removedColumns = Object.values(schemaToUse).filter(column => deletion || !table.schema[column.name]);
        // remove any formula columns that used related columns
        for (let removed of removedColumns) {
            let tableToUse = table;
            // if relationship, get the related table
            if (removed.type === FieldTypes.LINK) {
                tableToUse = tables.find(table => table._id === removed.tableId);
            }
            const columnsToDelete = getFormulaThatUseColumn(tableToUse, removed.name);
            if (columnsToDelete.length > 0) {
                yield clearColumns(table, columnsToDelete);
            }
            // need a special case, where a column has been removed from this table, but was used
            // in a different, related tables formula
            if (!table.relatedFormula) {
                continue;
            }
            for (let relatedTableId of table.relatedFormula) {
                const relatedColumns = Object.values(table.schema).filter(column => column.tableId === relatedTableId);
                const relatedTable = tables.find(table => table._id === relatedTableId);
                // look to see if the column was used in a relationship formula,
                // relationships won't be used for this
                if (relatedTable && relatedColumns && removed.type !== FieldTypes.LINK) {
                    let relatedFormulaToRemove = [];
                    for (let column of relatedColumns) {
                        relatedFormulaToRemove = relatedFormulaToRemove.concat(getFormulaThatUseColumn(relatedTable, [
                            column.fieldName,
                            removed.name,
                        ]));
                    }
                    if (relatedFormulaToRemove.length > 0) {
                        yield clearColumns(relatedTable, uniq(relatedFormulaToRemove));
                    }
                }
            }
        }
    });
}
/**
 * This function adds a note to related tables that they are
 * used in a static formula - so that the link controller
 * can manage hydrating related rows formula fields. This is
 * specifically only for static formula.
 */
function updateRelatedFormulaLinksOnTables(table, { deletion } = { deletion: false }) {
    return __awaiter(this, void 0, void 0, function* () {
        const db = getAppDB();
        // start by retrieving all tables, remove the current table from the list
        const tables = (yield getAllInternalTables()).filter(tbl => tbl._id !== table._id);
        // clone the tables, so we can compare at end
        const initialTables = cloneDeep(tables);
        // first find the related column names
        const relatedColumns = Object.values(table.schema).filter(col => col.type === FieldTypes.LINK);
        // we start by removing the formula field from all tables
        for (let otherTable of tables) {
            if (!otherTable.relatedFormula) {
                continue;
            }
            const index = otherTable.relatedFormula.indexOf(table._id);
            if (index !== -1) {
                otherTable.relatedFormula.splice(index, 1);
            }
        }
        // if deleting, just remove the table IDs, don't try add
        if (!deletion) {
            for (let relatedCol of relatedColumns) {
                let columns = getFormulaThatUseColumn(table, relatedCol.name);
                if (!columns || columns.length === 0) {
                    continue;
                }
                const relatedTable = tables.find(related => related._id === relatedCol.tableId);
                // check if the table is already in the list of related formula, if it isn't, then add it
                if (relatedTable &&
                    (!relatedTable.relatedFormula ||
                        !relatedTable.relatedFormula.includes(table._id))) {
                    relatedTable.relatedFormula = relatedTable.relatedFormula
                        ? [...relatedTable.relatedFormula, table._id]
                        : [table._id];
                }
            }
        }
        // now we just need to compare all the tables and see if any need saved
        for (let initial of initialTables) {
            const found = tables.find(tbl => initial._id === tbl._id);
            if (found && !isEqual(initial, found)) {
                yield db.put(found);
            }
        }
    });
}
function checkIfFormulaUpdated(table, { oldTable }) {
    return __awaiter(this, void 0, void 0, function* () {
        // look to see if any formula values have changed
        const shouldUpdate = Object.values(table.schema).find(column => isStaticFormula(column) &&
            (!oldTable ||
                !oldTable.schema[column.name] ||
                !isEqual(oldTable.schema[column.name], column)));
        // if a static formula column has updated, then need to run the update
        if (shouldUpdate != null) {
            yield updateAllFormulasInTable(table);
        }
    });
}
exports.runStaticFormulaChecks = (table, { oldTable, deletion }) => __awaiter(void 0, void 0, void 0, function* () {
    yield updateRelatedFormulaLinksOnTables(table, { deletion });
    yield checkIfFormulaNeedsCleared(table, { oldTable, deletion });
    if (!deletion) {
        yield checkIfFormulaUpdated(table, { oldTable });
    }
});
