// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/* eslint-disable @devtools/no-lit-render-outside-of-view */

import '../../../ui/kit/kit.js';

import * as i18n from '../../../core/i18n/i18n.js';
import * as Buttons from '../../../ui/components/buttons/buttons.js';
import * as Lit from '../../../ui/lit/lit.js';
import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';

import linearMemoryNavigatorStyles from './linearMemoryNavigator.css.js';

const UIStrings = {
  /**
   * @description Tooltip text that appears when hovering over a valid memory address (e.g. 0x0) in the address line in the Linear memory inspector.
   */
  enterAddress: 'Enter address',
  /**
   * @description Tooltip text that appears when hovering over the button to go back in history in the Linear Memory Navigator
   */
  goBackInAddressHistory: 'Go back in address history',
  /**
   * @description Tooltip text that appears when hovering over the button to go forward in history in the Linear Memory Navigator
   */
  goForwardInAddressHistory: 'Go forward in address history',
  /**
   * @description Tooltip text that appears when hovering over the page back icon in the Linear Memory Navigator
   */
  previousPage: 'Previous page',
  /**
   * @description Tooltip text that appears when hovering over the next page icon in the Linear Memory Navigator
   */
  nextPage: 'Next page',
  /**
   * @description Text to refresh the page
   */
  refresh: 'Refresh',
} as const;
const str_ =
    i18n.i18n.registerUIStrings('panels/linear_memory_inspector/components/LinearMemoryNavigator.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
const {render, html, Directives: {ifDefined}} = Lit;

export const enum Navigation {
  BACKWARD = 'Backward',
  FORWARD = 'Forward',
}

export class AddressInputChangedEvent extends Event {
  static readonly eventName = 'addressinputchanged';
  data: {address: string, mode: Mode};

  constructor(address: string, mode: Mode) {
    super(AddressInputChangedEvent.eventName);
    this.data = {address, mode};
  }
}

export class PageNavigationEvent extends Event {
  static readonly eventName = 'pagenavigation';
  data: Navigation;

  constructor(navigation: Navigation) {
    super(PageNavigationEvent.eventName, {});
    this.data = navigation;
  }
}

export class HistoryNavigationEvent extends Event {
  static readonly eventName = 'historynavigation';
  data: Navigation;

  constructor(navigation: Navigation) {
    super(HistoryNavigationEvent.eventName, {});
    this.data = navigation;
  }
}

export class RefreshRequestedEvent extends Event {
  static readonly eventName = 'refreshrequested';
  constructor() {
    super(RefreshRequestedEvent.eventName, {});
  }
}

export interface LinearMemoryNavigatorData {
  address: string;
  mode: Mode;
  canGoBackInHistory: boolean;
  canGoForwardInHistory: boolean;
  valid: boolean;
  error: string|undefined;
}

export const enum Mode {
  EDIT = 'Edit',
  SUBMITTED = 'Submitted',
  INVALID_SUBMIT = 'InvalidSubmit',
}

export class LinearMemoryNavigator extends HTMLElement {
  readonly #shadow = this.attachShadow({mode: 'open'});
  #address = '0';
  #error: string|undefined = undefined;
  #valid = true;
  #canGoBackInHistory = false;
  #canGoForwardInHistory = false;

  set data(data: LinearMemoryNavigatorData) {
    this.#address = data.address;
    this.#error = data.error;
    this.#valid = data.valid;
    this.#canGoBackInHistory = data.canGoBackInHistory;
    this.#canGoForwardInHistory = data.canGoForwardInHistory;
    this.#render();

    const addressInput = this.#shadow.querySelector<HTMLInputElement>('.address-input');
    if (addressInput) {
      if (data.mode === Mode.SUBMITTED) {
        addressInput.blur();
      } else if (data.mode === Mode.INVALID_SUBMIT) {
        addressInput.select();
      }
    }
  }

  #render(): void {
    // Disabled until https://crbug.com/1079231 is fixed.
    // clang-format off
    const result = html`
      <style>${linearMemoryNavigatorStyles}</style>
      <div class="navigator">
        <div class="navigator-item">
          ${this.#createButton({icon: 'undo', title: i18nString(UIStrings.goBackInAddressHistory),
              event: new HistoryNavigationEvent(Navigation.BACKWARD), enabled: this.#canGoBackInHistory,
              jslogContext:'linear-memory-inspector.history-back'})}
          ${this.#createButton({icon: 'redo', title: i18nString(UIStrings.goForwardInAddressHistory),
              event: new HistoryNavigationEvent(Navigation.FORWARD), enabled: this.#canGoForwardInHistory,
              jslogContext:'linear-memory-inspector.history-forward'})}
        </div>
        <div class="navigator-item">
          ${this.#createButton({icon: 'chevron-left', title: i18nString(UIStrings.previousPage),
              event: new PageNavigationEvent(Navigation.BACKWARD), enabled: true,
              jslogContext:'linear-memory-inspector.previous-page'})}
          ${this.#createAddressInput()}
          ${this.#createButton({icon: 'chevron-right', title: i18nString(UIStrings.nextPage),
              event: new PageNavigationEvent(Navigation.FORWARD), enabled: true,
              jslogContext:'linear-memory-inspector.next-page'})}
        </div>
        ${this.#createButton({icon: 'refresh', title: i18nString(UIStrings.refresh),
            event: new RefreshRequestedEvent(), enabled: true,
            jslogContext:'linear-memory-inspector.refresh'})}
      </div>
      `;
      render(result, this.#shadow, {host: this});
    // clang-format on
  }

  #createAddressInput(): Lit.TemplateResult {
    const classMap = {
      'address-input': true,
      invalid: !this.#valid,
    };
    return html`<input
      class=${Lit.Directives.classMap(classMap)}
      data-input="true"
      .value=${this.#address}
      jslog=${VisualLogging.textField('linear-memory-inspector.address').track({
      change: true,
    })}
      title=${
        ifDefined(
            this.#valid ? i18nString(UIStrings.enterAddress) : this.#error,
            )}
      @change=${this.#onAddressChange.bind(this, Mode.SUBMITTED)}
      @input=${this.#onAddressChange.bind(this, Mode.EDIT)}
    />`;
  }

  #onAddressChange(mode: Mode, event: Event): void {
    const addressInput = event.target as HTMLInputElement;
    this.dispatchEvent(new AddressInputChangedEvent(addressInput.value, mode));
  }

  #createButton(data: {icon: string, title: string, event: Event, enabled: boolean, jslogContext: string}):
      Lit.TemplateResult {
    return html`
      <devtools-button class="navigator-button"
        .data=${
        {variant: Buttons.Button.Variant.ICON, iconName: data.icon, disabled: !data.enabled} as
        Buttons.Button.ButtonData}
        jslog=${VisualLogging.action().track({click: true, keydown: 'Enter'}).context(data.jslogContext)}
        data-button=${data.event.type} title=${data.title}
        @click=${this.dispatchEvent.bind(this, data.event)}
      ></devtools-button>`;
  }
}

customElements.define('devtools-linear-memory-inspector-navigator', LinearMemoryNavigator);

declare global {
  interface HTMLElementTagNameMap {
    'devtools-linear-memory-inspector-navigator': LinearMemoryNavigator;
  }
}
