/**
 * Copyright (C) 2021 Thomas Weber
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

import Search from './search';
import importedAddons from '../generated/addon-manifests';
import messagesByLocale from '../generated/l10n-settings-entries';
import settingsTranslationsEnglish from './en.json';
import settingsTranslationsOther from './translations.json';
import upstreamMeta from '../generated/upstream-meta.json';
import { detectLocale } from '../../lib/detect-locale';
import { getInitialDarkMode } from '../../lib/tw-theme-hoc.jsx';
import SettingsStore from '../settings-store-singleton';
import Channels from '../channels';
import extensionImage from './icons/extension.svg';
import brushImage from './icons/brush.svg';
import undoImage from './icons/undo.svg';
import expandImageBlack from './icons/expand.svg';
import infoImage from './icons/info.svg';
import styles from './settings.css';
import '../polyfill';
import '../../lib/normalize.css';

/* eslint-disable no-alert */
/* eslint-disable no-console */
/* eslint-disable react/no-multi-comp */
/* eslint-disable react/jsx-no-bind */

const locale = detectLocale(Object.keys(messagesByLocale));
document.documentElement.lang = locale;

const addonTranslations = messagesByLocale[locale] ? messagesByLocale[locale]() : {};

const settingsTranslations = settingsTranslationsEnglish;
if (locale !== 'en') {
    const messages = settingsTranslationsOther[locale] || settingsTranslationsOther[locale.split('-')[0]];
    if (messages) {
        Object.assign(settingsTranslations, messages);
    }
}

document.title = `${settingsTranslations.title} - PenguinMod`;

const theme = getInitialDarkMode() ? 'dark' : 'light';
document.body.setAttribute('theme', theme);

let _throttleTimeout;
const postThrottledSettingsChange = store => {
    if (_throttleTimeout) {
        clearTimeout(_throttleTimeout);
    }
    _throttleTimeout = setTimeout(() => {
        Channels.changeChannel.postMessage({
            version: upstreamMeta.commit,
            store
        });
    }, 100);
};

const filterAddonsBySupport = () => {
    const supported = {};
    const unsupported = {};
    for (const [id, manifest] of Object.entries(importedAddons)) {
        if (manifest.unsupported) {
            unsupported[id] = manifest;
        } else {
            supported[id] = manifest;
        }
    }
    return {
        supported,
        unsupported
    };
};
const { supported: supportedAddons, unsupported: unsupportedAddons } = filterAddonsBySupport();

const groupAddons = () => {
    const groups = {
        new: {
            label: settingsTranslations.groupNew,
            open: true,
            addons: []
        },
        others: {
            label: settingsTranslations.groupOthers,
            open: true,
            addons: []
        },
        danger: {
            label: settingsTranslations.groupDanger,
            open: false,
            addons: []
        }
    };
    const manifests = Object.values(supportedAddons);
    for (let index = 0; index < manifests.length; index++) {
        const manifest = manifests[index];
        if (manifest.tags.includes('new')) {
            groups.new.addons.push(index);
        } else if (manifest.tags.includes('danger') || manifest.noCompiler) {
            groups.danger.addons.push(index);
        } else {
            groups.others.addons.push(index);
        }
    }
    return groups;
};
const groupedAddons = groupAddons();

const CreditList = ({ credits }) => (
    credits.map((author, index) => {
        const isLast = index === credits.length - 1;
        return (
            <span
                className={styles.credit}
                key={index}
            >
                {author.link ? (
                    <a
                        href={author.link}
                        target="_blank"
                        rel="noreferrer"
                    >
                        {author.name}
                    </a>
                ) : (
                    <span>
                        {author.name}
                    </span>
                )}
                {isLast ? null : ', '}
            </span>
        );
    })
);
CreditList.propTypes = {
    credits: PropTypes.arrayOf(PropTypes.shape({
        name: PropTypes.string,
        link: PropTypes.string
    }))
};

const Switch = ({ onChange, value, ...props }) => (
    <button
        className={styles.switch}
        state={value ? 'on' : 'off'}
        role="checkbox"
        aria-checked={value ? 'true' : 'false'}
        tabIndex="0"
        onClick={() => onChange(!value)}
        {...props}
    />
);
Switch.propTypes = {
    onChange: PropTypes.func,
    value: PropTypes.bool
};

