import 'styling/_ResultsPreferences';
import * as _ from 'underscore';
import { PreferencesPanelEvents } from '../../events/PreferencesPanelEvents';
import { exportGlobally } from '../../GlobalExports';
import { Assert } from '../../misc/Assert';
import { Defer } from '../../misc/Defer';
import { ComponentOptionsModel } from '../../models/ComponentOptionsModel';
import { l } from '../../strings/Strings';
import { $$ } from '../../utils/Dom';
import { StorageUtils } from '../../utils/StorageUtils';
import { analyticsActionCauseList, IAnalyticsPreferencesChangeMeta } from '../Analytics/AnalyticsActionListMeta';
import { Component } from '../Base/Component';
import { IComponentBindings } from '../Base/ComponentBindings';
import { ComponentOptions } from '../Base/ComponentOptions';
import { Initialization } from '../Base/Initialization';
import { Checkbox } from '../FormWidgets/Checkbox';
import { FormGroup } from '../FormWidgets/FormGroup';
import { IFormWidgetSelectable } from '../FormWidgets/FormWidgets';
import { RadioButton } from '../FormWidgets/RadioButton';

export interface IResultsPreferencesOptions {
  enableOpenInOutlook?: boolean;
  enableOpenInNewWindow?: boolean;
  enableQuerySyntax?: boolean;
}

export interface IPossiblePreferences {
  openInOutlook?: boolean;
  alwaysOpenInNewWindow?: boolean;
  enableQuerySyntax?: boolean;
}

/**
 * The ResultsPreferences component allows the end user to select preferences related to the search results. These
 * preferences are then saved in the local storage of the end user.
 *
 * This component is normally accessible through the {@link Settings} menu. Its usual location in the DOM is inside the
 * {@link PreferencesPanel} component.
 *
 * See also the {@link ResultsFiltersPreferences} component.
 */
export class ResultsPreferences extends Component {
  static ID = 'ResultsPreferences';

  static doExport = () => {
    exportGlobally({
      ResultsPreferences: ResultsPreferences
    });
  };

  /**
   * The options for the component
   * @componentOptions
   */
  static options: IResultsPreferencesOptions = {
    /**
     * Specifies whether to make the option to open results in Microsoft Outlook available.
     *
     * Default value is `false`
     */
    enableOpenInOutlook: ComponentOptions.buildBooleanOption({ defaultValue: false }),
    /**
     * Specifies whether to make the option to open results in a new window available.
     *
     * Default value is `true`
     */
    enableOpenInNewWindow: ComponentOptions.buildBooleanOption({ defaultValue: true }),
    /**
     * Specifies whether to make the option to allow end users to turn query syntax on or off available.
     *
     * If query syntax is enabled, the Coveo Platform tries to interpret special query syntax (e.g.,
     * `@objecttype=message`) when the end user types a query in the [`Querybox`]{@link Querybox} (see
     * [Coveo Query Syntax Reference](https://docs.coveo.com/en/1552/searching-with-coveo/coveo-cloud-query-syntax)). Enabling query
     * syntax also causes the `Querybox` to highlight any query syntax.
     *
     * Selecting **On** for the **Enable query syntax** setting enables query syntax, whereas selecting **Off** disables
     * it. Selecting **Automatic** uses the `Querybox` [`enableQuerySyntax`]{@link Querybox.options.enableQuerySyntax}
     * option value (which is `false` by default).
     *
     * Default value is `false`
     */
    enableQuerySyntax: ComponentOptions.buildBooleanOption({ defaultValue: false })
  };

  public preferences: IPossiblePreferences;
  private preferencePanelLocalStorage: StorageUtils<IPossiblePreferences>;
  private preferencesPanel: HTMLElement;
  private preferencePanelCheckboxInputs: { [label: string]: Checkbox } = {};
  private preferencePanelRadioInputs: { [label: string]: RadioButton } = {};

