/**
 * Copyright (c) 2020-present, Goldman Sachs
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { matchFunctionName } from '@finos/legend-graph';
import { addUniqueEntry, deleteEntry, guaranteeNonNullable, hashArray, uniq, } from '@finos/legend-shared';
import { action, computed, makeObservable, observable } from 'mobx';
import { QUERY_BUILDER_STATE_HASH_STRUCTURE } from '../../../QueryBuilderStateHashUtils.js';
import { DEFAULT_LAMBDA_VARIABLE_NAME } from '../../../QueryBuilderConfig.js';
import { QueryBuilderTDSColumnState } from '../QueryBuilderTDSColumnState.js';
export const QUERY_BUILDER_WINDOW_COLUMN_DND_TYPE = 'WINDOW_COLUMN';
export class WindowGroupByColumnSortByState {
    columnState;
    sortType;
    constructor(columnState, sortType) {
        makeObservable(this, {
            columnState: observable,
            sortType: observable,
            setColumnState: action,
            setSortType: action,
        });
        this.columnState = columnState;
        this.sortType = sortType;
    }
    setColumnState(colState) {
        this.columnState = colState;
    }
    setSortType(type) {
        this.sortType = type;
    }
    get hashCode() {
        return hashArray([
            QUERY_BUILDER_STATE_HASH_STRUCTURE.TDS_WINDOW_COLUMN_SORTBY_STATE,
            this.sortType,
            this.columnState.columnName,
        ]);
    }
}
export class QueryBuilderTDS_WindowOperatorState {
    windowState;
    lambdaParameterNames = [DEFAULT_LAMBDA_VARIABLE_NAME];
    operator;
    constructor(windowState, operator) {
        this.windowState = windowState;
        this.operator = operator;
    }
    setLambdaParameterNames(paramNames) {
        this.lambdaParameterNames = paramNames;
    }
    addParameterName(paramName) {
        this.lambdaParameterNames.push(paramName);
    }
    setOperator(val) {
        this.operator = val;
    }
    get hashCode() {
        return hashArray([
            QUERY_BUILDER_STATE_HASH_STRUCTURE.TDS_WINDOW_GROUPBY_OPERATION_STATE,
            hashArray(this.lambdaParameterNames),
            this.operator,
        ]);
    }
}
export class QueryBuilderTDS_WindowRankOperatorState extends QueryBuilderTDS_WindowOperatorState {
    constructor(windowState, operator) {
        super(windowState, operator);
        makeObservable(this, {
            setLambdaParameterNames: action,
        });
    }
}
export class QueryBuilderTDS_WindowAggreationOperatorState extends QueryBuilderTDS_WindowOperatorState {
    columnState;
    constructor(windowState, operator, columnState) {
        super(windowState, operator);
        makeObservable(this, {
            columnState: observable,
            setColumnState: action,
            setLambdaParameterNames: action,
        });
        this.columnState = columnState;
    }
    setColumnState(val) {
        this.columnState = val;
    }
    get hashCode() {
        return hashArray([
            QUERY_BUILDER_STATE_HASH_STRUCTURE.TDS_WINDOW_GROUPBY_AGG_OPERATOR_STATE,
            hashArray(this.lambdaParameterNames),
            this.operator,
            this.columnState.columnName,
        ]);
    }
}
export class QueryBuilderWindowColumnState extends QueryBuilderTDSColumnState {
    windowState;
    windowColumns = [];
    sortByState;
    operatorState;
    columnName;
    constructor(windowState, windowColumns, sortType, operatorState, columnName) {
        super();
        makeObservable(this, {
            windowColumns: observable,
            sortByState: observable,
            operatorState: observable,
            columnName: observable,
            setOperatorState: observable,
            setColumnName: action,
            setSortBy: action,
            changeWindow: action,
            deleteWindow: action,
            addWindow: action,
            setWindows: action,
            changeOperator: action,
            changeSortBy: action,
        });
        this.windowState = windowState;
        this.windowColumns = windowColumns;
        this.sortByState = sortType;
        this.operatorState = operatorState;
        this.columnName = columnName;
    }
    get columnWindowGroupIdx() {
        return this.windowState.windowColumns.findIndex((e) => e === this);
    }
    get possibleReferencedColumns() {
        // column can only reference TDS columns already defined, i.e in an earlier index of the window columns
        const idx = this.windowState.tdsState.tdsColumns.findIndex((e) => e === this);
        if (idx === -1) {
            return this.windowState.tdsState.tdsColumns;
        }
        return this.windowState.tdsState.tdsColumns.slice(0, idx);
    }
    get referencedTDSColumns() {
        const operatorReference = this.operatorState instanceof
            QueryBuilderTDS_WindowAggreationOperatorState
            ? [this.operatorState.columnState]
            : [];
        const soryByReference = this.sortByState
            ? [this.sortByState.columnState]
            : [];
        return [...this.windowColumns, ...soryByReference, ...operatorReference];
    }
    getColumnType() {
        return this.operatorState.operator.getOperatorReturnType(this.windowState.tdsState.queryBuilderState.graphManagerState.graph);
    }
    setColumnName(val) {
        this.columnName = val;
    }
    setOperatorState(op) {
        this.operatorState = op;
    }
    setSortBy(val) {
        this.sortByState = val;
    }
    changeWindow(val, idx) {
        this.windowColumns[idx] = val;
    }
    addWindow(val) {
        addUniqueEntry(this.windowColumns, val);
    }
    setWindows(val) {
        this.windowColumns = val;
    }
    deleteWindow(val) {
        deleteEntry(this.windowColumns, val);
    }
    possibleAggregatedColumns(op) {
        if (op.isColumnAggregator()) {
            return this.possibleReferencedColumns.filter((col) => op.isCompatibleWithColumn(col));
        }
        return [];
    }
    getChangeOperatorStateAndColumnName(currentOperator, currentColumn, newOperator) {
        if (currentOperator !== newOperator) {
            if (newOperator.isColumnAggregator()) {
                const compatibleAggCol = currentColumn && newOperator.isCompatibleWithColumn(currentColumn)
                    ? currentColumn
                    : this.possibleAggregatedColumns(newOperator)[0];
                if (compatibleAggCol) {
                    return {
                        operatorState: new QueryBuilderTDS_WindowAggreationOperatorState(this.windowState, newOperator, compatibleAggCol),
                        columnName: `${newOperator.getLabel()} of ${compatibleAggCol.columnName}`,
                    };
                }
            }
            else {
                return {
                    operatorState: new QueryBuilderTDS_WindowRankOperatorState(this.windowState, newOperator),
                    columnName: `${newOperator.getLabel()}`,
                };
            }
        }
        return undefined;
    }
    changeOperator(windowOp) {
        const stateAndName = this.getChangeOperatorStateAndColumnName(this.operatorState.operator, this.operatorState instanceof
            QueryBuilderTDS_WindowAggreationOperatorState
            ? this.operatorState.columnState
            : undefined, windowOp);
        if (stateAndName) {
            this.setOperatorState(stateAndName.operatorState);
            this.setColumnName(stateAndName.columnName);
        }
    }
    changeSortBy(sortOp) {
        const sortByState = this.sortByState;
        if (sortByState?.sortType !== sortOp) {
            if (sortOp) {
                const sortInfoOpState = sortByState ??
                    new WindowGroupByColumnSortByState(guaranteeNonNullable(this.possibleReferencedColumns[0]), sortOp);
                sortInfoOpState.setSortType(sortOp);
                this.setSortBy(sortInfoOpState);
            }
            else {
                this.setSortBy(undefined);
            }
        }
    }
    get hashCode() {
        return hashArray([
            QUERY_BUILDER_STATE_HASH_STRUCTURE.TDS_WINDOW_COLUMN_STATE,
            hashArray(this.windowColumns),
            this.sortByState ?? '',
            this.operatorState,
            this.columnName,
        ]);
    }
}
export class QueryBuilderWindowState {
    tdsState;
    windowColumns = [];
    operators;
    editColumn;
    constructor(tdsState, operators) {
        makeObservable(this, {
            windowColumns: observable,
            editColumn: observable,
            invalidWindowColumnNames: computed,
            windowValidationIssues: computed,
            addWindowColumn: action,
            removeColumn: action,
            moveColumn: action,
            setEditColumn: action,
        });
        this.tdsState = tdsState;
        this.operators = operators;
    }
    get isEmpty() {
        return !this.windowColumns.length;
    }
    get invalidWindowColumnNames() {
        const invalidColNames = [];
        const windowCols = this.windowColumns;
        windowCols.forEach((item, index) => {
            if (item.operatorState instanceof
                QueryBuilderTDS_WindowAggreationOperatorState) {
                if (item.operatorState.columnState instanceof
                    QueryBuilderWindowColumnState) {
                    const windowColumnName = item.operatorState.columnState.columnName;
                    const hasExistingColumn = item.windowState.isColumnOrderValid(windowColumnName, index);
                    if (!hasExistingColumn) {
                        invalidColNames.push({
                            invalidColumnName: item.columnName,
                            missingColumnName: windowColumnName,
                        });
                    }
                }
            }
        });
        return invalidColNames;
    }
    get windowValidationIssues() {
        const invalidWindowColumnNames = this.invalidWindowColumnNames;
        const issues = [];
        invalidWindowColumnNames.forEach((item) => {
            issues.push(`Column '${item.invalidColumnName}' cannot exist before column name '${item.missingColumnName}'`);
        });
        const hasDuplicatedWindowColumns = this.windowColumns.some((column) => this.windowColumns.filter((c) => c.columnName === column.columnName)
            .length > 1);
        if (hasDuplicatedWindowColumns) {
            issues.push(`Query has duplicated window columns`);
        }
        const hasDuplicatedProjectionWindowColumns = this.windowColumns.some((column) => this.tdsState.projectionColumns.filter((c) => c.columnName === column.columnName).length > 0);
        if (hasDuplicatedProjectionWindowColumns) {
            issues.push('Query has duplicated projection/window columns');
        }
        return issues;
    }
    get referencedTDSColumns() {
        return uniq(this.windowColumns.map((c) => c.referencedTDSColumns).flat());
    }
    setEditColumn(col) {
        this.editColumn = col;
    }
    findOperator(func, typed) {
        return this.operators.find((operator) => {
            const operatorFunction = typed
                ? operator.relationFunc
                : operator.pureFunc;
            return operatorFunction
                ? matchFunctionName(func, operatorFunction)
                : undefined;
        });
    }
    addWindowColumn(windowCol) {
        addUniqueEntry(this.windowColumns, windowCol);
    }
    removeColumn(column) {
        deleteEntry(this.windowColumns, column);
    }
    moveColumn(fromIndex, toIndex) {
        const fromCol = this.windowColumns[fromIndex];
        const toCol = this.windowColumns[toIndex];
        if (fromCol && toCol) {
            this.windowColumns[fromIndex] = toCol;
            this.windowColumns[toIndex] = fromCol;
        }
    }
    isColumnOrderValid(columnName, indexRange) {
        return this.windowColumns
            .slice(0, indexRange)
            .some((col) => col.columnName === columnName);
    }
    get hashCode() {
        return hashArray([
            QUERY_BUILDER_STATE_HASH_STRUCTURE.TDS_WINDOW_GROUPBY_STATE,
            hashArray(this.windowColumns),
        ]);
    }
}
//# sourceMappingURL=QueryBuilderWindowState.js.map