const Select = ({
    onChange,
    value,
    values
}) => (
    <div className={styles.select}>
        {values.map(potentialValue => {
            const id = potentialValue.id;
            const selected = id === value;
            return (
                <button
                    key={id}
                    onClick={() => onChange(id)}
                    className={classNames(styles.selectOption, { [styles.selected]: selected })}
                >
                    {potentialValue.name}
                </button>
            );
        })}
    </div>
);
Select.propTypes = {
    onChange: PropTypes.func,
    value: PropTypes.string,
    values: PropTypes.arrayOf(PropTypes.shape({
        id: PropTypes.string,
        name: PropTypes.string
    }))
};

const Tags = ({ manifest }) => (
    <span className={styles.tagContainer}>
        {manifest.tags.includes('recommended') && (
            <span className={classNames(styles.tag, styles.tagRecommended)}>
                {settingsTranslations.tagRecommended}
            </span>
        )}
        {manifest.tags.includes('theme') && (
            <span className={classNames(styles.tag, styles.tagTheme)}>
                {settingsTranslations.tagTheme}
            </span>
        )}
        {manifest.tags.includes('beta') && (
            <span className={classNames(styles.tag, styles.tagBeta)}>
                {settingsTranslations.tagBeta}
            </span>
        )}
        {manifest.tags.includes('new') && (
            <span className={classNames(styles.tag, styles.tagNew)}>
                {settingsTranslations.tagNew}
            </span>
        )}
        {manifest.tags.includes('danger') && (
            <span className={classNames(styles.tag, styles.tagDanger)}>
                {settingsTranslations.tagDanger}
            </span>
        )}
    </span>
);
Tags.propTypes = {
    manifest: PropTypes.shape({
        tags: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired
    }).isRequired
};

class TextInput extends React.Component {
    constructor(props) {
        super(props);
        this.handleKeyPress = this.handleKeyPress.bind(this);
        this.handleFocus = this.handleFocus.bind(this);
        this.handleFlush = this.handleFlush.bind(this);
        this.handleChange = this.handleChange.bind(this);
        this.state = {
            value: null,
            focused: false
        };
    }
    handleKeyPress(e) {
        if (e.key === 'Enter') {
            this.handleFlush(e);
            e.target.blur();
        }
    }
    handleFocus() {
        this.setState({
            focused: true
        });
    }
    handleFlush(e) {
        this.setState({
            focused: false
        });
        if (this.state.value === null) {
            return;
        }
        if (this.props.type === 'number') {
            let value = +this.state.value;
            const min = e.target.min;
            const max = e.target.max;
            const step = e.target.step;
            if (min !== '') value = Math.max(min, value);
            if (max !== '') value = Math.min(max, value);
            if (step === '1') value = Math.round(value);
            this.props.onChange(value);
        } else {
            this.props.onChange(this.state.value);
        }
        this.setState({ value: null });
    }
    handleChange(e) {
        e.persist();
        this.setState({ value: e.target.value }, () => {
            // A change event can be fired when not focused by using the browser's number spinners
            if (!this.state.focused) {
                this.handleFlush(e);
            }
        });
    }
    render() {
        return (
            <input
                {...this.props}
                value={this.state.value === null ? this.props.value : this.state.value}
                onFocus={this.handleFocus}
                onBlur={this.handleFlush}
                onChange={this.handleChange}
                onKeyPress={this.handleKeyPress}
            />
        );
    }
}
TextInput.propTypes = {
    onChange: PropTypes.func.isRequired,
    type: PropTypes.string,
    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
};

const ColorInput = props => (
    <input
        type="color"
        id={props.id}
        value={props.value}
        onChange={props.onChange}
    />
);
ColorInput.propTypes = {
    id: PropTypes.string.isRequired,
    onChange: PropTypes.func.isRequired,
    value: PropTypes.string.isRequired
};

const ResetButton = ({
    addonId,
    settingId,
    forTextInput
}) => (
    <button
        className={classNames(styles.button, styles.resetSettingButton)}
        onClick={() => SettingsStore.setAddonSetting(addonId, settingId, null)}
        title={settingsTranslations.reset}
        data-for-text-input={forTextInput}
    >
        <img
            src={undoImage}
            alt={settingsTranslations.reset}
        />
    </button>
);
ResetButton.propTypes = {
    addonId: PropTypes.string,
    settingId: PropTypes.string,
    forTextInput: PropTypes.bool
};

