// Copyright 2019 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-imperative-dom-api */

import '../../ui/legacy/legacy.js';

import * as Common from '../../core/common/common.js';
import * as Host from '../../core/host/host.js';
import * as i18n from '../../core/i18n/i18n.js';
import type * as Platform from '../../core/platform/platform.js';
import type * as TextUtils from '../../models/text_utils/text_utils.js';
import * as SourceFrame from '../../ui/legacy/components/source_frame/source_frame.js';
import * as UI from '../../ui/legacy/legacy.js';

import binaryResourceViewStyles from './binaryResourceView.css.js';

const UIStrings = {
  /**
   * @description Text in Binary Resource View of the Network panel. Shown to the user as a status
   * message after the current text has been copied to the clipboard. Base64 is a format for encoding
   * data.
   */
  copiedAsBase: 'Copied as `Base64`',
  /**
   * @description Text in Binary Resource View of the Network panel
   */
  hexViewer: '`Hex` Viewer',
  /**
   * @description Text in Binary Resource View of the Network panel. Shown to the user as a status
   * message after the current text has been copied to the clipboard. Hex is short for hexadecimal,
   * and is a format for encoding data.
   */
  copiedAsHex: 'Copied as `Hex`',
  /**
   * @description Text in Binary Resource View of the Network panel. Shown to the user as a status
   * message after the current text has been copied to the clipboard. UTF-8 is a format for encoding data.
   */
  copiedAsUtf: 'Copied as `UTF-8`',
  /**
   * @description Screen reader label for a select box that chooses how to display binary data in the Network panel
   */
  binaryViewType: 'Binary view type',
  /**
   * @description Tooltip text that appears when hovering over the largeicon copy button in the Binary Resource View of the Network panel
   */
  copyToClipboard: 'Copy to clipboard',
  /**
   * @description A context menu command in the Binary Resource View of the Network panel, for
   * copying to the clipboard. Base64 is a format for encoding data.
   */
  copyAsBase: 'Copy as `Base64`',
  /**
   * @description A context menu command in the Binary Resource View of the Network panel, for copying
   * to the clipboard. Hex is short for hexadecimal, and is a format for encoding data.
   */
  copyAsHex: 'Copy as `Hex`',
  /**
   * @description A context menu command in the Binary Resource View of the Network panel, for copying
   *to the clipboard. UTF-8 is a format for encoding data.
   */
  copyAsUtf: 'Copy as `UTF-8`',
} as const;
const str_ = i18n.i18n.registerUIStrings('panels/network/BinaryResourceView.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
export class BinaryResourceView extends UI.Widget.VBox {
  private readonly binaryResourceViewFactory: SourceFrame.BinaryResourceViewFactory.BinaryResourceViewFactory;
  private readonly toolbar: UI.Toolbar.Toolbar;
  private readonly binaryViewObjects: BinaryViewObject[];
  private binaryViewTypeSetting: Common.Settings.Setting<string>;
  private binaryViewTypeCombobox: UI.Toolbar.ToolbarComboBox;
  private readonly copiedText: UI.Toolbar.ToolbarText;
  private addFadeoutSettimeoutId: number|null;
  private lastView: UI.Widget.Widget|null;

  constructor(
      content: TextUtils.StreamingContentData.StreamingContentData, contentUrl: Platform.DevToolsPath.UrlString,
      resourceType: Common.ResourceType.ResourceType, element?: HTMLElement) {
    super(element);
    this.registerRequiredCSS(binaryResourceViewStyles);

    this.binaryResourceViewFactory =
        new SourceFrame.BinaryResourceViewFactory.BinaryResourceViewFactory(content, contentUrl, resourceType);

    this.toolbar = this.element.createChild('devtools-toolbar', 'binary-view-toolbar');

    this.binaryViewObjects = [
      new BinaryViewObject(
          'base64', i18n.i18n.lockedString('Base64'), i18nString(UIStrings.copiedAsBase),
          this.binaryResourceViewFactory.createBase64View.bind(this.binaryResourceViewFactory),
          this.binaryResourceViewFactory.base64.bind(this.binaryResourceViewFactory)),
      new BinaryViewObject(
          'hex', i18nString(UIStrings.hexViewer), i18nString(UIStrings.copiedAsHex),
          this.binaryResourceViewFactory.createHexView.bind(this.binaryResourceViewFactory),
          this.binaryResourceViewFactory.hex.bind(this.binaryResourceViewFactory)),
      new BinaryViewObject(
          'utf8', i18n.i18n.lockedString('UTF-8'), i18nString(UIStrings.copiedAsUtf),
          this.binaryResourceViewFactory.createUtf8View.bind(this.binaryResourceViewFactory),
          this.binaryResourceViewFactory.utf8.bind(this.binaryResourceViewFactory)),
    ];
    this.binaryViewTypeSetting = Common.Settings.Settings.instance().createSetting('binary-view-type', 'hex');
    this.binaryViewTypeCombobox =
        new UI.Toolbar.ToolbarComboBox(this.binaryViewTypeChanged.bind(this), i18nString(UIStrings.binaryViewType));
    for (const viewObject of this.binaryViewObjects) {
      this.binaryViewTypeCombobox.addOption(
          this.binaryViewTypeCombobox.createOption(viewObject.label, viewObject.type));
    }
    this.toolbar.appendToolbarItem(this.binaryViewTypeCombobox);

    const copyButton = new UI.Toolbar.ToolbarButton(i18nString(UIStrings.copyToClipboard), 'copy');
    copyButton.addEventListener(UI.Toolbar.ToolbarButton.Events.CLICK, _event => {
      this.copySelectedViewToClipboard();
    }, this);
    this.toolbar.appendToolbarItem(copyButton);

    this.copiedText = new UI.Toolbar.ToolbarText();
    this.copiedText.element.classList.add('binary-view-copied-text');
    this.toolbar.appendChild(this.copiedText.element);

    this.addFadeoutSettimeoutId = null;

    this.lastView = null;
    this.updateView();
  }

  private getCurrentViewObject(): BinaryViewObject|null {
    const filter = (obj: BinaryViewObject): boolean => obj.type === this.binaryViewTypeSetting.get();
    const binaryViewObject = this.binaryViewObjects.find(filter);
    console.assert(
        Boolean(binaryViewObject),
        `No binary view found for binary view type found in setting 'binary-view-type': ${
            this.binaryViewTypeSetting.get()}`);
    return binaryViewObject || null;
  }

  private copySelectedViewToClipboard(): void {
    const viewObject = this.getCurrentViewObject();
    if (!viewObject) {
      return;
    }
    Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(viewObject.content());
    this.copiedText.setText(viewObject.copiedMessage);
    this.copiedText.element.classList.remove('fadeout');
    function addFadeoutClass(this: BinaryResourceView): void {
      this.copiedText.element.classList.add('fadeout');
    }
    if (this.addFadeoutSettimeoutId) {
      clearTimeout(this.addFadeoutSettimeoutId);
      this.addFadeoutSettimeoutId = null;
    }
    this.addFadeoutSettimeoutId = window.setTimeout(addFadeoutClass.bind(this), 2000);
  }

  private updateView(): void {
    const newViewObject = this.getCurrentViewObject();
    if (!newViewObject) {
      return;
    }

    const newView = newViewObject.getView();
    if (newView === this.lastView) {
      return;
    }

    if (this.lastView) {
      this.lastView.detach();
    }
    this.lastView = newView;

    newView.show(this.element, this.toolbar);
    this.binaryViewTypeCombobox.element.value = this.binaryViewTypeSetting.get();
  }

  private binaryViewTypeChanged(): void {
    const selectedOption = (this.binaryViewTypeCombobox.selectedOption());
    if (!selectedOption) {
      return;
    }
    const newViewType = selectedOption.value;
    if (this.binaryViewTypeSetting.get() === newViewType) {
      return;
    }
    this.binaryViewTypeSetting.set(newViewType);
    this.updateView();
  }

  addCopyToContextMenu(contextMenu: UI.ContextMenu.ContextMenu, submenuItemText: string): void {
    const copyMenu = contextMenu.clipboardSection().appendSubMenuItem(submenuItemText, false, 'copy');
    const footerSection = copyMenu.footerSection();

    footerSection.appendItem(i18nString(UIStrings.copyAsBase), async () => {
      const content = this.binaryResourceViewFactory.base64();
      Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(content);
    }, {jslogContext: 'copy-as-base'});
    footerSection.appendItem(i18nString(UIStrings.copyAsHex), async () => {
      const content = await this.binaryResourceViewFactory.hex();
      Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(content);
    }, {jslogContext: 'copy-as-hex'});
    footerSection.appendItem(i18nString(UIStrings.copyAsUtf), async () => {
      const content = await this.binaryResourceViewFactory.utf8();
      Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(content);
    }, {jslogContext: 'copy-as-utf'});
  }
}

export class BinaryViewObject {
  type: string;
  label: string;
  copiedMessage: string;
  content: () => string;
  private createViewFn: () => UI.Widget.Widget;
  private view: UI.Widget.Widget|null;

  constructor(
      type: string, label: string, copiedMessage: string, createViewFn: () => UI.Widget.Widget, content: () => string) {
    this.type = type;
    this.label = label;
    this.copiedMessage = copiedMessage;
    this.content = content;
    this.createViewFn = createViewFn;

    this.view = null;
  }

  getView(): UI.Widget.Widget {
    if (!this.view) {
      this.view = this.createViewFn();
    }
    return this.view;
  }
}
