/**
 * @license
 * Copyright (C) 2018 The Android Open Source Project
 *
 * 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 '@polymer/iron-input/iron-input';
import '@polymer/paper-toggle-button/paper-toggle-button';
import '../../../styles/gr-form-styles';
import '../../../styles/gr-subpage-styles';
import '../../../styles/shared-styles';
import '../../shared/gr-icons/gr-icons';
import '../../shared/gr-select/gr-select';
import '../../shared/gr-tooltip-content/gr-tooltip-content';
import '../gr-plugin-config-array-editor/gr-plugin-config-array-editor';
import {dom, EventApi} from '@polymer/polymer/lib/legacy/polymer.dom';
import {GestureEventListeners} from '@polymer/polymer/lib/mixins/gesture-event-listeners';
import {LegacyElementMixin} from '@polymer/polymer/lib/legacy/legacy-element-mixin';
import {PolymerElement} from '@polymer/polymer/polymer-element';
import {htmlTemplate} from './gr-repo-plugin-config_html';
import {customElement, property} from '@polymer/decorators';
import {ConfigParameterInfoType} from '../../../constants/constants';
import {PolymerDeepPropertyChange} from '@polymer/polymer/interfaces';
import {
  ConfigParameterInfo,
  PluginParameterToConfigParameterInfoMap,
} from '../../../types/common';
import {PaperToggleButtonElement} from '@polymer/paper-toggle-button/paper-toggle-button';
import {IronInputElement} from '@polymer/iron-input/iron-input';
import {
  PluginConfigOptionsChangedEventDetail,
  PluginOption,
} from './gr-repo-plugin-config-types';

const PLUGIN_CONFIG_CHANGED_EVENT_NAME = 'plugin-config-changed';

export interface ConfigChangeInfo {
  _key: string; // parameterName of PluginParameterToConfigParameterInfoMap
  info: ConfigParameterInfo;
  notifyPath: string;
}

export interface PluginData {
  name: string; // parameterName of PluginParameterToConfigParameterInfoMap
  config: PluginParameterToConfigParameterInfoMap;
}

export interface PluginConfigChangeDetail {
  name: string; // parameterName of PluginParameterToConfigParameterInfoMap
  config: PluginParameterToConfigParameterInfoMap;
  notifyPath: string;
}

@customElement('gr-repo-plugin-config')
class GrRepoPluginConfig extends GestureEventListeners(
  LegacyElementMixin(PolymerElement)
) {
  static get template() {
    return htmlTemplate;
  }

  /**
   * Fired when the plugin config changes.
   *
   * @event plugin-config-changed
   */

  @property({type: Object})
  pluginData?: PluginData;

  @property({
    type: Array,
    computed: '_computePluginConfigOptions(pluginData.*)',
  })
  _pluginConfigOptions!: PluginOption[]; // _computePluginConfigOptions never returns null

  _computePluginConfigOptions(
    dataRecord: PolymerDeepPropertyChange<PluginData, PluginData>
  ): PluginOption[] {
    if (!dataRecord || !dataRecord.base || !dataRecord.base.config) {
      return [];
    }
    const config = dataRecord.base.config;
    return Object.keys(config).map(_key => {
      return {_key, info: config[_key]};
    });
  }

  _isArray(type: ConfigParameterInfoType) {
    return type === ConfigParameterInfoType.ARRAY;
  }

  _isBoolean(type: ConfigParameterInfoType) {
    return type === ConfigParameterInfoType.BOOLEAN;
  }

  _isList(type: ConfigParameterInfoType) {
    return type === ConfigParameterInfoType.LIST;
  }

  _isString(type: ConfigParameterInfoType) {
    // Treat numbers like strings for simplicity.
    return (
      type === ConfigParameterInfoType.STRING ||
      type === ConfigParameterInfoType.INT ||
      type === ConfigParameterInfoType.LONG
    );
  }

  _computeDisabled(editable: string) {
    return editable === 'false';
  }

  _computeChecked(value = 'false') {
    return JSON.parse(value) as boolean;
  }

  _handleStringChange(e: Event) {
    const el = (dom(e) as EventApi).localTarget as IronInputElement;
    // In the template, the data-option-key is assigned to each editor
    const _key = el.getAttribute('data-option-key')!;
    const configChangeInfo = this._buildConfigChangeInfo(el.value, _key);
    this._handleChange(configChangeInfo);
  }

  _handleListChange(e: Event) {
    const el = (dom(e) as EventApi).localTarget as HTMLOptionElement;
    // In the template, the data-option-key is assigned to each editor
    const _key = el.getAttribute('data-option-key')!;
    const configChangeInfo = this._buildConfigChangeInfo(el.value, _key);
    this._handleChange(configChangeInfo);
  }

  _handleBooleanChange(e: Event) {
    const el = (dom(e) as EventApi).localTarget as PaperToggleButtonElement;
    // In the template, the data-option-key is assigned to each editor
    const _key = el.getAttribute('data-option-key')!;
    const configChangeInfo = this._buildConfigChangeInfo(
      JSON.stringify(el.checked),
      _key
    );
    this._handleChange(configChangeInfo);
  }

  _buildConfigChangeInfo(
    value: string | null | undefined,
    _key: string
  ): ConfigChangeInfo {
    // If pluginData is not set, editors are not created and this method
    // can't be called
    const info = this.pluginData!.config[_key];
    info.value = value !== null ? value : undefined;
    return {
      _key,
      info,
      notifyPath: `${_key}.value`,
    };
  }

  _handleArrayChange(e: CustomEvent<PluginConfigOptionsChangedEventDetail>) {
    this._handleChange(e.detail);
  }

  _handleChange({_key, info, notifyPath}: ConfigChangeInfo) {
    // If pluginData is not set, editors are not created and this method
    // can't be called
    const {name, config} = this.pluginData!;

    /** @type {Object} */
    const detail: PluginConfigChangeDetail = {
      name,
      config: {...config, [_key]: info},
      notifyPath: `${name}.${notifyPath}`,
    };

    this.dispatchEvent(
      new CustomEvent(PLUGIN_CONFIG_CHANGED_EVENT_NAME, {
        detail,
        bubbles: true,
        composed: true,
      })
    );
  }

  /**
   * Work around a issue on iOS when clicking turns into double tap
   */
  _onTapPluginBoolean(e: Event) {
    e.preventDefault();
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'gr-repo-plugin-config': GrRepoPluginConfig;
  }
}