const Setting = ({
    addonId,
    setting,
    value
}) => {
    if (!SettingsStore.evaluateCondition(addonId, setting.if)) {
        return null;
    }
    const settingId = setting.id;
    const settingName = addonTranslations[`${addonId}/@settings-name-${settingId}`] || setting.name;
    const uniqueId = `setting/${addonId}/${settingId}`;
    const label = (
        <label
            htmlFor={uniqueId}
            className={styles.settingLabel}
        >
            {settingName}
        </label>
    );
    return (
        <div
            className={styles.setting}
        >
            {setting.type === 'boolean' && (
                <React.Fragment>
                    {label}
                    <input
                        id={uniqueId}
                        type="checkbox"
                        checked={value}
                        onChange={e => SettingsStore.setAddonSetting(addonId, settingId, e.target.checked)}
                    />
                </React.Fragment>
            )}
            {setting.type === 'integer' && (
                <React.Fragment>
                    {label}
                    <TextInput
                        id={uniqueId}
                        type="number"
                        min={setting.min}
                        max={setting.max}
                        step="1"
                        value={value}
                        onChange={newValue => SettingsStore.setAddonSetting(addonId, settingId, newValue)}
                    />
                    <ResetButton
                        addonId={addonId}
                        settingId={settingId}
                        forTextInput
                    />
                </React.Fragment>
            )}
            {setting.type === 'color' && (
                <React.Fragment>
                    {label}
                    <ColorInput
                        id={uniqueId}
                        value={value}
                        onChange={e => SettingsStore.setAddonSetting(addonId, settingId, e.target.value)}
                    />
                    <ResetButton
                        addonId={addonId}
                        settingId={settingId}
                    />
                </React.Fragment>
            )}
            {setting.type === 'select' && (
                <React.Fragment>
                    {label}
                    <Select
                        value={value}
                        values={setting.potentialValues.map(({ id, name }) => ({
                            id,
                            name: addonTranslations[`${addonId}/@settings-select-${settingId}-${id}`] || name
                        }))}
                        onChange={v => SettingsStore.setAddonSetting(addonId, settingId, v)}
                        setting={setting}
                    />
                </React.Fragment>
            )}
        </div>
    );
};
Setting.propTypes = {
    addonId: PropTypes.string,
    setting: PropTypes.shape({
        type: PropTypes.string,
        id: PropTypes.string,
        name: PropTypes.string,
        min: PropTypes.number,
        max: PropTypes.number,
        default: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
        potentialValues: PropTypes.arrayOf(PropTypes.shape({
            id: PropTypes.string,
            name: PropTypes.string
        })),
        if: PropTypes.shape({
            addonEnabled: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
            // eslint-disable-next-line react/forbid-prop-types
            settings: PropTypes.object
        })
    }),
    value: PropTypes.oneOfType([PropTypes.string, PropTypes.bool, PropTypes.number])
};

const Notice = ({
    type,
    text
}) => (
    <div
        className={styles.notice}
        type={type}
    >
        <div>
            <img
                className={styles.noticeIcon}
                src={infoImage}
                alt=""
                draggable={false}
            />
        </div>
        <div>
            {text}
        </div>
    </div>
);
Notice.propTypes = {
    type: PropTypes.string,
    text: PropTypes.string
};

const Presets = ({
    addonId,
    presets
}) => (
    <div className={classNames(styles.setting, styles.presets)}>
        <div className={styles.settingLabel}>
            {settingsTranslations.presets}
        </div>
        {presets.map(preset => {
            const presetId = preset.id;
            const name = addonTranslations[`${addonId}/@preset-name-${presetId}`] || preset.name;
            const description = addonTranslations[`${addonId}/@preset-description-${presetId}`] || preset.description;
            return (
                <button
                    key={presetId}
                    title={description}
                    className={classNames(styles.button, styles.presetButton)}
                    onClick={() => SettingsStore.applyAddonPreset(addonId, presetId)}
                >
                    {name}
                </button>
            );
        })}
    </div>
);
Presets.propTypes = {
    addonId: PropTypes.string,
    presets: PropTypes.arrayOf(PropTypes.shape({
        name: PropTypes.string,
        id: PropTypes.string,
        description: PropTypes.string,
        values: PropTypes.shape({})
    }))
};

