/**
 * 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 { AssertionStatus, RunTestsTestableInput, TestSuite, AtomicTest, UniqueTestId, TestError, TestExecutionStatus, TestExecuted, AssertPass, AssertFail, PackageableElement, getNullableIDFromTestable, MultiExecutionServiceTestResult, } from '@finos/legend-graph';
import { assertErrorThrown, isNonNullable, ActionState, uuid, assertTrue, guaranteeNonNullable, UnsupportedOperationError, filterByType, } from '@finos/legend-shared';
import { action, flow, makeObservable, observable } from 'mobx';
import { ServiceEditorState } from '../../editor-state/element-editor-state/service/ServiceEditorState.js';
import { LegendStudioUserDataHelper } from '../../../../__lib__/LegendStudioUserDataHelper.js';
export const getTestableMetadata = (testable, editorStore, extraTestableMetadataGetters) => {
    if (testable instanceof PackageableElement) {
        return {
            testable,
            id: getNullableIDFromTestable(testable, editorStore.graphManagerState.graph, editorStore.graphManagerState.pluginManager.getPureGraphManagerPlugins()) ?? uuid(),
            name: testable.name,
        };
    }
    const extraTestables = extraTestableMetadataGetters
        .map((getter) => getter(testable, editorStore))
        .filter(isNonNullable);
    return (extraTestables[0] ?? {
        testable,
        id: uuid(),
        name: '(unknown)',
    });
};
// TreeData
export class TestableExplorerTreeNodeData {
    isSelected;
    isOpen;
    id;
    label;
    childrenIds;
    constructor(id, label) {
        this.id = id;
        this.label = label;
    }
}
export class TestableTreeNodeData extends TestableExplorerTreeNodeData {
    testableMetadata;
    isRunning = false;
    constructor(testable) {
        super(testable.id, testable.id);
        this.testableMetadata = testable;
        makeObservable(this, {
            isRunning: observable,
        });
    }
}
export class TestTreeNodeData extends TestableExplorerTreeNodeData {
    isRunning = false;
    constructor(id, label) {
        super(id, label);
        makeObservable(this, {
            isRunning: observable,
        });
    }
}
export class AtomicTestTreeNodeData extends TestTreeNodeData {
    atomicTest;
    constructor(id, atomicTest) {
        super(id, atomicTest.id);
        this.atomicTest = atomicTest;
    }
}
export class TestSuiteTreeNodeData extends TestTreeNodeData {
    testSuite;
    constructor(id, testSuite) {
        super(id, testSuite.id);
        this.testSuite = testSuite;
    }
}
export class AssertionTestTreeNodeData extends TestableExplorerTreeNodeData {
    assertion;
    constructor(id, assertion) {
        super(id, assertion.id);
        this.assertion = assertion;
    }
}
const buildTestNodeData = (test, parentId) => {
    if (test instanceof AtomicTest) {
        return new AtomicTestTreeNodeData(`${parentId}.${test.id}`, test);
    }
    else if (test instanceof TestSuite) {
        return new TestSuiteTreeNodeData(`${parentId}.${test.id}`, test);
    }
    return undefined;
};
const buildChildrenIfPossible = (node, treeData) => {
    if (!node.childrenIds) {
        let children = [];
        if (node instanceof TestableTreeNodeData) {
            children = node.testableMetadata.testable.tests
                .map((t) => buildTestNodeData(t, node.id))
                .filter(isNonNullable);
        }
        else if (node instanceof TestSuiteTreeNodeData) {
            children = node.testSuite.tests
                .map((t) => buildTestNodeData(t, node.id))
                .filter(isNonNullable);
        }
        else if (node instanceof AtomicTestTreeNodeData) {
            children = node.atomicTest.assertions.map((assertion) => {
                const assertionNode = new AssertionTestTreeNodeData(`${node.id}.${assertion.id}`, assertion);
                return assertionNode;
            });
        }
        node.childrenIds = children.map((c) => c.id);
        children.forEach((c) => treeData.nodes.set(c.id, c));
    }
};
const onTreeNodeSelect = (node, treeData) => {
    buildChildrenIfPossible(node, treeData);
    node.isOpen = !node.isOpen;
};
// Result Helpers
export const getAtomicTest_TestResult = (atomicTest, results) => results.get(atomicTest);
const getAssertion_TestResult = (assertion, results) => {
    const test = assertion.parentTest;
    return test ? getAtomicTest_TestResult(test, results) : undefined;
};
export const getAssertionStatus = (assertion, results) => {
    const result = getAssertion_TestResult(assertion, results);
    if (result instanceof TestExecuted &&
        result.testExecutionStatus === TestExecutionStatus.FAIL) {
        return result.assertStatuses.find((s) => s.assertion === assertion);
    }
    else if (result instanceof MultiExecutionServiceTestResult) {
        const testAssertionStatus = new Map();
        Array.from(result.keyIndexedTestResults.entries()).forEach(([key, testResult]) => {
            if (testResult instanceof TestExecuted) {
                const testAssertion = testResult.assertStatuses.find((s) => s.assertion === assertion);
                if (testAssertion) {
                    testAssertionStatus.set(key, testAssertion);
                }
            }
        });
        return testAssertionStatus;
    }
    return undefined;
};
const getTestSuite_TestResults = (suite, results) => suite.tests.map((t) => getAtomicTest_TestResult(t, results));
const getTest_TestResults = (test, results) => {
    if (test instanceof AtomicTest) {
        return [getAtomicTest_TestResult(test, results)];
    }
    else if (test instanceof TestSuite) {
        return getTestSuite_TestResults(test, results);
    }
    return [undefined];
};
const getTestable_TestResult = (test, results) => test.tests.flatMap((t) => getTest_TestResults(t, results));
export var TESTABLE_RESULT;
(function (TESTABLE_RESULT) {
    TESTABLE_RESULT["DID_NOT_RUN"] = "DID_NOT_RUN";
    TESTABLE_RESULT["ERROR"] = "ERROR";
    TESTABLE_RESULT["FAILED"] = "FAILED";
    TESTABLE_RESULT["PASSED"] = "PASSED";
    TESTABLE_RESULT["IN_PROGRESS"] = "IN_PROGRESS";
    TESTABLE_RESULT["NO_TESTS"] = "NO_TESTS";
})(TESTABLE_RESULT || (TESTABLE_RESULT = {}));
export const getTestableResultFromTestResult = (testResult) => {
    if (testResult instanceof TestExecuted &&
        testResult.testExecutionStatus === TestExecutionStatus.PASS) {
        return TESTABLE_RESULT.PASSED;
    }
    else if (testResult instanceof TestExecuted &&
        testResult.testExecutionStatus === TestExecutionStatus.FAIL) {
        return TESTABLE_RESULT.FAILED;
    }
    else if (testResult instanceof TestError) {
        return TESTABLE_RESULT.ERROR;
    }
    else if (testResult instanceof MultiExecutionServiceTestResult) {
        const result = Array.from(testResult.keyIndexedTestResults.values());
        if (result.every((t) => t instanceof TestExecuted &&
            t.testExecutionStatus === TestExecutionStatus.PASS)) {
            return TESTABLE_RESULT.PASSED;
        }
        else if (result.some((t) => t instanceof TestError)) {
            return TESTABLE_RESULT.ERROR;
        }
        return TESTABLE_RESULT.FAILED;
    }
    return TESTABLE_RESULT.DID_NOT_RUN;
};
export const getTestableResultFromAssertionStatus = (assertionStatus) => {
    if (assertionStatus instanceof AssertPass) {
        return TESTABLE_RESULT.PASSED;
    }
    else if (assertionStatus instanceof AssertFail) {
        return TESTABLE_RESULT.FAILED;
    }
    else if (assertionStatus && !(assertionStatus instanceof AssertionStatus)) {
        const assertionStatuses = Array.from(assertionStatus.values());
        if (assertionStatuses.every((t) => t instanceof AssertPass)) {
            return TESTABLE_RESULT.PASSED;
        }
        else {
            return TESTABLE_RESULT.FAILED;
        }
    }
    return TESTABLE_RESULT.DID_NOT_RUN;
};
export const getTestableResultFromTestResults = (testResults) => {
    if (!testResults?.length) {
        return TESTABLE_RESULT.DID_NOT_RUN;
    }
    if (testResults.every((t) => t instanceof TestExecuted &&
        t.testExecutionStatus === TestExecutionStatus.PASS)) {
        return TESTABLE_RESULT.PASSED;
    }
    else if (testResults.find((t) => t instanceof TestError)) {
        return TESTABLE_RESULT.ERROR;
    }
    else if (testResults.find((t) => t instanceof TestExecuted &&
        t.testExecutionStatus === TestExecutionStatus.FAIL)) {
        return TESTABLE_RESULT.FAILED;
    }
    else if (testResults.find((t) => t instanceof MultiExecutionServiceTestResult)) {
        let result = [];
        testResults.forEach((testResult) => {
            if (testResult instanceof MultiExecutionServiceTestResult) {
                result = result.concat(Array.from(testResult.keyIndexedTestResults.values()));
            }
        });
        if (result.every((t) => t instanceof TestExecuted &&
            t.testExecutionStatus === TestExecutionStatus.PASS)) {
            return TESTABLE_RESULT.PASSED;
        }
        else if (result.some((t) => t instanceof TestError)) {
            return TESTABLE_RESULT.ERROR;
        }
        return TESTABLE_RESULT.FAILED;
    }
    return TESTABLE_RESULT.DID_NOT_RUN;
};
export const getNodeTestableResult = (node, globalRun, results) => {
    if (globalRun && node instanceof TestableTreeNodeData) {
        return TESTABLE_RESULT.IN_PROGRESS;
    }
    if ((node instanceof TestTreeNodeData ||
        node instanceof TestableTreeNodeData) &&
        node.isRunning) {
        return TESTABLE_RESULT.IN_PROGRESS;
    }
    if (node instanceof AssertionTestTreeNodeData) {
        const status = getAssertionStatus(node.assertion, results);
        if (status) {
            return getTestableResultFromAssertionStatus(status);
        }
        const result = node.assertion.parentTest
            ? results.get(node.assertion.parentTest)
            : undefined;
        return getTestableResultFromTestResult(result);
    }
    else if (node instanceof AtomicTestTreeNodeData) {
        return getTestableResultFromTestResult(getAtomicTest_TestResult(node.atomicTest, results));
    }
    else if (node instanceof TestSuiteTreeNodeData) {
        return getTestableResultFromTestResults(getTestSuite_TestResults(node.testSuite, results));
    }
    else if (node instanceof TestableTreeNodeData) {
        return getTestableResultFromTestResults(getTestable_TestResult(node.testableMetadata.testable, results));
    }
    return TESTABLE_RESULT.DID_NOT_RUN;
};
export class TestableState {
    uuid = uuid();
    globalTestRunnerState;
    editorStore;
    testableMetadata;
    treeData;
    results = new Map();
    isRunningTests = ActionState.create();
    constructor(editorStore, globalTestRunnerState, testable) {
        makeObservable(this, {
            editorStore: false,
            testableMetadata: observable,
            isRunningTests: observable,
            results: observable,
            treeData: observable.ref,
            handleTestableResult: action,
            setTreeData: action,
            onTreeNodeSelect: action,
            run: flow,
        });
        this.editorStore = editorStore;
        this.globalTestRunnerState = globalTestRunnerState;
        this.testableMetadata = getTestableMetadata(testable, editorStore, this.globalTestRunnerState.extraTestableMetadataGetters);
        this.treeData = this.buildTreeData(this.testableMetadata);
    }
    *run(node) {
        this.isRunningTests.inProgress();
        let input;
        let currentNode = node;
        try {
            if (node instanceof AssertionTestTreeNodeData) {
                const atomicTest = guaranteeNonNullable(node.assertion.parentTest);
                const suite = atomicTest.__parent instanceof TestSuite
                    ? atomicTest.__parent
                    : undefined;
                input = new RunTestsTestableInput(this.testableMetadata.testable);
                input.unitTestIds = [new UniqueTestId(suite, atomicTest)];
                const parentNode = Array.from(this.treeData.nodes.values())
                    .filter(filterByType(AtomicTestTreeNodeData))
                    .find((n) => n.atomicTest === atomicTest);
                if (parentNode) {
                    currentNode = parentNode;
                    parentNode.isRunning = true;
                }
            }
            else if (node instanceof AtomicTestTreeNodeData) {
                const atomicTest = node.atomicTest;
                const suite = atomicTest.__parent instanceof TestSuite
                    ? atomicTest.__parent
                    : undefined;
                input = new RunTestsTestableInput(this.testableMetadata.testable);
                input.unitTestIds = [new UniqueTestId(suite, atomicTest)];
                node.isRunning = true;
            }
            else if (node instanceof TestSuiteTreeNodeData) {
                input = new RunTestsTestableInput(this.testableMetadata.testable);
                input.unitTestIds = node.testSuite.tests.map((s) => new UniqueTestId(node.testSuite, s));
                node.isRunning = true;
            }
            else if (node instanceof TestableTreeNodeData) {
                input = new RunTestsTestableInput(this.testableMetadata.testable);
                node.isRunning = true;
            }
            else {
                throw new UnsupportedOperationError(`Unable to run tests for node ${node}`);
            }
            const testResults = (yield this.editorStore.graphManagerState.graphManager.runTests([input], this.editorStore.graphManagerState.graph));
            this.globalTestRunnerState.handleResults(testResults);
            this.isRunningTests.complete();
        }
        catch (error) {
            assertErrorThrown(error);
            this.editorStore.applicationStore.notificationService.notifyError(error);
            this.isRunningTests.fail();
        }
        finally {
            if (currentNode instanceof TestTreeNodeData ||
                currentNode instanceof TestableTreeNodeData) {
                currentNode.isRunning = false;
            }
        }
    }
    handleTestableResult(testResult, openAssertions) {
        try {
            assertTrue(testResult.testable === this.testableMetadata.testable);
            this.results.set(testResult.atomicTest, testResult);
        }
        catch (error) {
            assertErrorThrown(error);
            this.editorStore.applicationStore.notificationService.notifyError(`Unable to update test result: ${error.message}`);
        }
    }
    buildTreeData(testable) {
        const rootIds = [];
        const nodes = new Map();
        const treeData = { rootIds, nodes };
        const testableTreeNodeData = new TestableTreeNodeData(testable);
        treeData.rootIds.push(testableTreeNodeData.id);
        treeData.nodes.set(testableTreeNodeData.id, testableTreeNodeData);
        return treeData;
    }
    setTreeData(data) {
        this.treeData = data;
    }
    onTreeNodeSelect(node, treeData) {
        onTreeNodeSelect(node, treeData);
        this.setTreeData({ ...treeData });
    }
}
export class GlobalTestRunnerState {
    editorStore;
    sdlcState;
    extraTestableMetadataGetters = [];
    // current project
    isRunningTests = ActionState.create();
    testableStates;
    // dependencies
    showDependencyPanel = false;
    isRunningDependencyTests = ActionState.create();
    dependencyTestableStates;
    // error
    failureViewing;
    constructor(editorStore, sdlcState) {
        makeObservable(this, {
            editorStore: false,
            sdlcState: false,
            testableStates: observable,
            dependencyTestableStates: observable,
            isRunningTests: observable,
            isRunningDependencyTests: observable,
            initOwnTestables: action,
            runAllTests: flow,
            runDependenciesTests: flow,
            failureViewing: observable,
            showDependencyPanel: observable,
            setFailureViewing: action,
            setShowDependencyPanel: action,
            initDependency: action,
            visitTestable: action,
        });
        this.editorStore = editorStore;
        this.sdlcState = sdlcState;
        this.extraTestableMetadataGetters = editorStore.pluginManager
            .getApplicationPlugins()
            .flatMap((plugin) => plugin.getExtraTestableMetadata?.() ?? [])
            .filter(isNonNullable);
        const showDependencyPanelVal = LegendStudioUserDataHelper.globalTestRunner_getShowDependencyPanel(this.editorStore.applicationStore.userDataService);
        if (showDependencyPanelVal !== undefined) {
            this.showDependencyPanel = showDependencyPanelVal;
        }
    }
    get ownTestableStates() {
        return this.testableStates ?? [];
    }
    get allDependencyTestablesStates() {
        return this.dependencyTestableStates ?? [];
    }
    get allTestableStates() {
        return [...this.ownTestableStates, ...this.allDependencyTestablesStates];
    }
    get isDispatchingOwnProjectAction() {
        return (this.isRunningTests.isInProgress ||
            this.ownTestableStates.some((s) => s.isRunningTests.isInProgress));
    }
    get isDispatchingDependencyAction() {
        return (this.isRunningDependencyTests.isInProgress ||
            this.allDependencyTestablesStates.some((s) => s.isRunningTests.isInProgress));
    }
    initOwnTestables(force) {
        if (!this.testableStates || force) {
            const testables = this.editorStore.graphManagerState.graph.ownTestables;
            this.testableStates = testables.map((testable) => new TestableState(this.editorStore, this, testable));
        }
    }
    visitTestable(testable) {
        if (testable instanceof PackageableElement) {
            this.editorStore.graphEditorMode.openElement(testable);
            const currentTab = this.editorStore.tabManagerState.currentTab;
            // TODO: should be abstracted onto a `TestableEditorState`
            if (currentTab instanceof ServiceEditorState) {
                currentTab.openToTestTab();
            }
        }
    }
    setFailureViewing(val) {
        this.failureViewing = val;
    }
    *runAllTests() {
        try {
            this.isRunningTests.inProgress();
            const inputs = this.ownTestableStates.map((e) => new RunTestsTestableInput(e.testableMetadata.testable));
            const testResults = (yield this.editorStore.graphManagerState.graphManager.runTests(inputs, this.editorStore.graphManagerState.graph));
            this.handleResults(testResults);
            this.isRunningTests.complete();
        }
        catch (error) {
            assertErrorThrown(error);
            this.editorStore.applicationStore.notificationService.notifyError(error);
            this.isRunningTests.fail();
        }
    }
    handleResults(testResults) {
        testResults.forEach((testResult) => {
            const testableState = this.allTestableStates.find((tState) => tState.testableMetadata.testable === testResult.testable);
            if (testableState) {
                testableState.handleTestableResult(testResult, true);
            }
        });
    }
    // dependency
    setShowDependencyPanel(val) {
        this.showDependencyPanel = val;
        if (this.showDependencyPanel) {
            this.initDependency();
        }
        LegendStudioUserDataHelper.globalTestRunner_setShowDependencyPanel(this.editorStore.applicationStore.userDataService, val);
    }
    initDependency() {
        if (!this.dependencyTestableStates) {
            this.dependencyTestableStates =
                this.editorStore.graphManagerState.graph.dependencyManager.testables.map((testable) => new TestableState(this.editorStore, this, testable));
        }
    }
    *runDependenciesTests() {
        try {
            this.isRunningDependencyTests.inProgress();
            const inputs = this.allDependencyTestablesStates.map((e) => new RunTestsTestableInput(e.testableMetadata.testable));
            const testResults = (yield this.editorStore.graphManagerState.graphManager.runTests(inputs, this.editorStore.graphManagerState.graph));
            this.handleResults(testResults);
            this.isRunningDependencyTests.complete();
        }
        catch (error) {
            assertErrorThrown(error);
            this.editorStore.applicationStore.notificationService.notifyError(error);
            this.isRunningDependencyTests.fail();
        }
    }
}
//# sourceMappingURL=GlobalTestRunnerState.js.map