/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

/* globals ContentSearchUIController, ContentSearchHandoffUIController */

import { actionCreators as ac, actionTypes as at } from "common/Actions.mjs";
import { connect } from "react-redux";
import { IS_NEWTAB } from "content-src/lib/constants";
import { Logo } from "content-src/components/Logo/Logo";
import React from "react";

export class _Search extends React.PureComponent {
  constructor(props) {
    super(props);
    this.onSearchClick = this.onSearchClick.bind(this);
    this.onSearchHandoffClick = this.onSearchHandoffClick.bind(this);
    this.onSearchHandoffPaste = this.onSearchHandoffPaste.bind(this);
    this.onSearchHandoffDrop = this.onSearchHandoffDrop.bind(this);
    this.onInputMount = this.onInputMount.bind(this);
    this.onInputMountHandoff = this.onInputMountHandoff.bind(this);
    this.onSearchHandoffButtonMount =
      this.onSearchHandoffButtonMount.bind(this);
  }

  handleEvent(event) {
    // Also track search events with our own telemetry
    if (event.detail.type === "Search") {
      this.props.dispatch(ac.UserEvent({ event: "SEARCH" }));
    }
  }

  onSearchClick(event) {
    window.gContentSearchController.search(event);
  }

  doSearchHandoff(text) {
    this.props.dispatch(
      ac.OnlyToMain({ type: at.HANDOFF_SEARCH_TO_AWESOMEBAR, data: { text } })
    );
    this.props.dispatch({ type: at.FAKE_FOCUS_SEARCH });
    this.props.dispatch(ac.UserEvent({ event: "SEARCH_HANDOFF" }));
    if (text) {
      this.props.dispatch({ type: at.DISABLE_SEARCH });
    }
  }

  onSearchHandoffClick(event) {
    // When search hand-off is enabled, we render a big button that is styled to
    // look like a search textbox. If the button is clicked, we style
    // the button as if it was a focused search box and show a fake cursor but
    // really focus the awesomebar without the focus styles ("hidden focus").
    event.preventDefault();
    this.doSearchHandoff();
  }

  onSearchHandoffPaste(event) {
    event.preventDefault();
    this.doSearchHandoff(event.clipboardData.getData("Text"));
  }

  onSearchHandoffDrop(event) {
    event.preventDefault();
    let text = event.dataTransfer.getData("text");
    if (text) {
      this.doSearchHandoff(text);
    }
  }

  componentDidMount() {
    const caret = this.fakeCaret;
    const { caretBlinkCount, caretBlinkTime } = this.props.Prefs.values;

    if (caret) {
      // If caret blink count isn't defined, use the default infinite behavior for animation
      caret.style.setProperty(
        "--caret-blink-count",
        caretBlinkCount > -1 ? caretBlinkCount : "infinite"
      );

      // Apply custom blink rate if set, else fallback to default (567ms on/off --> 1134ms total)
      caret.style.setProperty(
        "--caret-blink-time",
        caretBlinkTime > 0 ? `${caretBlinkTime * 2}ms` : `${1134}ms`
      );
    }
  }

  componentWillUnmount() {
    delete window.gContentSearchController;
  }

  onInputMount(input) {
    if (input) {
      // The "healthReportKey" and needs to be "newtab" or "abouthome" so that
      // BrowserUsageTelemetry.sys.mjs knows to handle events with this name, and
      // can add the appropriate telemetry probes for search. Without the correct
      // name, certain tests like browser_UsageTelemetry_content.js will fail
      // (See github ticket #2348 for more details)
      const healthReportKey = IS_NEWTAB ? "newtab" : "abouthome";

      // The "searchSource" needs to be "newtab" or "homepage" and is sent with
      // the search data and acts as context for the search request (See
      // nsISearchEngine.getSubmission). It is necessary so that search engine
      // plugins can correctly atribute referrals. (See github ticket #3321 for
      // more details)
      const searchSource = IS_NEWTAB ? "newtab" : "homepage";

      // gContentSearchController needs to exist as a global so that tests for
      // the existing about:home can find it; and so it allows these tests to pass.
      // In the future, when activity stream is default about:home, this can be renamed
      window.gContentSearchController = new ContentSearchUIController(
        input,
        input.parentNode,
        healthReportKey,
        searchSource
      );
      addEventListener("ContentSearchClient", this);
    } else {
      window.gContentSearchController = null;
      removeEventListener("ContentSearchClient", this);
    }
  }

  onInputMountHandoff(input) {
    if (input) {
      // The handoff UI controller helps us set the search icon and reacts to
      // changes to default engine to keep everything in sync.
      this._handoffSearchController = new ContentSearchHandoffUIController();
    }
  }

  onSearchHandoffButtonMount(button) {
    // Keep a reference to the button for use during "paste" event handling.
    this._searchHandoffButton = button;
  }

  /*
   * Do not change the ID on the input field, as legacy newtab code
   * specifically looks for the id 'newtab-search-text' on input fields
   * in order to execute searches in various tests
   */
  render() {
    const wrapperClassName = [
      "search-wrapper",
      this.props.disable && "search-disabled",
      this.props.fakeFocus && "fake-focus",
    ]
      .filter(v => v)
      .join(" ");

    return (
      <div className={wrapperClassName}>
        {this.props.showLogo && <Logo />}
        {!this.props.handoffEnabled && (
          <div className="search-inner-wrapper no-handoff">
            <input
              id="newtab-search-text"
              data-l10n-id="newtab-search-box-input"
              maxLength="256"
              ref={this.onInputMount}
              type="search"
            />
            <button
              id="searchSubmit"
              className="search-button"
              data-l10n-id="newtab-search-box-search-button"
              onClick={this.onSearchClick}
            />
          </div>
        )}
        {this.props.handoffEnabled && (
          <div className="search-inner-wrapper">
            <button
              className="search-handoff-button"
              ref={this.onSearchHandoffButtonMount}
              onClick={this.onSearchHandoffClick}
              tabIndex="-1"
            >
              <div className="fake-textbox" />
              <input
                type="search"
                className="fake-editable"
                tabIndex="-1"
                aria-hidden="true"
                onDrop={this.onSearchHandoffDrop}
                onPaste={this.onSearchHandoffPaste}
                ref={this.onInputMountHandoff}
              />
              <div
                className="fake-caret"
                ref={el => {
                  this.fakeCaret = el;
                }}
              />
            </button>
          </div>
        )}
      </div>
    );
  }
}

export const Search = connect(state => ({
  Prefs: state.Prefs,
}))(_Search);
