/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */

import { ImplementationError, MaybePromise } from "@matter/general";
import { OperationalState } from "@matter/types/clusters/operational-state";
import { OvenCavityOperationalStateBehavior } from "./OvenCavityOperationalStateBehavior.js";

/**
 * This is the default server implementation of {@link OvenCavityOperationalStateBehavior}.
 *
 * The default implementation is currently not handling all Q-Requirements of the optional CountdownTime attribute,
 * so this needs to be adjusted and implemented device specific if the attribute is used.
 *
 * The default implementation will send the error event and sets the device into the error state when the operational
 * error attribute is set to a value other than NoError. The error state will be cleared when the operational state
 * is set to a value other than Error.
 */
export class OvenCavityOperationalStateServer extends OvenCavityOperationalStateBehavior {
    override initialize(): MaybePromise {
        this.#assertOperationalStateList();
        this.#assertPhaseList();
        this.#syncCurrentPhaseWithPhaseList();

        this.reactTo(this.events.operationalState$Changing, this.#assertOperationalState);

        this.reactTo(this.events.phaseList$Changed, this.#syncCurrentPhaseWithPhaseList);
        this.reactTo(this.events.currentPhase$Changing, this.#assertCurrentPhase);

        this.reactTo(this.events.operationalError$Changed, this.#handleOperationalError);
    }

    #assertPhaseList() {
        const allowedPhases = ["pre-heating", "pre-heated", "cooling down"];
        if (this.state.phaseList !== null) {
            for (const phase of this.state.phaseList) {
                if (!allowedPhases.includes(phase)) {
                    throw new ImplementationError(
                        `Phase ${phase} is not allowed. Allowed phases are: ${allowedPhases.join(", ")}`,
                    );
                }
            }
        }
    }

    #assertOperationalStateList() {
        if (
            !this.state.operationalStateList.some(
                ({ operationalStateId }) => operationalStateId === OperationalState.OperationalStateEnum.Error,
            )
        ) {
            throw new ImplementationError(`Operational state list must at least contain an error entry.`);
        }
    }

    #syncCurrentPhaseWithPhaseList() {
        if (this.state.phaseList === null || this.state.phaseList.length === 0) {
            this.state.currentPhase = null;
        }
    }

    #assertCurrentPhase(currentPhase: number | null) {
        if (this.state.phaseList === null || this.state.phaseList.length === 0) {
            if (currentPhase === null) {
                return;
            }
            throw new ImplementationError(
                "Cannot set current phase to an other value than null when phase list is empty",
            );
        }

        if (currentPhase === null || currentPhase < 0 || currentPhase >= this.state.phaseList.length) {
            throw new ImplementationError(
                `Current phase ${currentPhase} is out of bounds for phase list of length ${this.state.phaseList.length}`,
            );
        }
    }

    #assertOperationalState(newState: number, oldState: number) {
        if (!this.state.operationalStateList.some(({ operationalStateId }) => operationalStateId === newState)) {
            throw new ImplementationError(
                `Cannot set operational state id ${newState} as it is not in the operational state list`,
            );
        }
        if (
            oldState === OperationalState.OperationalStateEnum.Error &&
            this.state.operationalError.errorStateId !== OperationalState.ErrorState.NoError &&
            newState !== OperationalState.OperationalStateEnum.Error
        ) {
            this.state.operationalError = { errorStateId: OperationalState.ErrorState.NoError };
        }
    }

    #handleOperationalError(operationalError: OperationalState.ErrorStateStruct) {
        if (operationalError.errorStateId === OperationalState.ErrorState.NoError) {
            return;
        }

        if (this.state.operationalState !== OperationalState.OperationalStateEnum.Error) {
            this.state.operationalState = OperationalState.OperationalStateEnum.Error;
        }

        this.events.operationalError.emit({ errorState: operationalError }, this.context);
    }
}