const Addon = ({
    id,
    settings,
    manifest,
    extended
}) => (
    <div className={classNames(styles.addon, { [styles.addonDirty]: settings.dirty })}>
        <div className={styles.addonHeader}>
            <label className={styles.addonTitle}>
                <div className={styles.addonSwitch}>
                    <Switch
                        value={settings.enabled}
                        onChange={value => {
                            if (
                                !value ||
                                !manifest.tags.includes('danger') ||
                                confirm(settingsTranslations.enableDangerous)
                            ) {
                                SettingsStore.setAddonEnabled(id, value);
                            }
                        }}
                    />
                </div>
                {manifest.tags.includes('theme') ? (
                    <img
                        className={styles.extensionImage}
                        src={brushImage}
                        draggable={false}
                        alt=""
                    />
                ) : (
                    <img
                        className={styles.extensionImage}
                        src={extensionImage}
                        draggable={false}
                        alt=""
                    />
                )}
                <div className={styles.addonTitleText}>
                    {addonTranslations[`${id}/@name`] || manifest.name}
                </div>
                {extended && (
                    <div className={styles.addonId}>
                        {`(${id})`}
                    </div>
                )}
            </label>
            <Tags manifest={manifest} />
            {!settings.enabled && (
                <div className={styles.inlineDescription}>
                    {addonTranslations[`${id}/@description`] || manifest.description}
                </div>
            )}
            <div className={styles.addonOperations}>
                {settings.enabled && manifest.settings && (
                    <button
                        className={styles.resetButton}
                        onClick={() => SettingsStore.resetAddon(id)}
                        title={settingsTranslations.reset}
                    >
                        <img
                            src={undoImage}
                            className={styles.resetButtonImage}
                            alt={settingsTranslations.reset}
                            draggable={false}
                        />
                    </button>
                )}
            </div>
        </div>
        {settings.enabled && (
            <div className={styles.addonDetails}>
                <div className={styles.description}>
                    {addonTranslations[`${id}/@description`] || manifest.description}
                </div>
                {manifest.credits && (
                    <div className={styles.creditContainer}>
                        <span className={styles.creditTitle}>
                            {settingsTranslations.credits}
                        </span>
                        <CreditList credits={manifest.credits} />
                    </div>
                )}
                {manifest.info && (
                    manifest.info.map(info => (
                        <Notice
                            key={info.id}
                            type={info.type}
                            text={addonTranslations[`${id}/@info-${info.id}`] || info.text}
                        />
                    ))
                )}
                {manifest.noCompiler && (
                    <Notice
                        type="warning"
                        text={settingsTranslations.noCompiler}
                    />
                )}
                {manifest.settings && (
                    <div className={styles.settingContainer}>
                        {manifest.settings.map(setting => (
                            <Setting
                                key={setting.id}
                                addonId={id}
                                setting={setting}
                                value={settings[setting.id]}
                            />
                        ))}
                        {manifest.presets && (
                            <Presets
                                addonId={id}
                                presets={manifest.presets}
                            />
                        )}
                    </div>
                )}
            </div>
        )}
    </div>
);
Addon.propTypes = {
    id: PropTypes.string,
    settings: PropTypes.shape({
        enabled: PropTypes.bool,
        dirty: PropTypes.bool
    }),
    manifest: PropTypes.shape({
        name: PropTypes.string,
        description: PropTypes.string,
        credits: PropTypes.arrayOf(PropTypes.shape({})),
        info: PropTypes.arrayOf(PropTypes.shape({
            id: PropTypes.string
        })),
        settings: PropTypes.arrayOf(PropTypes.shape({
            id: PropTypes.string
        })),
        presets: PropTypes.arrayOf(PropTypes.shape({})),
        tags: PropTypes.arrayOf(PropTypes.string),
        noCompiler: PropTypes.bool
    }),
    extended: PropTypes.bool
};

