/*
Copyright 2024 New Vector Ltd.
Copyright 2019-2022 The Matrix.org Foundation C.I.C.
Copyright 2019 New Vector Ltd.
Copyright 2017 Travis Ralston

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 { SettingLevel } from "../SettingLevel";
import { type CallbackFn, type WatchManager } from "../WatchManager";
import AbstractLocalStorageSettingsHandler from "./AbstractLocalStorageSettingsHandler";

/**
 * Gets and sets settings at the "device" level for the current device.
 * This handler does not make use of the roomId parameter. This handler
 * will special-case features to support legacy settings.
 */
export default class DeviceSettingsHandler extends AbstractLocalStorageSettingsHandler {
    /**
     * Creates a new device settings handler
     * @param {string[]} featureNames The names of known features.
     * @param {WatchManager} watchers The watch manager to notify updates to
     */
    public constructor(
        private featureNames: string[],
        public readonly watchers: WatchManager,
    ) {
        super();
    }

    public getValue(settingName: string, roomId: string): any {
        if (this.featureNames.includes(settingName)) {
            return this.readFeature(settingName);
        }

        // Special case notifications
        if (settingName === "notificationsEnabled") {
            return this.getBoolean("notifications_enabled");
        } else if (settingName === "notificationBodyEnabled") {
            return this.getBoolean("notifications_body_enabled");
        } else if (settingName === "audioNotificationsEnabled") {
            return this.getBoolean("audio_notifications_enabled");
        }

        const settings = this.getSettings() || {};
        return settings[settingName];
    }

    public setValue(settingName: string, roomId: string, newValue: any): Promise<void> {
        if (this.featureNames.includes(settingName)) {
            this.writeFeature(settingName, newValue);
            return Promise.resolve();
        }

        // Special case notifications
        if (settingName === "notificationsEnabled") {
            this.setBoolean("notifications_enabled", newValue);
            this.watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);
            return Promise.resolve();
        } else if (settingName === "notificationBodyEnabled") {
            this.setBoolean("notifications_body_enabled", newValue);
            this.watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);
            return Promise.resolve();
        } else if (settingName === "audioNotificationsEnabled") {
            this.setBoolean("audio_notifications_enabled", newValue);
            this.watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);
            return Promise.resolve();
        }

        // Special case for old useIRCLayout setting
        if (settingName === "layout") {
            const settings = this.getSettings() || {};

            delete settings["useIRCLayout"];
            settings["layout"] = newValue;
            this.setObject("mx_local_settings", settings);

            this.watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);
            return Promise.resolve();
        }

        const settings = this.getSettings() || {};
        settings[settingName] = newValue;
        this.setObject("mx_local_settings", settings);
        this.watchers.notifyUpdate(settingName, null, SettingLevel.DEVICE, newValue);

        return Promise.resolve();
    }

    public canSetValue(settingName: string, roomId: string): boolean {
        return true; // It's their device, so they should be able to
    }

    public watchSetting(settingName: string, roomId: string, cb: CallbackFn): void {
        this.watchers.watchSetting(settingName, roomId, cb);
    }

    public unwatchSetting(cb: CallbackFn): void {
        this.watchers.unwatchSetting(cb);
    }

    private getSettings(): any {
        // TODO: [TS] Type return
        return this.getObject("mx_local_settings");
    }

    // Note: features intentionally don't use the same key as settings to avoid conflicts
    // and to be backwards compatible.

    // public for access to migrations - not exposed from the SettingsHandler interface
    public readFeature(featureName: string): boolean | null {
        // Previously, we disabled all features for guests, but since different
        // installations can have site-specific config files which might set up
        // different behaviour that is relevant to guests, we removed that
        // special behaviour. See
        // https://github.com/vector-im/element-web/issues/24513 for the
        // discussion.

        // XXX: This turns they key names into `mx_labs_feature_feature_x` (double feature).
        // This is because all feature names start with `feature_` as a matter of policy.
        // Oh well.
        return this.getBoolean("mx_labs_feature_" + featureName);
    }

    private writeFeature(featureName: string, enabled: boolean | null): void {
        this.setBoolean("mx_labs_feature_" + featureName, enabled);
        this.watchers.notifyUpdate(featureName, null, SettingLevel.DEVICE, enabled);
    }
}
