import { TableColumn } from "../../Shared/TableColumn";
import { RuleTableCell } from "./RuleTableCell";
import { RuleTableRow } from "./RuleTableRow";
import { ValidationIssue } from "../../../../Common/IDesignValidationProvider";
import { XDataType } from "../../../../Common/Data/XDataType";
import { XExpressionDefinition } from "../../../../Expressions/XExpressionDefinition";
import { DataUtilities } from "../../../../Common/DataUtilities";
import { BooleanDataDefinition } from "../../../../Common/Data/Definitions/BooleanDataDefinition";
import { ResultCell } from "../../Common/ResultCell";
import { BaseRuleTableDefinition } from "../../Common/BaseRuleTableDefinition";
import { RuleTableColumn } from "./RuleTableRuleColumn";
import { RuleTableRowTestResult, RuleTableCellTestResult, RuleTableTestResult } from "../RuleTableTestResult";
import { EventLog, EventLogType } from "../../../../Common/Event/EventLog";
import { ExpressionValueType } from "../../../../Expressions/ExpressionValueType";
// 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: add result col is missing
export class RuleTableDefinition extends BaseRuleTableDefinition {
    constructor() {
        super();
        this._id = DataUtilities.generateUniqueId();
        this._ruleColumns = [new RuleTableColumn()];
        this._rows = [];
        if (this.canChangeOutputs) {
            this.addResultColumn();
        }
        this.addRow();
    }
    run(data, testContext) {
        this.syncAllRows();
        let subContext;
        if (testContext) {
            subContext = new RuleTableTestResult(this._id);
            subContext.definition = this;
            testContext.addTestResultItem(subContext);
            subContext.addLogItem(new EventLog(`RuleTable: start evaluating`));
        }
        for (let rowidx in this._rows) {
            let row = this._rows[rowidx];
            let evaluated = true;
            let rowContext;
            if (subContext) {
                rowContext = new RuleTableRowTestResult(row._id);
                rowContext.addLogItem(new EventLog(`RuleTable: start evaluating ${parseInt(rowidx, 10) + 1}th row`));
                subContext.addTestResultItem(rowContext);
            }
            for (let ruleColumn of this.ruleColumns) {
                let cell = row.ruleCells[ruleColumn._id];
                if (!cell.expression) {
                    throw new Error("Expression is not set.");
                }
                let ruleColumnContext;
                if (rowContext) {
                    ruleColumnContext = new RuleTableCellTestResult(`${row._id}-${cell._columnId}`);
                    ruleColumnContext.addLogItem(new EventLog(`RuleTable: start evaluating "${ruleColumn.name || ruleColumn.path}" column`));
                    rowContext.addTestResultItem(ruleColumnContext);
                }
                try {
                    let res = cell.expression.run(data, ruleColumnContext);
                    evaluated = evaluated && res;
                    if (ruleColumnContext) {
                        ruleColumnContext.result = res.toString();
                        ruleColumnContext.addLogItem(new EventLog(`ValueTable: end evaluating "${ruleColumn.name || ruleColumn.path}" cell with result [${ruleColumnContext.result}]`));
                    }
                }
                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) {
                    break;
                }
            }
            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";
                    rowContext.addLogItem(new EventLog(`RuleTable: end evaluating ${parseInt(rowidx, 10) + 1}th row with result [${evaluated}]`));
                    if (subContext) {
                        subContext.addLogItems(rowContext.logs);
                        subContext.addLogItem(new EventLog(`RuleTable: end evaluating ${parseInt(rowidx, 10) + 1}th row with result [${evaluated}]`));
                        subContext.result = JSON.stringify(resultdata);
                        subContext.addLogItem(new EventLog(`RuleTable: end evaluating with result (${subContext.result})`));
                    }
                }
                return resultdata;
            }
            else if (rowContext) {
                rowContext.result = "false";
                rowContext.addLogItem(new EventLog(`RuleTable: end evaluating ${parseInt(rowidx, 10) + 1}th row with result [${evaluated}]`));
                if (subContext) {
                    subContext.addLogItems(rowContext.logs);
                    subContext.addLogItem(new EventLog(`RuleTable: end evaluating ${parseInt(rowidx, 10) + 1}th row with result [${evaluated}]`));
                }
            }
        }
        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(`RuleTable: end evaluating with default result (${subContext.result})`));
            }
            return resultdata;
        }
    }
    getDefaultResultByOrder(order) {
        var _a;
        return this._defaultResult[(_a = this.resultColumns[order]) === null || _a === void 0 ? void 0 : _a._id];
    }
    get defaultResult() {
        return this._defaultResult;
    }
    get rows() {
        return this._rows;
    }
    get resultColumns() {
        return this._resultColumns;
    }
    get ruleColumns() {
        return this._ruleColumns;
    }
    syncAllRows() {
        for (let r of this._rows) {
            this.syncRow(r);
        }
    }
    syncRow(row) {
        // todo: remove extra
        // remove the extra
        let columnsIds = this._ruleColumns.map((col) => col._id);
        for (let colId in row.ruleCells) {
            if (!columnsIds.includes(colId)) {
                delete row.ruleCells[colId];
            }
        }
        // verify that all the cells are present.
        for (let col of this._ruleColumns) {
            let cell = row.ruleCells[col._id];
            if (!cell) {
                cell = new RuleTableCell(col._id);
                row.ruleCells[col._id] = cell;
            }
            else {
                // ensure setup is correct
            }
            if (!cell.expression) {
                cell.expression = new XExpressionDefinition();
                cell.expression.valueType = ExpressionValueType.SUBEXPRESSION;
            }
            let datadef = this.getInputDefinition();
            if (datadef !== cell.expression.getInputDefinition()) {
                cell.expression.inputData = () => this.getInputDefinition();
            }
            if (cell.expression.getRuleResult().type !== XDataType.BOOLEAN) {
                let bool = new BooleanDataDefinition();
                cell.expression.executionResult = () => {
                    return bool;
                };
            }
            if (!cell.expression.requiredVariable) {
                cell.expression.requiredVariable = this.getRuleColumnData(col);
            }
        }
        // remove the extra
        columnsIds = this._resultColumns.map((col) => col._id);
        for (let colId in row.resultCellsData) {
            if (!columnsIds.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) {
                cell = new ResultCell(col._id);
                row.resultCellsData[col._id] = cell;
            }
            else {
                // ensure setup is 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));
                }
            }
        }
    }
    addRow(row) {
        let newRow = row;
        if (!newRow) {
            newRow = new RuleTableRow();
        }
        this.syncRow(newRow);
        this._rows.push(newRow);
        return newRow;
    }
    removeRow(row) {
        this._rows = this._rows.filter((r) => r !== row);
    }
    getColumnByName(name) {
        return this.ruleColumns.find((c) => c.name === name);
    }
    // todo: this should just take in name
    addRuleColumn(name) {
        let newCol = new RuleTableColumn();
        if (!name) {
            name = "";
            // no need to create the name as we can use the variable picker
            let index = this.ruleColumns.length + 1;
            name = "column_" + index;
            while (this.getColumnByName(name) !== undefined) {
                index = index + 1;
            }
            name = "column_" + index;
        }
        newCol.name = name;
        this._ruleColumns.push(newCol);
        this.syncAllRows();
        return newCol;
    }
    removeRuleColumn(id) {
        if (this._ruleColumns.length > 1) {
            this._ruleColumns = this._ruleColumns.filter((c) => c._id !== id);
            this.syncAllRows();
        }
    }
    getResultColumnType(resultcol) {
        return this.getExecutionResult().fields.find((f) => f.name === resultcol.name && f.type === resultcol.dataType);
    }
    setResultColumnType(rule, dataType) {
        if (!this.canChangeOutputs)
            throw new Error("it's not editable");
        let defaultResult = this.getDefaultResultByColumnId(rule._id);
        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);
        }
        // not sure if it's needed
        if (defaultResult) {
            if (dataType instanceof BooleanDataDefinition &&
                (defaultResult.value === undefined || defaultResult.value === null)) {
                defaultResult.value = false;
            }
            defaultResult.value = dataType.convert(defaultResult.value);
        }
        let outputdef = this.getExecutionResult();
        // remove old and add new;
        outputdef.fields = outputdef.fields.map((f) => {
            if (f.name === rule.name && f.type === rule.dataType) {
                return dataType;
            }
            return f;
        });
        rule.name = dataType.name;
        rule.dataType = dataType.type;
    }
    syncDefaultResults() {
        let columnsIds = this._resultColumns.map((col) => col._id);
        for (let colId in this._defaultResult) {
            if (!columnsIds.includes(colId)) {
                delete this._defaultResult[colId];
            }
        }
        let outputdef = this.getExecutionResult();
        // 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 setup is 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) { }
            }
        }
    }
    removeResultColumn(id) {
        if (!this.canChangeOutputs) {
            throw new Error("cannot add result columns because the outputs are specified by API");
        }
        if (this._resultColumns.length > 1) {
            let resultColIdx = this._resultColumns.findIndex((resultCol) => resultCol._id === id);
            if (resultColIdx < 0)
                return;
            let resultcol = this._resultColumns[resultColIdx];
            this._resultColumns.splice(resultColIdx, 1);
            this.getExecutionResult().fields = this.getExecutionResult().fields.filter((f) => f.name !== resultcol.name || f.type !== resultcol.dataType);
            this.syncDefaultResults();
            this.syncAllRows();
        }
    }
    getResultColumnValidationIssues(rescol) {
        let issues = rescol.getDesignValidationIssues();
        let outputdef = this.getExecutionResult();
        if (!rescol.dataType) {
            issues.push(new ValidationIssue("Datatype required"));
        }
        try {
            const field = DataUtilities.getFieldFromDataDefinitionByProperty(outputdef.fields, rescol.name, "name");
            if (!field)
                issues.push(new ValidationIssue("Data not found"));
            else if (field && field.type !== rescol.dataType) {
                issues.push(new ValidationIssue("Data type does not match required type"));
            }
        }
        catch (e) {
            issues.push(new ValidationIssue("Data not found"));
        }
        return issues;
    }
    getRuleCellValidationIssues(rulecell) {
        let issues = [];
        let column = this._ruleColumns.find((col) => col._id === rulecell._columnId);
        if (!column) {
            issues.push(new ValidationIssue("Rule column not found"));
        }
        else {
            issues.push(...rulecell.getDesignValidationIssues());
        }
        return issues;
    }
    getRuleColumnValidationIssues(rulecol) {
        let issues = [];
        issues.push(...rulecol.getDesignValidationIssues());
        let inputdef = this.getInputDefinition();
        if (rulecol.path) {
            try {
                const field = DataUtilities.getFieldFromDataDefinitionByProperty(inputdef.fields, rulecol.path, "name");
                if (!field)
                    issues.push(new ValidationIssue("Column data not found"));
            }
            catch (e) {
                // todo: why is there a catch here?  this is bad code.
                issues.push(new ValidationIssue("Error looking up column definition " + e));
            }
        }
        return issues;
    }
    getDesignValidationIssues() {
        let issues = [];
        for (let colidx in this._ruleColumns) {
            const col = this._ruleColumns[colidx];
            issues.push(...this.getRuleColumnValidationIssues(col).map((issue) => {
                issue.issue = `Rule column [${col.name}]:: ${issue.issue}`;
                return issue;
            }));
        }
        for (let rescolidx in this._resultColumns) {
            const rescol = this._resultColumns[rescolidx];
            issues.push(...this.getResultColumnValidationIssues(rescol).map((issue) => {
                issue.issue = `Result column [${rescol.name}]:: ${issue.issue}`;
                return issue;
            }));
            if (this.allowDefaultResults) {
                issues.push(...this.getDefaultResultCellValidationIssues(this.defaultResult[rescol._id]).map((issue) => {
                    issue.issue = `Result default cell [${rescol.name}]:: ${issue.issue}`;
                    return issue;
                }));
            }
        }
        for (let rowidx in this.rows) {
            const row = this.rows[rowidx];
            for (let columnIdx in this.ruleColumns) {
                const column = this.ruleColumns[columnIdx];
                const cell = row.ruleCells[column._id];
                issues.push(...this.getRuleCellValidationIssues(cell).map((issue) => {
                    issue.issue = `Rule cell [${column.name}, ${parseInt(rowidx, 10) + 1}]:: ${issue.issue}`;
                    return issue;
                }));
            }
            for (let rescol of this.resultColumns) {
                let cell = row.resultCellsData[rescol._id];
                issues.push(...this.getResultCellValidationIssues(cell).map((issue) => {
                    issue.issue = `Result cell [${rescol.name}, ${parseInt(rowidx, 10) + 1}]:: ${issue.issue}`;
                    return issue;
                }));
            }
        }
        return issues;
    }
    getStorageData() {
        let data = super.getStorageData();
        let colData = [];
        for (let c of this.ruleColumns) {
            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);
        let colData = Reflect.get(data, "cols");
        this._ruleColumns = colData.map((value) => {
            let col = new TableColumn();
            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 RuleTableRow();
            col.setStorageData(value);
            return col;
        });
    }
    getRuleColumnData(rule) {
        if (!rule.path)
            return undefined;
        try {
            const dataDef = this.getInputDefinition();
            const field = DataUtilities.getFieldFromDataDefinitionByProperty(dataDef.fields, rule.path, "name");
            return field;
        }
        catch (e) {
            return undefined;
        }
    }
    setRuleColumn(rule, path) {
        const dataDef = this.getInputDefinition();
        try {
            const field = DataUtilities.getFieldFromDataDefinitionByProperty(dataDef.fields, path, "name");
            if (field) {
                rule.name = field.name;
                rule.path = path;
                for (let row of this.rows) {
                    if (!row.ruleCells[rule._id].expression) {
                        row.ruleCells[rule._id].expression = new XExpressionDefinition();
                    }
                    row.ruleCells[rule._id].expression.requiredVariable = field;
                }
            }
        }
        catch (e) {
            console.log("Set Rule Column Error:", e.message);
        }
    }
    getNextCell(cell) {
        if (this.resultColumns.length === 0)
            return null;
        for (let rowIdx in this._rows) {
            let row = this._rows[rowIdx];
            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.resultCellsData[this.resultColumns[resultcolIdx + 1]._id] || null;
                    }
                    else {
                        if (parseInt(rowIdx, 10) < this._rows.length - 1) {
                            return this._rows[parseInt(rowIdx, 10) + 1].resultCellsData[this.resultColumns[0]._id];
                        }
                        return null;
                    }
                }
            }
        }
        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;
    }
    getPrevCell(cell) {
        if (this.resultColumns.length === 0)
            return null;
        for (let rowIdx in this._rows) {
            let row = this._rows[rowIdx];
            if (row.resultCells.indexOf(cell) >= 0) {
                let resultcolIdx = this.resultColumns.findIndex((rescol) => rescol._id === cell._columnId);
                if (resultcolIdx > 0) {
                    return row.resultCellsData[this.resultColumns[resultcolIdx - 1]._id] || null;
                }
                else {
                    if (parseInt(rowIdx, 10) > 0) {
                        return this._rows[parseInt(rowIdx, 10) - 1].resultCellsData[this.resultColumns[this.resultColumns.length - 1]._id];
                    }
                    return null;
                }
            }
        }
        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 (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 (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;
    }
}
//# sourceMappingURL=RuleTableDefinition.js.map