/*
Copyright 2026 Element Creations Ltd.
Copyright 2024 New Vector Ltd.
Copyright 2023 The Matrix.org Foundation C.I.C.

SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/

import { type SettingLevel } from "../SettingLevel";
import MatrixClientBackedController from "./MatrixClientBackedController";
import { type WatchManager } from "../WatchManager";
import { type SettingKey } from "../Settings.tsx";
import { _t } from "../../languageHandler";
import PlatformPeg from "../../PlatformPeg.ts";

/**
 * Disables a given setting if the server unstable feature it requires is not supported
 * When a setting gets disabled or enabled from this controller it notifies the given WatchManager
 */
export default class ServerSupportUnstableFeatureController extends MatrixClientBackedController {
    // Starts off as `undefined` so when we first compare the `newDisabledValue`, it sees
    // it as a change and updates the watchers.
    private enabled: boolean | undefined;

    /**
     * Construct a new ServerSupportUnstableFeatureController.
     *
     * @param unstableFeatureGroups - If any one of the feature groups is satisfied,
     * then the setting is considered enabled. A feature group is satisfied if all of
     * the features in the group are supported (all features in a group are required).
     * @param defaultEnabled - If we haven't been able to check for support yet, should
     *  this feature be enabled or disabled (default).
     * @param forceReload - Should the client force reload.
     */
    public constructor(
        private readonly settingName: SettingKey,
        private readonly watchers: WatchManager,
        private readonly unstableFeatureGroups: string[][],
        private readonly stableVersion?: string,
        private readonly disabledMessage?: TranslationKey,
        private readonly forcedValue: any = false,
        private readonly defaultEnabled = false,
        private readonly forceReload = false,
    ) {
        super();
    }

    public onChange(): void {
        if (this.forceReload) {
            PlatformPeg.get()?.reload();
        }
    }

    public get disabled(): boolean {
        if (this.enabled !== undefined) {
            return !this.enabled;
        }
        return !this.defaultEnabled;
    }

    public set disabled(newDisabledValue: boolean) {
        if (!newDisabledValue === this.enabled) return;
        this.enabled = !newDisabledValue;
        const level = this.settingsStore.firstSupportedLevel(this.settingName);
        if (!level) return;
        const settingValue = this.settingsStore.getValue(this.settingName, null);
        this.watchers.notifyUpdate(this.settingName, null, level, settingValue);
    }

    protected async initMatrixClient(): Promise<void> {
        // Check for stable version support first
        if (this.stableVersion && (await this.client!.isVersionSupported(this.stableVersion))) {
            this.disabled = false;
            return;
        }

        // Otherwise, only one of the unstable feature groups needs to be satisfied in
        // order for this setting overall to be enabled
        let isEnabled = false;
        for (const featureGroup of this.unstableFeatureGroups) {
            const featureSupportList = await Promise.all(
                featureGroup.map(async (feature) => {
                    const isFeatureSupported = await this.client!.doesServerSupportUnstableFeature(feature);
                    return isFeatureSupported;
                }),
            );

            // Every feature in a feature group is required in order
            // for this setting overall to be enabled.
            const isFeatureGroupSatisfied = featureSupportList.every((isFeatureSupported) => isFeatureSupported);
            if (isFeatureGroupSatisfied) {
                isEnabled = true;
                break;
            }
        }

        this.disabled = !isEnabled;
    }

    public getValueOverride(
        level: SettingLevel,
        roomId: string | null,
        calculatedValue: any,
        calculatedAtLevel: SettingLevel | null,
    ): any {
        if (this.settingDisabled) {
            return this.forcedValue;
        }
        return null; // no override
    }

    public get settingDisabled(): boolean | string {
        if (this.disabled) {
            return this.disabledMessage ? _t(this.disabledMessage) : true;
        }
        return false;
    }
}
