/*
Copyright 2022 Michael Telatynski <7t3chguy@gmail.com>
Copyright 2018-2023 The Matrix.org Foundation C.I.C.

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 React, { ChangeEvent, useContext, useMemo, useState } from "react";
import { logger } from "matrix-js-sdk/src/logger";

import { _t } from "../../../../languageHandler";
import BaseTool, { DevtoolsContext, IDevtoolsProps } from "./BaseTool";
import AccessibleButton from "../../elements/AccessibleButton";
import SettingsStore, { LEVEL_ORDER } from "../../../../settings/SettingsStore";
import { SettingLevel } from "../../../../settings/SettingLevel";
import { SETTINGS } from "../../../../settings/Settings";
import Field from "../../elements/Field";

const SettingExplorer: React.FC<IDevtoolsProps> = ({ onBack }) => {
    const [setting, setSetting] = useState<string | null>(null);
    const [editing, setEditing] = useState(false);

    if (setting && editing) {
        const onBack = (): void => {
            setEditing(false);
        };
        return <EditSetting setting={setting} onBack={onBack} />;
    } else if (setting) {
        const onBack = (): void => {
            setSetting(null);
        };
        const onEdit = async (): Promise<void> => {
            setEditing(true);
        };
        return <ViewSetting setting={setting} onBack={onBack} onEdit={onEdit} />;
    } else {
        const onView = (setting: string): void => {
            setSetting(setting);
        };
        const onEdit = (setting: string): void => {
            setSetting(setting);
            setEditing(true);
        };
        return <SettingsList onBack={onBack} onView={onView} onEdit={onEdit} />;
    }
};

export default SettingExplorer;

interface ICanEditLevelFieldProps {
    setting: string;
    level: SettingLevel;
    roomId?: string;
}

const CanEditLevelField: React.FC<ICanEditLevelFieldProps> = ({ setting, roomId, level }) => {
    const canEdit = SettingsStore.canSetValue(setting, roomId ?? null, level);
    const className = canEdit ? "mx_DevTools_SettingsExplorer_mutable" : "mx_DevTools_SettingsExplorer_immutable";
    return (
        <td className={className}>
            <code>{canEdit.toString()}</code>
        </td>
    );
};

function renderExplicitSettingValues(setting: string, roomId?: string): string {
    const vals: Record<string, number | null> = {};
    for (const level of LEVEL_ORDER) {
        try {
            vals[level] = SettingsStore.getValueAt(level, setting, roomId, true, true);
            if (vals[level] === undefined) {
                vals[level] = null;
            }
        } catch (e) {
            logger.warn(e);
        }
    }
    return JSON.stringify(vals, null, 4);
}

interface IEditSettingProps extends Pick<IDevtoolsProps, "onBack"> {
    setting: string;
}

const EditSetting: React.FC<IEditSettingProps> = ({ setting, onBack }) => {
    const context = useContext(DevtoolsContext);
    const [explicitValue, setExplicitValue] = useState(renderExplicitSettingValues(setting));
    const [explicitRoomValue, setExplicitRoomValue] = useState(
        renderExplicitSettingValues(setting, context.room.roomId),
    );

    const onSave = async (): Promise<string | undefined> => {
        try {
            const parsedExplicit = JSON.parse(explicitValue);
            const parsedExplicitRoom = JSON.parse(explicitRoomValue);
            for (const level of Object.keys(parsedExplicit)) {
                logger.log(`[Devtools] Setting value of ${setting} at ${level} from user input`);
                try {
                    const val = parsedExplicit[level];
                    await SettingsStore.setValue(setting, null, level as SettingLevel, val);
                } catch (e) {
                    logger.warn(e);
                }
            }

            const roomId = context.room.roomId;
            for (const level of Object.keys(parsedExplicit)) {
                logger.log(`[Devtools] Setting value of ${setting} at ${level} in ${roomId} from user input`);
                try {
                    const val = parsedExplicitRoom[level];
                    await SettingsStore.setValue(setting, roomId, level as SettingLevel, val);
                } catch (e) {
                    logger.warn(e);
                }
            }
            onBack();
        } catch (e) {
            return _t("devtools|failed_to_save") + (e instanceof Error ? ` (${e.message})` : "");
        }
    };

    return (
        <BaseTool onBack={onBack} actionLabel={_t("devtools|save_setting_values")} onAction={onSave}>
            <h3>
                {_t("devtools|setting_colon")} <code>{setting}</code>
            </h3>

            <div className="mx_DevTools_SettingsExplorer_warning">
                <b>{_t("devtools|caution_colon")}</b> {_t("devtools|use_at_own_risk")}
            </div>

            <div>
                {_t("devtools|setting_definition")}
                <pre>
                    <code>{JSON.stringify(SETTINGS[setting], null, 4)}</code>
                </pre>
            </div>

            <div>
                <table>
                    <thead>
                        <tr>
                            <th>{_t("devtools|level")}</th>
                            <th>{_t("devtools|settable_global")}</th>
                            <th>{_t("devtools|settable_room")}</th>
                        </tr>
                    </thead>
                    <tbody>
                        {LEVEL_ORDER.map((lvl) => (
                            <tr key={lvl}>
                                <td>
                                    <code>{lvl}</code>
                                </td>
                                <CanEditLevelField setting={setting} level={lvl} />
                                <CanEditLevelField setting={setting} roomId={context.room.roomId} level={lvl} />
                            </tr>
                        ))}
                    </tbody>
                </table>
            </div>

            <div>
                <Field
                    id="valExpl"
                    label={_t("devtools|values_explicit")}
                    type="text"
                    className="mx_DevTools_textarea"
                    element="textarea"
                    autoComplete="off"
                    value={explicitValue}
                    onChange={(e) => setExplicitValue(e.target.value)}
                />
            </div>

            <div>
                <Field
                    id="valExpl"
                    label={_t("devtools|values_explicit_room")}
                    type="text"
                    className="mx_DevTools_textarea"
                    element="textarea"
                    autoComplete="off"
                    value={explicitRoomValue}
                    onChange={(e) => setExplicitRoomValue(e.target.value)}
                />
            </div>
        </BaseTool>
    );
};

interface IViewSettingProps extends Pick<IDevtoolsProps, "onBack"> {
    setting: string;
    onEdit(): Promise<void>;
}

const ViewSetting: React.FC<IViewSettingProps> = ({ setting, onEdit, onBack }) => {
    const context = useContext(DevtoolsContext);

    return (
        <BaseTool onBack={onBack} actionLabel={_t("devtools|edit_values")} onAction={onEdit}>
            <h3>
                {_t("devtools|setting_colon")} <code>{setting}</code>
            </h3>

            <div>
                {_t("devtools|setting_definition")}
                <pre>
                    <code>{JSON.stringify(SETTINGS[setting], null, 4)}</code>
                </pre>
            </div>

            <div>
                {_t("devtools|value_colon")}&nbsp;
                <code>{renderSettingValue(SettingsStore.getValue(setting))}</code>
            </div>

            <div>
                {_t("devtools|value_this_room_colon")}&nbsp;
                <code>{renderSettingValue(SettingsStore.getValue(setting, context.room.roomId))}</code>
            </div>

            <div>
                {_t("devtools|values_explicit_colon")}
                <pre>
                    <code>{renderExplicitSettingValues(setting)}</code>
                </pre>
            </div>

            <div>
                {_t("devtools|values_explicit_this_room_colon")}
                <pre>
                    <code>{renderExplicitSettingValues(setting, context.room.roomId)}</code>
                </pre>
            </div>
        </BaseTool>
    );
};

function renderSettingValue(val: any): string {
    // Note: we don't .toString() a string because we want JSON.stringify to inject quotes for us
    const toStringTypes = ["boolean", "number"];
    if (toStringTypes.includes(typeof val)) {
        return val.toString();
    } else {
        return JSON.stringify(val);
    }
}

interface ISettingsListProps extends Pick<IDevtoolsProps, "onBack"> {
    onView(setting: string): void;
    onEdit(setting: string): void;
}

const SettingsList: React.FC<ISettingsListProps> = ({ onBack, onView, onEdit }) => {
    const context = useContext(DevtoolsContext);
    const [query, setQuery] = useState("");

    const allSettings = useMemo(() => {
        let allSettings = Object.keys(SETTINGS);
        if (query) {
            const lcQuery = query.toLowerCase();
            allSettings = allSettings.filter((setting) => setting.toLowerCase().includes(lcQuery));
        }
        return allSettings;
    }, [query]);

    return (
        <BaseTool onBack={onBack} className="mx_DevTools_SettingsExplorer">
            <Field
                label={_t("Filter results")}
                autoFocus={true}
                size={64}
                type="text"
                autoComplete="off"
                value={query}
                onChange={(ev: ChangeEvent<HTMLInputElement>) => setQuery(ev.target.value)}
                className="mx_TextInputDialog_input mx_DevTools_RoomStateExplorer_query"
            />
            <table>
                <thead>
                    <tr>
                        <th>{_t("devtools|setting_id")}</th>
                        <th>{_t("devtools|value")}</th>
                        <th>{_t("devtools|value_in_this_room")}</th>
                    </tr>
                </thead>
                <tbody>
                    {allSettings.map((i) => (
                        <tr key={i}>
                            <td>
                                <AccessibleButton
                                    kind="link_inline"
                                    className="mx_DevTools_SettingsExplorer_setting"
                                    onClick={() => onView(i)}
                                >
                                    <code>{i}</code>
                                </AccessibleButton>
                                <AccessibleButton
                                    alt={_t("devtools|edit_setting")}
                                    onClick={() => onEdit(i)}
                                    className="mx_DevTools_SettingsExplorer_edit"
                                >
                                    ✏
                                </AccessibleButton>
                            </td>
                            <td>
                                <code>{renderSettingValue(SettingsStore.getValue(i))}</code>
                            </td>
                            <td>
                                <code>{renderSettingValue(SettingsStore.getValue(i, context.room.roomId))}</code>
                            </td>
                        </tr>
                    ))}
                </tbody>
            </table>
        </BaseTool>
    );
};