const Dirty = props => (
    <div className={styles.dirtyOuter}>
        <div className={styles.dirtyInner}>
            {settingsTranslations.dirty}
            {props.onReloadNow && (
                <button
                    className={classNames(styles.button, styles.dirtyButton)}
                    onClick={props.onReloadNow}
                >
                    {settingsTranslations.dirtyButton}
                </button>
            )}
        </div>
    </div>
);
Dirty.propTypes = {
    onReloadNow: PropTypes.func
};

const UnsupportedAddons = ({ addons: addonList }) => (
    <div className={styles.unsupportedContainer}>
        <span className={styles.unsupportedText}>
            {settingsTranslations.unsupported}
        </span>
        {addonList.map(({ id, manifest }, index) => (
            <span
                key={id}
                className={styles.unsupportedAddon}
            >
                {addonTranslations[`${id}/@name`] || manifest.name}
                {index !== addonList.length - 1 && (
                    ', '
                )}
            </span>
        ))}
    </div>
);
UnsupportedAddons.propTypes = {
    addons: PropTypes.arrayOf(PropTypes.shape({
        id: PropTypes.string,
        manifest: PropTypes.shape({
            name: PropTypes.string
        })
    }))
};

const InternalAddonList = ({ addons, extended }) => (
    addons.map(({ id, manifest, state }) => (
        <Addon
            key={id}
            id={id}
            settings={state}
            manifest={manifest}
            extended={extended}
        />
    ))
);

class AddonGroup extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            open: props.open
        };
    }
    render() {
        if (this.props.addons.length === 0) {
            return null;
        }
        return (
            <div className={styles.addonGroup}>
                <button
                    className={styles.addonGroupName}
                    onClick={() => {
                        this.setState({
                            open: !this.state.open
                        });
                    }}
                >
                    <img
                        className={styles.addonGroupExpand}
                        src={expandImageBlack}
                        data-open={this.state.open}
                        alt=""
                    />
                    {this.props.label.replace('{number}', this.props.addons.length)}
                </button>
                {this.state.open && (
                    <InternalAddonList
                        addons={this.props.addons}
                        extended={this.props.extended}
                    />
                )}
            </div>
        );
    }
}
AddonGroup.propTypes = {
    label: PropTypes.string,
    open: PropTypes.bool,
    addons: PropTypes.arrayOf(PropTypes.shape({
        id: PropTypes.string.isRequired,
        state: PropTypes.shape({}).isRequired,
        manifest: PropTypes.shape({}).isRequired
    })).isRequired,
    extended: PropTypes.bool.isRequired
};

const addonToSearchItem = ({ id, manifest }) => {
    const texts = new Set();
    const addText = (score, text) => {
        if (text) {
            texts.add({
                score,
                text
            });
        }
    };
    addText(1, id);
    addText(1, manifest.name);
    addText(1, addonTranslations[`${id}/@name`]);
    addText(0.5, manifest.description);
    addText(0.5, addonTranslations[`${id}/@description`]);
    if (manifest.settings) {
        for (const setting of manifest.settings) {
            addText(0.25, setting.name);
            addText(0.25, addonTranslations[`${id}/@settings-name-${setting.id}`]);
        }
    }
    if (manifest.presets) {
        for (const preset of manifest.presets) {
            addText(0.1, preset.name);
            addText(0.1, addonTranslations[`${id}/@preset-name-${preset.id}`]);
            addText(0.1, preset.description);
            addText(0.1, addonTranslations[`${id}/@preset-description-${preset.id}`]);
        }
    }
    for (const tag of manifest.tags) {
        const key = `tags.${tag}`;
        if (settingsTranslations[key]) {
            addText(0.25, settingsTranslations[key]);
        }
    }
    if (manifest.info) {
        for (const info of manifest.info) {
            addText(0.25, info.text);
            addText(0.25, addonTranslations[`${id}/@info-${info.id}`]);
        }
    }
    return texts;
};