  /**
   * Creates a new ResultsPreference component.
   * @param element The HTMLElement on which to instantiate the component.
   * @param options The options for the ResultsPreferences component.
   * @param bindings The bindings that the component requires to function normally. If not set, these will be
   * automatically resolved (with a slower execution time).
   */
  constructor(public element: HTMLElement, public options: IResultsPreferencesOptions, public bindings: IComponentBindings) {
    super(element, ResultsPreferences.ID, bindings);

    this.options = ComponentOptions.initComponentOptions(element, ResultsPreferences, options);

    this.preferencesPanel = $$(this.element).closest(Component.computeCssClassNameForType('PreferencesPanel'));
    this.preferencePanelLocalStorage = new StorageUtils(ResultsPreferences.ID);
    Assert.exists(this.componentOptionsModel);
    Assert.exists(this.preferencesPanel);

    this.preferences = this.preferencePanelLocalStorage.load() || {};
    this.adjustPreferencesToComponentConfig();

    ComponentOptions.initComponentOptions(this.element, ResultsPreferences, this.options);

    this.updateComponentOptionsModel();

    this.bind.on(this.preferencesPanel, PreferencesPanelEvents.savePreferences, () => this.save());
    this.bind.on(this.preferencesPanel, PreferencesPanelEvents.exitPreferencesWithoutSave, () => this.exitWithoutSave());
    this.buildCheckboxesInput();
    this.buildRadiosInput();
  }

  /**
   * Saves the current state of the ResultsPreferences component in the local storage.
   */
  public save() {
    this.fromInputToPreferences();
    this.logger.info('Saving preferences', this.preferences);
    this.preferencePanelLocalStorage.save(this.preferences);
    this.updateComponentOptionsModel();
  }

  public exitWithoutSave() {
    this.fromPreferencesToCheckboxInput();
  }

  private updateComponentOptionsModel() {
    const resultLinkOption = _.pick(this.preferences, 'openInOutlook', 'alwaysOpenInNewWindow');
    const searchBoxOption = _.pick(this.preferences, 'enableQuerySyntax');
    this.componentOptionsModel.set(ComponentOptionsModel.attributesEnum.resultLink, resultLinkOption);
    this.componentOptionsModel.set(ComponentOptionsModel.attributesEnum.searchBox, searchBoxOption);
  }

  private buildRadiosInput() {
    if (this.options.enableQuerySyntax) {
      const createRadioButton = (label: string) => {
        const radio = new RadioButton(
          radioButtonInstance => {
            this.fromPreferenceChangeEventToUsageAnalyticsLog(radioButtonInstance.isSelected() ? 'selected' : 'unselected', label);
            this.save();

            this.queryController.executeQuery({
              closeModalBox: false
            });
          },
          label,
          'coveo-results-preferences-query-syntax'
        );
        return radio;
      };

      const translatedLabels = _.map(['On', 'Off', 'Automatic'], label => l(label));
      const radios = _.map(translatedLabels, label => {
        const radio = createRadioButton(label);
        this.preferencePanelRadioInputs[label] = radio;
        return radio;
      });

      const formGroup = new FormGroup(radios, l('EnableQuerySyntax'));
      $$(this.element).append(formGroup.build());
      this.fromPreferencesToRadioInput();
    }
  }

  private buildCheckboxesInput() {
    const createCheckbox = (label: string) => {
      const checkbox = new Checkbox(checkboxInstance => {
        this.fromPreferenceChangeEventToUsageAnalyticsLog(checkboxInstance.isSelected() ? 'selected' : 'unselected', label);
        this.save();
        this.queryController.executeQuery({
          closeModalBox: false
        });
      }, label);
      this.preferencePanelCheckboxInputs[label] = checkbox;
      return checkbox;
    };

    const checkboxes: Checkbox[] = [];

    if (this.options.enableOpenInOutlook) {
      checkboxes.push(createCheckbox(l('OpenInOutlookWhenPossible')));
    }
    if (this.options.enableOpenInNewWindow) {
      checkboxes.push(createCheckbox(l('AlwaysOpenInNewWindow')));
    }

    this.element.appendChild(new FormGroup(checkboxes, l('ResultLinks')).build());
    this.fromPreferencesToCheckboxInput();
  }

