/*
Copyright 2024 New Vector Ltd.
Copyright 2019 The Matrix.org Foundation C.I.C.
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 React, { type ChangeEvent } from "react";
import { secureRandomString } from "matrix-js-sdk/src/randomstring";
import { SettingsToggleInput } from "@vector-im/compound-web";
import { logger } from "matrix-js-sdk/src/logger";

import SettingsStore from "../../../settings/SettingsStore";
import { _t } from "../../../languageHandler";
import { type SettingLevel } from "../../../settings/SettingLevel";
import { type NullableBooleanSettingKey, defaultWatchManager } from "../../../settings/Settings";

interface IProps {
    // The setting must be a boolean
    name: NullableBooleanSettingKey;
    level: SettingLevel;
    roomId?: string; // for per-room settings
    label?: string;
    isExplicit?: boolean;
    hideIfCannotSet?: boolean;
    requires?: NullableBooleanSettingKey[];
    onChange?(checked: boolean): void;
}

interface IState {
    value: boolean;
}

export default class SettingsFlag extends React.Component<IProps, IState> {
    private readonly id = `mx_SettingsFlag_${secureRandomString(12)}`;

    public constructor(props: IProps) {
        super(props);

        this.state = {
            value: this.getSettingValue(),
        };
    }

    public componentDidMount(): void {
        defaultWatchManager.watchSetting(this.props.name, this.props.roomId ?? null, this.onSettingChange);
        if (this.props.requires) {
            // If we have any dependencies for this feature, also watch those features to ensure we catch the disabled state.
            for (const flag of this.props.requires) {
                defaultWatchManager.watchSetting(flag, this.props.roomId ?? null, this.onSettingChange);
            }
        }
    }

    public componentWillUnmount(): void {
        defaultWatchManager.unwatchSetting(this.onSettingChange);
    }

    private getSettingValue(): boolean {
        // If a level defined in props is overridden by a level at a high precedence,
        // or the setting lacks support for the desired level, it gets disabled and we should show the overriding value.
        if (
            !SettingsStore.doesSettingSupportLevel(this.props.name, this.props.level) ||
            SettingsStore.settingIsOveriddenAtConfigLevel(this.props.name, this.props.roomId ?? null, this.props.level)
        ) {
            return !!SettingsStore.getValue(this.props.name);
        }
        return !!SettingsStore.getValueAt(
            this.props.level,
            this.props.name,
            this.props.roomId ?? null,
            this.props.isExplicit,
        );
    }
    private onSettingChange = (): void => {
        this.setState({
            value: this.getSettingValue(),
        });
    };

    private onChange = async (evt: ChangeEvent<HTMLInputElement>): Promise<void> => {
        const newValue = evt.target.checked;
        try {
            await this.save(newValue);
        } catch (ex) {
            logger.info(`Failed to save setting ${this.props.name}`, ex);
            return;
        }
        this.setState({ value: newValue });
        this.props.onChange?.(newValue);
    };

    private save = async (val?: boolean): Promise<void> => {
        await SettingsStore.setValue(
            this.props.name,
            this.props.roomId ?? null,
            this.props.level,
            val !== undefined ? val : this.state.value,
        );
    };

    public render(): React.ReactNode {
        const disabled = !SettingsStore.canSetValue(this.props.name, this.props.roomId ?? null, this.props.level);

        if (disabled && this.props.hideIfCannotSet) return null;

        const label = this.props.label ?? SettingsStore.getDisplayName(this.props.name, this.props.level);
        const description = SettingsStore.getDescription(this.props.name);
        const shouldWarn = SettingsStore.shouldHaveWarning(this.props.name);
        const helpMessage = shouldWarn
            ? _t(
                  "settings|warning",
                  {},
                  {
                      w: (sub) => <span className="mx_SettingsTab_microcopy_warning">{sub}</span>,
                      description,
                  },
              )
            : description;
        const disabledMessage = SettingsStore.disabledMessage(this.props.name);
        return (
            <SettingsToggleInput
                id={this.id}
                checked={this.state.value}
                onChange={disabledMessage ? undefined : this.onChange}
                name={this.props.name}
                disabled={disabled}
                label={label ?? this.props.name}
                helpMessage={helpMessage as string}
                disabledMessage={disabledMessage}
            />
        );
    }
}