class AddonList extends React.Component {
    constructor(props) {
        super(props);
        this.search = new Search(this.props.addons.map(addonToSearchItem));
        this.groups = [];
    }
    render() {
        if (this.props.search) {
            const addons = this.search.search(this.props.search)
                .slice(0, 20)
                .map(({ index }) => this.props.addons[index]);
            if (addons.length === 0) {
                return (
                    <div className={styles.noResults}>
                        {settingsTranslations.noResults}
                    </div>
                );
            }
            return (
                <div>
                    <InternalAddonList
                        addons={addons}
                        extended={this.props.extended}
                    />
                </div>
            );
        }
        return (
            <div>
                {Object.entries(groupedAddons).map(([id, { label, addons, open }]) => (
                    <AddonGroup
                        key={id}
                        label={label}
                        open={open}
                        addons={addons.map(index => this.props.addons[index])}
                        extended={this.props.extended}
                    />
                ))}
            </div>
        );
    }
}
AddonList.propTypes = {
    addons: PropTypes.arrayOf(PropTypes.shape({
        id: PropTypes.string.isRequired,
        state: PropTypes.shape({}).isRequired,
        manifest: PropTypes.shape({}).isRequired
    })).isRequired,
    search: PropTypes.string.isRequired,
    extended: PropTypes.bool.isRequired
};

