"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 { getRowParams } = require("../../../db/utils");
const { outputProcessing, processAutoColumn, processFormulas, } = require("../../../utilities/rowProcessor");
const { FieldTypes, FormulaTypes } = require("../../../constants");
const { isEqual } = require("lodash");
const { cloneDeep } = require("lodash/fp");
const { getAppDB } = require("@devlego/backend-core/context");
/**
 * This function runs through a list of enriched rows, looks at the rows which
 * are related and then checks if they need the state of their formulas
 * updated.
 * NOTE: this will only for affect static formulas.
 */
exports.updateRelatedFormula = (table, enrichedRows) => __awaiter(void 0, void 0, void 0, function* () {
    const db = getAppDB();
    // no formula to update, we're done
    if (!table.relatedFormula) {
        return;
    }
    let promises = [];
    for (let enrichedRow of Array.isArray(enrichedRows)
        ? enrichedRows
        : [enrichedRows]) {
        // the related rows by tableId
        let relatedRows = {};
        for (let [key, field] of Object.entries(enrichedRow)) {
            const columnDefinition = table.schema[key];
            if (columnDefinition && columnDefinition.type === FieldTypes.LINK) {
                const relatedTableId = columnDefinition.tableId;
                if (!relatedRows[relatedTableId]) {
                    relatedRows[relatedTableId] = [];
                }
                relatedRows[relatedTableId] = relatedRows[relatedTableId].concat(field);
            }
        }
        for (let tableId of table.relatedFormula) {
            let relatedTable;
            try {
                // no rows to update, skip
                if (!relatedRows[tableId] || relatedRows[tableId].length === 0) {
                    continue;
                }
                relatedTable = yield db.get(tableId);
            }
            catch (err) {
                // no error scenario, table doesn't seem to exist anymore, ignore
            }
            for (let column of Object.values(relatedTable.schema)) {
                // needs updated in related rows
                if (column.type === FieldTypes.FORMULA &&
                    column.formulaType === FormulaTypes.STATIC) {
                    // re-enrich rows for all the related, don't update the related formula for them
                    promises = promises.concat(relatedRows[tableId].map(related => exports.finaliseRow(relatedTable, related, {
                        updateFormula: false,
                    })));
                }
            }
        }
    }
    yield Promise.all(promises);
});
exports.updateAllFormulasInTable = (table) => __awaiter(void 0, void 0, void 0, function* () {
    const db = getAppDB();
    // start by getting the raw rows (which will be written back to DB after update)
    let rows = (yield db.allDocs(getRowParams(table._id, null, {
        include_docs: true,
    }))).rows.map(row => row.doc);
    // now enrich the rows, note the clone so that we have the base state of the
    // rows so that we don't write any of the enriched information back
    let enrichedRows = yield outputProcessing(table, cloneDeep(rows), {
        squash: false,
    });
    const updatedRows = [];
    for (let row of rows) {
        // find the enriched row, if found process the formulas
        const enrichedRow = enrichedRows.find(enriched => enriched._id === row._id);
        if (enrichedRow) {
            const processed = processFormulas(table, cloneDeep(row), {
                dynamic: false,
                contextRows: enrichedRow,
            });
            // values have changed, need to add to bulk docs to update
            if (!isEqual(processed, row)) {
                updatedRows.push(processed);
            }
        }
    }
    yield db.bulkDocs(updatedRows);
});
/**
 * This function runs at the end of the save/patch functions of the row controller, all this
 * really does is enrich the row, handle any static formula processing, then return the enriched
 * row. The reason we need to return the enriched row is that the automation row created trigger
 * expects the row to be totally enriched/contain all relationships.
 */
exports.finaliseRow = (table, row, { oldTable, updateFormula } = { updateFormula: true }) => __awaiter(void 0, void 0, void 0, function* () {
    const db = getAppDB();
    row.type = "row";
    // process the row before return, to include relationships
    let enrichedRow = yield outputProcessing(table, cloneDeep(row), {
        squash: false,
    });
    // use enriched row to generate formulas for saving, specifically only use as context
    row = processFormulas(table, row, {
        dynamic: false,
        contextRows: enrichedRow,
    });
    // don't worry about rev, tables handle rev/lastID updates
    // if another row has been written since processing this will
    // handle the auto ID clash
    if (oldTable && !isEqual(oldTable, table)) {
        try {
            yield db.put(table);
        }
        catch (err) {
            if (err.status === 409) {
                const updatedTable = yield db.get(table._id);
                let response = processAutoColumn(null, updatedTable, row, {
                    reprocessing: true,
                });
                yield db.put(response.table);
                row = response.row;
            }
            else {
                throw err;
            }
        }
    }
    const response = yield db.put(row);
    // for response, calculate the formulas for the enriched row
    enrichedRow._rev = response.rev;
    enrichedRow = yield processFormulas(table, enrichedRow, { dynamic: false });
    // this updates the related formulas in other rows based on the relations to this row
    if (updateFormula) {
        yield exports.updateRelatedFormula(table, enrichedRow);
    }
    return { row: enrichedRow, table };
});
