import { TableColumn } from "../Shared/TableColumn";
import { ValueRuleRow } from "./ValueRuleRow";
import { ValueRuleColumn } from "./ValueRuleColumn";
import { ValueRuleCell } from "./ValueRuleCell";
import { XDataType } from "../../../Common/Data/XDataType";
import { DataUtilities } from "../../../Common/DataUtilities";
import { BooleanDataDefinition } from "../../../Common/Data/Definitions/BooleanDataDefinition";
import { BaseRuleTableDefinition } from "../Common/BaseRuleTableDefinition";
import { ResultCell } from "../Common/ResultCell";
import { ValueTableRowTestResult, ValueTableRuleColumnTestResult, ValueTableTestResult } from "./ValueTableTestResult";
import { EventLog, EventLogType } from "../../../Common/Event/EventLog";
// todo: render all result cols
// todo: make the + work
// todo: sync the outputs.  if they are supplied, we need to not have the + and the columns setup.  if not supplied, we need to populate the output
//   data def outputs on executable
// todo: is default editable in ui?
// todo: remove col (cannot remove the last one)
// todo: missing method remove result col (cannot remove the last one)
// todo: implement import
// todo: implement export
// todo: validation
// todo: config storage
export class ValueTableDefinition extends BaseRuleTableDefinition {
    constructor() {
        super();
        this._id = DataUtilities.generateUniqueId();
        // initial cols are added in construtor
        this._columns = [];
        this._rows = [];
        this.addColumn();
        if (this.canChangeOutputs) {
            this.addResultColumn();
        }
        this.addRow();
    }
    addResultColumn(name) {
        if (!this.canChangeOutputs) {
            throw new Error("cannot add result columns because the outputs are specified by API");
        }
        let rescol = this.addResultColumnInternal(name);
        // update the output;
        this.getExecutionResult().fields.push(DataUtilities.createDataByType(rescol.dataType, rescol.name));
        this.syncDefaultResults();
        this.syncAllRows();
        return rescol;
    }
    import(data) {
        throw new Error("not implemented");
    }
    export(data) {
        throw new Error("not implemented");
    }
    run(data, testContext) {
        let subContext;
        if (testContext) {
            subContext = new ValueTableTestResult(this._id);
            subContext.definition = this;
            testContext.addTestResultItem(subContext);
            subContext.addLogItem(new EventLog(`ValueTable: start evaluating`));
        }
        for (let rowidx in this.rows) {
            const row = this.rows[rowidx];
            let rowContext;
            if (subContext) {
                subContext.addLogItem(new EventLog(`ValueTableDefinition: start evaluating ${parseInt(rowidx, 10) + 1}th row`));
                rowContext = new ValueTableRowTestResult(row._id);
                rowContext.addLogItem(new EventLog(`ValueTable: start evaluating ${parseInt(rowidx, 10) + 1}th row`));
                subContext.addTestResultItem(rowContext);
            }
            let evaluated = true;
            for (let ruleCol of this.columns) {
                let cell = row.cellData[ruleCol._id];
                if (!cell)
                    throw new Error("cell not found " + ruleCol._id);
                let ruleColumnContext;
                if (rowContext) {
                    rowContext.addLogItem(new EventLog(`ValueTableDefinition: start evaluating ${ruleCol.columnName} rule column`));
                    ruleColumnContext = new ValueTableRuleColumnTestResult(`${row._id}-${cell._columnId}`);
                    ruleColumnContext.addLogItem(new EventLog(`ValueTable: start evaluating "${ruleCol.columnName}" cell`));
                    rowContext.addTestResultItem(ruleColumnContext);
                }
                try {
                    if (cell.values === ValueRuleCell.IGNORE_CELL_VALUE) {
                        if (ruleColumnContext) {
                            ruleColumnContext.result = "true";
                        }
                        continue;
                    }
                    else if (cell.values === ValueRuleCell.MISSING_CELL_VALUE) {
                        throw new Error("Cell Value is missing");
                    }
                    else if (Array.isArray(cell.values)) {
                        // here we will execute the rule for each value in the cell.  if ANY evaluate true, then the cell is true.  if all evaluate false, then the cell is false
                        // for each value
                        // create copy of data
                        // add CELL VALUE TO IT
                        let cellRes = false;
                        for (let v of cell.values) {
                            let newdata = Object.assign({}, data);
                            newdata["cellValue"] = v;
                            cellRes = ruleCol.expression.run(newdata, ruleColumnContext);
                            if (cellRes) {
                                if (ruleColumnContext) {
                                    ruleColumnContext.result = "true";
                                }
                                break;
                            }
                        }
                        evaluated = cellRes && evaluated;
                        if (ruleColumnContext && !cellRes) {
                            ruleColumnContext.result = "false";
                        }
                    }
                    else {
                        // create copy of data
                        // add CELL VALUE TO IT
                        let newdata = Object.assign({}, data);
                        newdata["cellValue"] = cell.values;
                        let res = ruleCol.expression.run(newdata, ruleColumnContext);
                        if (ruleColumnContext) {
                            ruleColumnContext.result = res.toString();
                        }
                        evaluated = evaluated && res;
                    }
                    if (ruleColumnContext) {
                        let logitems = Reflect.get(ruleColumnContext.items[ruleColumnContext.items.length - 1], "logs");
                        ruleColumnContext.addLogItems(logitems || []);
                        ruleColumnContext.addLogItem(new EventLog(`ValueTableDefinition: end evaluating ${ruleCol.columnName} cell`));
                    }
                    if (!evaluated)
                        break;
                }
                catch (e) {
                    if (ruleColumnContext) {
                        ruleColumnContext.isError = true;
                        ruleColumnContext.result = e.message;
                        ruleColumnContext.addLogItem(new EventLog(e.message, EventLogType.ERROR));
                    }
                    if (rowContext) {
                        rowContext.isError = true;
                        rowContext.result = e.message;
                        rowContext.addLogItem(new EventLog(e.message, EventLogType.ERROR));
                    }
                    if (subContext) {
                        subContext.isError = true;
                        subContext.result = e.message;
                        subContext.addLogItem(new EventLog(e.message, EventLogType.ERROR));
                    }
                    throw e;
                }
            }
            if (evaluated) {
                //  this is our result, return it.
                let resultdata = {};
                for (let rescol of this.resultColumns) {
                    Reflect.set(resultdata, rescol.name, row.resultCellsData[rescol._id].value);
                }
                if (rowContext) {
                    rowContext.result = "true";
                    if (subContext) {
                        subContext.addLogItems(rowContext.logs);
                        subContext.addLogItem(new EventLog(`ValueTableDefinition: end evaluating ${parseInt(rowidx, 10) + 1}th row with result [${evaluated}]`));
                        subContext.result = JSON.stringify(resultdata);
                        subContext.addLogItem(new EventLog(`ValueTableDefinition: end evaluating with result (${subContext.result})`));
                    }
                }
                return resultdata;
            }
            else if (rowContext) {
                rowContext.result = "false";
                if (subContext) {
                    subContext.addLogItems(rowContext.logs);
                    subContext.addLogItem(new EventLog(`ValueTableDefinition: end evaluating ${parseInt(rowidx, 10) + 1}th row with result [${evaluated}]`));
                }
            }
        }
        // if we get here, none evaluated true, we need to return default this.resultColumns
        // if no default result and we got here, throw error?
        // return default result if allowed.
        if (this.allowDefaultResults) {
            let resultdata = {};
            for (let rescol of this.resultColumns) {
                Reflect.set(resultdata, rescol.name, this.defaultResult[rescol._id].value);
            }
            if (subContext) {
                subContext.result = JSON.stringify(resultdata);
                subContext.addLogItem(new EventLog(`ValueTable: end evaluating with default result (${subContext.result})`));
            }
            return resultdata;
        }
    }
    getDefaultResultByColumnId(columnId) {
        return this._defaultResult[columnId];
    }
    // not sure if it's needed, but it's for getting default result by order
    getDefaultResultByOrder(order) {
        var _a;
        return this._defaultResult[(_a = this.resultColumns[order]) === null || _a === void 0 ? void 0 : _a._id];
    }
    get columns() {
        return this._columns;
    }
    get rows() {
        return this._rows;
    }
    syncAllRows() {
        for (let r of this._rows) {
            this.syncRow(r);
        }
    }
    syncDefaultResults() {
        let outputdef = this.getExecutionResult();
        let columnsIds = this._resultColumns.map((col) => col._id);
        for (let colId in this._defaultResult) {
            if (!columnsIds.includes(colId)) {
                delete this._defaultResult[colId];
            }
        }
        // verify that all the cells are present.
        for (let col of this._resultColumns) {
            let cell = this._defaultResult[col._id];
            if (!cell) {
                this._defaultResult[col._id] = new ResultCell(col._id);
                cell = this._defaultResult[col._id];
            }
            else {
                // ensure its setup right
            }
            if ((cell.value === undefined || cell.value === null) && col.dataType === XDataType.BOOLEAN) {
                cell.value = false;
            }
            if (col.name !== "") {
                try {
                    let field = DataUtilities.getFieldFromDataDefinitionByProperty(outputdef.fields, col.name, "name");
                    if (field && field.type !== col.dataType) {
                        col.dataType = field.type;
                    }
                }
                catch (e) { }
            }
        }
    }
    syncRow(row) {
        // todo: remove extra
        var _a, _b;
        // remove the extra
        let columnsIds = this._columns.map((col) => col._id);
        for (let colId in row.cellData) {
            if (!columnsIds.includes(colId)) {
                delete row.cellData[colId];
            }
        }
        // verify that all the cells are present.
        for (let col of this._columns) {
            let cell = row.cellData[col._id];
            if (!cell) {
                row.cellData[col._id] = new ValueRuleCell(col._id);
                cell = row.cellData[col._id];
            }
            else {
                // ensure its setup right
            }
            if ((cell.values === undefined || cell.values === null) && col.cellData.type === XDataType.BOOLEAN) {
                cell.values = false;
            }
            // confirm if expression inputdata is set correctly.
            let datadef = this.getInputDefinition();
            if (datadef !== col.expression.getInputDefinition()) {
                col.expression.inputData = () => this.getInputDefinition();
            }
            if (col.expression.getRuleResult().type !== XDataType.BOOLEAN ||
                col.expression.getRuleResult().list === true) {
                col.expression.executionResult = () => {
                    return new BooleanDataDefinition();
                };
            }
            if (((_a = col.expression.requiredVariable) === null || _a === void 0 ? void 0 : _a.name) !== col.cellData.name ||
                ((_b = col.expression.requiredVariable) === null || _b === void 0 ? void 0 : _b.type) !== col.cellData.type) {
                col.expression.requiredVariable = col.cellData;
            }
        }
        // remove the extra
        let resColumnsIds = this._resultColumns.map((col) => col._id);
        for (let colId in row.resultCellsData) {
            if (!resColumnsIds.includes(colId)) {
                delete row.resultCellsData[colId];
            }
        }
        // verify that all the cells are present.
        for (let col of this._resultColumns) {
            let cell = row.resultCellsData[col._id];
            if (!cell) {
                row.resultCellsData[col._id] = new ResultCell(col._id);
                cell = row.resultCellsData[col._id];
            }
            else {
                // ensure its setup right
            }
            if ((cell.value === undefined || cell.value === null) && col.dataType === XDataType.BOOLEAN) {
                cell.value = false;
            }
            //
            if (!this.getExecutionResult().fields.find((f) => f.name === col.name && f.type === col.dataType)) {
                if (!this.canChangeOutputs) {
                    // remove bad result col;
                    this.removeResultColumn(col._id);
                }
                else {
                    // update the outputs with this col.
                    this.getExecutionResult().fields.push(DataUtilities.createDataByType(col.dataType, col.name));
                }
            }
        }
    }
    addColumn(name) {
        const newcol = new ValueRuleColumn();
        newcol.expression.inputData = () => {
            return this.getInputDefinition();
        };
        if (!name) {
            let index = this.columns.length + 1;
            name = "column_" + index;
            while (this.getColumnByName(name) !== undefined) {
                index = index + 1;
            }
            name = "column_" + index;
        }
        newcol.columnName = name;
        this._columns.push(newcol);
        this.syncDefaultResults();
        this.syncAllRows();
        return newcol;
    }
    getColumnByName(name) {
        return this.columns.find((c) => c.columnName === name);
    }
    removeColumn(id) {
        if (this._columns.length > 1) {
            this._columns = this._columns.filter((c) => c._id !== id);
            this.syncAllRows();
        }
    }
    addRow() {
        let newRow = new ValueRuleRow();
        this.syncRow(newRow);
        this._rows.push(newRow);
        return newRow;
    }
    removeRow(row) {
        this._rows = this._rows.filter((r) => r !== row);
    }
    changeResultColumn(resultColumn, type) {
        if (!this.canChangeOutputs) {
            throw new Error("cannot add result columns because the outputs are specified by API");
        }
        this.getExecutionResult().fields = this.getExecutionResult().fields.map((field) => field.name === resultColumn.name && field.type === resultColumn.dataType
            ? DataUtilities.createDataByType(type, resultColumn.name)
            : field);
        resultColumn.dataType = type;
    }
    // why is this not in the backend
    renameColumn(oldName, newName) {
        const newcolumn = this._columns.find((c) => c.columnName === newName);
        if (!newcolumn) {
            throw new Error(`Name: ${newName} is already assigned`);
        }
        const oldCol = this._columns.find((c) => c.columnName === oldName);
        if (!oldCol) {
            throw new Error(`Name: ${newName} is already assigned`);
        }
        oldCol.columnName = newName;
    }
    changeColumnType(column, type) {
        column.cellData = DataUtilities.createDataByType(type, column.cellData.name);
        for (let row of this.rows) {
            if (Array.isArray(row.cellData[column._id].values)) {
                row.cellData[column._id].values = row.cellData[column._id].values.map((v) => column.cellData.convert(v));
            }
            else {
                row.cellData[column._id].values = column.cellData.convert(row.cellData[column._id].values);
            }
        }
    }
    getColumnById(id) {
        return this._columns.find((c) => c._id === id);
    }
    getResultColumnById(id) {
        return this._resultColumns.find((resc) => resc._id === id);
    }
    getNextCell(cell) {
        for (let rowIdx in this._rows) {
            let row = this._rows[rowIdx];
            if (cell instanceof ValueRuleCell) {
                let idx = row.cells.indexOf(cell);
                if (idx >= 0) {
                    if (row.cells.length - 1 > idx) {
                        return row.cells[idx + 1];
                    }
                    else {
                        return row.resultCellsData[this.resultColumns[0]._id] || null;
                    }
                }
            }
            else if (row.resultCells.indexOf(cell) >= 0) {
                let resultcolIdx = this.resultColumns.findIndex((rescol) => rescol._id === cell._columnId);
                if (resultcolIdx >= 0) {
                    if (this.resultColumns.length - 1 > resultcolIdx) {
                        return row.cellData[this.resultColumns[resultcolIdx + 1]._id] || null;
                    }
                    else {
                        if (parseInt(rowIdx, 10) < this._rows.length - 1) {
                            return this._rows[parseInt(rowIdx, 10) + 1].cells[0];
                        }
                        return null;
                    }
                }
            }
        }
        if (cell instanceof ResultCell) {
            if (Object.values(this.defaultResult).indexOf(cell) >= 0) {
                let idx = this.resultColumns.findIndex((rescol) => rescol._id === cell._columnId);
                if (idx <= this.resultColumns.length - 1) {
                    return this.defaultResult[this.resultColumns[idx + 1]._id];
                }
            }
        }
        return null;
    }
    setResultColumnType(rule, dataType) {
        super.setResultColumnType(rule, dataType);
        for (let row of this.rows) {
            if (dataType instanceof BooleanDataDefinition) {
                if (row.resultCellsData[rule._id].value === undefined || row.resultCellsData[rule._id].value === null) {
                    row.resultCellsData[rule._id].value = false;
                }
            }
            row.resultCellsData[rule._id].value = dataType.convert(row.resultCellsData[rule._id].value);
        }
    }
    getPrevCell(cell) {
        for (let rowIdx in this._rows) {
            let row = this._rows[rowIdx];
            if (cell instanceof ValueRuleCell) {
                let idx = row.cells.findIndex((c) => c === cell);
                if (idx > 0) {
                    return row.cells[idx - 1];
                }
                else if (idx === 0) {
                    if (parseInt(rowIdx, 10) > 0) {
                        return this._rows[parseInt(rowIdx, 10) - 1].cells[row.cells.length - 1];
                    }
                    return null;
                }
            }
            else if (row.resultCells.indexOf(cell) >= 0) {
                let resultcolIdx = this.resultColumns.findIndex((rescol) => rescol._id === cell._columnId);
                if (resultcolIdx > 0) {
                    return row.cellData[this.resultColumns[resultcolIdx - 1]._id] || null;
                }
                else {
                    return row.cells[row.cells.length - 1];
                }
            }
        }
        if (cell instanceof ResultCell) {
            if (Object.values(this.defaultResult).indexOf(cell) >= 0) {
                let idx = this.resultColumns.findIndex((rescol) => rescol._id === cell._columnId);
                if (idx > 0) {
                    return this.defaultResult[this.resultColumns[idx - 1]._id];
                }
            }
        }
        return null;
    }
    getNextRowCell(cell) {
        for (let rowIdx in this._rows) {
            let row = this._rows[rowIdx];
            if (cell instanceof ValueRuleCell) {
                let idx = row.cells.findIndex((c) => c === cell);
                if (idx >= 0) {
                    if (parseInt(rowIdx, 10) < this._rows.length - 1) {
                        return this._rows[parseInt(rowIdx, 10) + 1].cells[idx];
                    }
                    else {
                        return null;
                    }
                }
            }
            else if (row.resultCells.indexOf(cell) >= 0) {
                let idx = this.resultColumns.findIndex((rescol) => rescol._id === cell._columnId);
                if (parseInt(rowIdx, 10) < this._rows.length - 1) {
                    return this._rows[parseInt(rowIdx, 10) + 1].resultCellsData[this.resultColumns[idx]._id];
                }
                else if (parseInt(rowIdx, 10) === this._rows.length - 1 && this.allowDefaultResults) {
                    return this._defaultResult[this.resultColumns[idx]._id];
                }
                else {
                    return null;
                }
            }
        }
        return null;
    }
    getPrevRowCell(cell) {
        for (let rowIdx in this._rows) {
            let row = this._rows[rowIdx];
            if (cell instanceof ValueRuleCell) {
                let idx = row.cells.findIndex((c) => c === cell);
                if (idx >= 0) {
                    if (parseInt(rowIdx, 10) > 0) {
                        return this._rows[parseInt(rowIdx, 10) - 1].cells[idx];
                    }
                    else {
                        return null;
                    }
                }
            }
            else if (row.resultCells.indexOf(cell) >= 0) {
                let idx = this.resultColumns.findIndex((rescol) => rescol._id === cell._columnId);
                if (parseInt(rowIdx, 10) > 0) {
                    return this._rows[parseInt(rowIdx, 10) - 1].resultCellsData[this.resultColumns[idx]._id];
                }
                else {
                    return null;
                }
            }
        }
        if (cell instanceof ResultCell) {
            if (Object.values(this.defaultResult).indexOf(cell) >= 0) {
                let idx = this.resultColumns.findIndex((rescol) => rescol._id === cell._columnId);
                return this._rows[this.rows.length - 1].resultCellsData[this.resultColumns[idx]._id];
            }
        }
        return null;
    }
    getDesignValidationIssues() {
        var _a;
        let issues = [];
        for (let colidx in this.columns) {
            let col = this.columns[colidx];
            issues.push(...col.getDesignValidationIssues().map((issue) => {
                issue.issue = `${parseInt(colidx, 10) + 1}th rule column(${col._id}): ${issue.issue}`;
                return issue;
            }));
            for (let rowIdx in this.rows) {
                let row = this.rows[rowIdx];
                issues.push(...(((_a = row.cellData[col._id]) === null || _a === void 0 ? void 0 : _a.validateCellData(col.cellData)) || []).map((issue) => {
                    issue.issue = `cell (${parseInt(rowIdx, 10) + 1}, ${parseInt(colidx, 10) + 1}): ${issue.issue}`;
                    return issue;
                }));
            }
        }
        for (let rowIdx in this.rows) {
            let row = this.rows[rowIdx];
            for (let cellIdx in row.resultCells) {
                let cell = row.resultCells[cellIdx];
                issues.push(...this.getResultCellValidationIssues(cell).map((issue) => {
                    issue.issue = `result cell (${parseInt(rowIdx, 10) + 1}, ${parseInt(cellIdx, 10) + 1}): ${issue.issue}`;
                    return issue;
                }));
            }
        }
        for (let rescolidx in this.resultColumns) {
            let rescol = this.resultColumns[rescolidx];
            issues.push(...this.getResultColumnValidationIssues(rescol).map((issue) => {
                issue.issue = `${rescol.name} result column: ${issue.issue}`;
                return issue;
            }));
            if (this.allowDefaultResults) {
                issues.push(...this.getDefaultResultCellValidationIssues(this._defaultResult[rescol._id]).map((issue) => {
                    issue.issue = `${rescol.name} default result column: ${issue.issue}`;
                    return issue;
                }));
            }
        }
        return issues;
    }
    getStorageData() {
        let data = super.getStorageData();
        Reflect.set(data, "allowDefaultResults", this.allowDefaultResults);
        let colData = [];
        for (let c of this.columns) {
            colData.push(c.getStorageData());
        }
        Reflect.set(data, "cols", colData);
        let resultData = [];
        for (let c of this.resultColumns) {
            resultData.push(c.getStorageData());
        }
        Reflect.set(data, "resultCols", resultData);
        let defaultsData = {};
        for (let cId in this._defaultResult) {
            Reflect.set(defaultsData, cId, this._defaultResult[cId].getStorageData());
        }
        Reflect.set(data, "defaults", defaultsData);
        let rowsData = [];
        for (let c of this.rows) {
            rowsData.push(c.getStorageData());
        }
        Reflect.set(data, "rows", rowsData);
        return data;
    }
    setStorageData(data) {
        super.setStorageData(data);
        this.allowDefaultResults = Reflect.get(data, "allowDefaultResults");
        let colData = Reflect.get(data, "cols");
        this._columns = colData.map((value) => {
            let col = new ValueRuleColumn();
            col.setStorageData(value);
            return col;
        });
        let resultData = Reflect.get(data, "resultCols");
        this._resultColumns = resultData.map((value) => {
            let col = new TableColumn();
            col.setStorageData(value);
            return col;
        });
        let defaultsData = Reflect.get(data, "defaults");
        for (let dd in defaultsData) {
            let resultCell = new ResultCell(dd);
            resultCell.setStorageData(Reflect.get(defaultsData, dd));
            this._defaultResult[resultCell._columnId] = resultCell;
        }
        let rowsData = Reflect.get(data, "rows");
        this._rows = rowsData.map((value) => {
            let col = new ValueRuleRow();
            col.setStorageData(value);
            return col;
        });
    }
    setRuleColumnType(rule, dataType) {
        rule.cellData = dataType;
        rule.expression.requiredVariable = rule.cellData;
        // setting the default value for boolean data definition
        for (let row of this.rows) {
            if (dataType instanceof BooleanDataDefinition) {
                if (row.cellData[rule._id].values === undefined || row.cellData[rule._id].values === null) {
                    row.cellData[rule._id].values = false;
                }
            }
            row.cellData[rule._id].values = ValueRuleCell.MISSING_CELL_VALUE;
        }
    }
    getCellDataDef(cell) {
        let rulecolumn = this.columns.find((c) => c._id === cell._columnId);
        if (!rulecolumn)
            throw new Error("can not find the cell datadef");
        if (Array.isArray(cell.values)) {
            let datadef = DataUtilities.createDataByType(rulecolumn.cellData.type, rulecolumn.cellData.name);
            datadef.setStorageData(rulecolumn.cellData.getStorageData());
            datadef.list = true;
            return datadef;
        }
        return rulecolumn.cellData;
    }
}
//# sourceMappingURL=ValueTableDefinition.js.map