class AddonSettingsComponent extends React.Component {
    constructor(props) {
        super(props);
        this.handleSettingStoreChanged = this.handleSettingStoreChanged.bind(this);
        this.handleReloadNow = this.handleReloadNow.bind(this);
        this.handleResetAll = this.handleResetAll.bind(this);
        this.handleExport = this.handleExport.bind(this);
        this.handleImport = this.handleImport.bind(this);
        this.handleKeyDown = this.handleKeyDown.bind(this);
        this.handleSearch = this.handleSearch.bind(this);
        this.handleClickSearchButton = this.handleClickSearchButton.bind(this);
        this.handleClickVersion = this.handleClickVersion.bind(this);
        this.searchRef = this.searchRef.bind(this);
        this.searchBar = null;
        this.state = {
            loading: false,
            dirty: false,
            search: location.hash ? location.hash.substr(1) : '',
            extended: false,
            ...this.readFullAddonState()
        };
        if (Channels.changeChannel) {
            Channels.changeChannel.addEventListener('message', () => {
                SettingsStore.readLocalStorage();
                this.setState(this.readFullAddonState());
            });
        }
    }
    componentDidMount() {
        SettingsStore.addEventListener('setting-changed', this.handleSettingStoreChanged);
        document.body.addEventListener('keydown', this.handleKeyDown);
    }
    componentWillUnmount() {
        SettingsStore.removeEventListener('setting-changed', this.handleSettingStoreChanged);
        document.body.removeEventListener('keydown', this.handleKeyDown);
    }
    readFullAddonState() {
        const result = {};
        for (const [id, manifest] of Object.entries(supportedAddons)) {
            const enabled = SettingsStore.getAddonEnabled(id);
            const addonState = {
                enabled: enabled,
                dirty: false
            };
            if (manifest.settings) {
                for (const setting of manifest.settings) {
                    addonState[setting.id] = SettingsStore.getAddonSetting(id, setting.id);
                }
            }
            result[id] = addonState;
        }
        return result;
    }
    handleSettingStoreChanged(e) {
        const { addonId, settingId, value } = e.detail;
        // If channels are unavailable, every change requires reload.
        const reloadRequired = e.detail.reloadRequired || !Channels.changeChannel;
        this.setState(state => {
            const newState = {
                [addonId]: {
                    ...state[addonId],
                    [settingId]: value,
                    dirty: true
                }
            };
            if (reloadRequired) {
                newState.dirty = true;
            }
            return newState;
        });
        if (!reloadRequired) {
            postThrottledSettingsChange(SettingsStore.store);
        }
    }
    handleReloadNow() {
        // Value posted does not matter
        Channels.reloadChannel.postMessage(0);
        this.setState({
            dirty: false
        });
        for (const addonId of Object.keys(supportedAddons)) {
            if (this.state[addonId].dirty) {
                this.setState(state => ({
                    [addonId]: {
                        ...state[addonId],
                        dirty: false
                    }
                }));
            }
        }
    }
    handleResetAll() {
        if (confirm(settingsTranslations.confirmResetAll)) {
            SettingsStore.resetAllAddons();
            this.setState({
                search: ''
            });
        }
    }
    handleExport() {
        const exportedData = SettingsStore.export({
            theme
        });
        this.props.onExportSettings(exportedData);
    }
    handleImport() {
        const fileSelector = document.createElement('input');
        fileSelector.type = 'file';
        fileSelector.accept = '.json';
        document.body.appendChild(fileSelector);
        fileSelector.click();
        document.body.removeChild(fileSelector);
        fileSelector.addEventListener('change', async () => {
            const file = fileSelector.files[0];
            if (!file) {
                return;
            }
            try {
                const text = await file.text();
                const data = JSON.parse(text);
                SettingsStore.import(data);
                this.setState({
                    search: ''
                });
            } catch (e) {
                console.error(e);
                alert(e);
            }
        });
    }
    handleSearch(e) {
        const value = e.target.value;
        this.setState({
            search: value
        });
    }
    handleClickSearchButton() {
        this.setState({
            search: ''
        });
        this.searchBar.focus();
    }
    handleClickVersion() {
        this.setState({
            extended: !this.state.extended
        });
    }
    searchRef(searchBar) {
        this.searchBar = searchBar;
    }
    handleKeyDown(e) {
        const key = e.key;
        if (key.length === 1 && key !== ' ' && e.target === document.body && !(e.ctrlKey || e.metaKey || e.altKey)) {
            this.searchBar.focus();
        }
        // Only preventDefault() if the search bar isn't already focused so
        // that we don't break the browser's builtin ctrl+f
        if (key === 'f' && (e.ctrlKey || e.metaKey) && document.activeElement !== this.searchBar) {
            this.searchBar.focus();
            e.preventDefault();
        }
    }
    render() {
        const addonState = Object.entries(supportedAddons).map(([id, manifest]) => ({
            id,
            manifest,
            state: this.state[id]
        }));
        const unsupported = Object.entries(unsupportedAddons).map(([id, manifest]) => ({
            id,
            manifest
        }));
        return (
            <div className={styles.container}>
                <div className={styles.header}>
                    <div className={styles.section}>
                        <div className={styles.searchContainer}>
                            <input
                                className={styles.searchInput}
                                value={this.state.search}
                                onChange={this.handleSearch}
                                placeholder={settingsTranslations.search}
                                aria-label={settingsTranslations.search}
                                ref={this.searchRef}
                                spellCheck="false"
                                autoFocus
                            />
                            <div
                                className={styles.searchButton}
                                onClick={this.handleClickSearchButton}
                            />
                        </div>
                        <a
                            href="https://discord.gg/NZ9MBMYTZh"
                            target="_blank"
                            rel="noreferrer"
                            className={styles.feedbackButtonOuter}
                        >
                            <span className={styles.feedbackButtonInner}>
                                {settingsTranslations.addonFeedback}
                            </span>
                        </a>
                    </div>
                    {this.state.dirty && (
                        <Dirty
                            onReloadNow={Channels.reloadChannel ? this.handleReloadNow : null}
                        />
                    )}
                </div>
                <div className={styles.addons}>
                    {!this.state.loading && (
                        <div className={styles.section}>
                            <AddonList
                                addons={addonState}
                                search={this.state.search}
                                extended={this.state.extended}
                            />
                            <div className={styles.footerButtons}>
                                <button
                                    className={classNames(styles.button, styles.resetAllButton)}
                                    onClick={this.handleResetAll}
                                >
                                    {settingsTranslations.resetAll}
                                </button>
                                <button
                                    className={classNames(styles.button, styles.exportButton)}
                                    onClick={this.handleExport}
                                >
                                    {settingsTranslations.export}
                                </button>
                                <button
                                    className={classNames(styles.button, styles.importButton)}
                                    onClick={this.handleImport}
                                >
                                    {settingsTranslations.import}
                                </button>
                            </div>
                            <footer className={styles.footer}>
                                {unsupported.length ? (
                                    <UnsupportedAddons
                                        addons={unsupported}
                                    />
                                ) : null}
                                <span
                                    className={styles.version}
                                    onClick={this.handleClickVersion}
                                >
                                    {this.state.extended ?
                                        // Don't bother translating, pretty much no one will ever see this.
                                        // eslint-disable-next-line max-len
                                        `You have enabled debug mode. (Addons version ${upstreamMeta.commit})` :
                                        `Addons version ${upstreamMeta.commit}`}
                                </span>
                            </footer>
                        </div>
                    )}
                </div>
            </div>
        );
    }
}
AddonSettingsComponent.propTypes = {
    onExportSettings: PropTypes.func
};

export default AddonSettingsComponent;