  private fromInputToPreferences() {
    this.preferences = this.preferences || {
      openInOutlook: false,
      alwaysOpenInNewWindow: false,
      enableQuerySyntax: undefined
    };

    _.each(this.preferencePanelCheckboxInputs, (checkbox: Checkbox, label: string) => {
      if (label == l('OpenInOutlookWhenPossible')) {
        if (this.isSelected(l('OpenInOutlookWhenPossible'), label, checkbox)) {
          this.preferences.openInOutlook = true;
        } else if (this.preferences.openInOutlook != null) {
          this.preferences.openInOutlook = false;
        }
      }
      if (label == l('AlwaysOpenInNewWindow')) {
        if (this.isSelected(l('AlwaysOpenInNewWindow'), label, checkbox)) {
          this.preferences.alwaysOpenInNewWindow = true;
        } else if (this.preferences.alwaysOpenInNewWindow != null) {
          this.preferences.alwaysOpenInNewWindow = false;
        }
      }
    });

    _.each(this.preferencePanelRadioInputs, (radio: RadioButton, label: string) => {
      if (this.isSelected(l('On'), label, radio)) {
        this.preferences.enableQuerySyntax = true;
      }
      if (this.isSelected(l('Off'), label, radio)) {
        this.preferences.enableQuerySyntax = false;
      }
      if (this.isSelected(l('Automatic'), label, radio)) {
        delete this.preferences.enableQuerySyntax;
      }
    });
  }

  private fromPreferencesToCheckboxInput() {
    if (this.preferences.openInOutlook) {
      this.preferencePanelCheckboxInputs[l('OpenInOutlookWhenPossible')].select(false);
    }
    if (this.preferences.alwaysOpenInNewWindow) {
      this.preferencePanelCheckboxInputs[l('AlwaysOpenInNewWindow')].select(false);
    }
  }

  private fromPreferencesToRadioInput() {
    if (this.preferences.enableQuerySyntax === true) {
      this.preferencePanelRadioInputs[l('On')].select(false);
    } else if (this.preferences.enableQuerySyntax === false) {
      this.preferencePanelRadioInputs[l('Off')].select(false);
    } else {
      this.preferencePanelRadioInputs[l('Automatic')].select(false);
    }
  }

  private fromPreferenceChangeEventToUsageAnalyticsLog(type: 'selected' | 'unselected', preference: string) {
    this.usageAnalytics.logCustomEvent<IAnalyticsPreferencesChangeMeta>(
      analyticsActionCauseList.preferencesChange,
      { preferenceName: preference, preferenceType: type },
      this.element
    );
    this.usageAnalytics.logSearchEvent<IAnalyticsPreferencesChangeMeta>(analyticsActionCauseList.preferencesChange, {
      preferenceName: preference,
      preferenceType: type
    });
  }

  private adjustPreferencesToComponentConfig() {
    // This method is used when there are illogical configuration between what's saved in local storage (the preferences)
    // and how the component is configured.
    // This can happen if an admin change the component configuration after end users have already selected a preferences.
    // We need to adapt the saved preferences to what's actually available in the component
    let needToSave = false;
    if (!this.options.enableOpenInNewWindow) {
      delete this.preferences.alwaysOpenInNewWindow;
      needToSave = true;
    }

    if (!this.options.enableOpenInOutlook) {
      delete this.preferences.openInOutlook;
      needToSave = true;
    }

    if (!this.options.enableQuerySyntax) {
      delete this.preferences.enableQuerySyntax;
      needToSave = true;
    }

    if (needToSave) {
      Defer.defer(() => {
        this.save();
      });
    }
  }

  private isSelected(checkingFor: string, label: string, input: IFormWidgetSelectable) {
    return checkingFor == label && input.isSelected();
  }
}

Initialization.registerAutoCreateComponent(ResultsPreferences);
