// 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 '../../core/text_utils/text_utils.js';
import * as Buttons from '../../ui/components/buttons/buttons.js';
import * as SourceFrame from '../../ui/legacy/components/source_frame/source_frame.js';
import * as UI from '../../ui/legacy/legacy.js';
import {html, nothing, render} from '../../ui/lit/lit.js';
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';

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

const {widget} = UI.Widget;

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 Option label in binary resource view of the Network panel to view data in hexadecimal (Hex) format.
   */
  hexViewer: '`Hex` viewer',
  /**
   * @description Status message in binary resource view of the Network panel after copying text in hexadecimal (Hex) format. Hex is short for hexadecimal.
   */
  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 in hexadecimal (Hex) format. Hex is short for hexadecimal.
   */
  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 interface ViewInput {
  binaryViewObjects: BinaryViewObject[];
  content: TextUtils.StreamingContentData.StreamingContentData;
  contentUrl: Platform.DevToolsPath.UrlString;
  resourceType: Common.ResourceType.ResourceType;
  activePositionPercentage: number;
  binaryViewTypeSetting: Common.Settings.Setting<string>;
  binaryViewTypeChanged: (event: Event) => void;
  copySelectedViewToClipboard: () => void;
  copiedText: UI.Toolbar.ToolbarText;
}
export type ViewOutput = undefined;
export type View = (input: ViewInput, output: ViewOutput, target: HTMLElement) => void;

const defaultView: View = (input, _output, target) => {
  // clang-format off
  render(html`
    <style>${binaryResourceViewStyles}</style>
    <div class="vbox flex-auto">
      ${input.binaryViewObjects.map(
        obj => obj.type === input.binaryViewTypeSetting.get() ? html`
          <devtools-widget
            class="widget vbox flex-auto"
            ${widget((e: HTMLElement) => {
              const factory = new SourceFrame.BinaryResourceViewFactory.BinaryResourceViewFactory(
                  input.content, input.contentUrl, input.resourceType);
              let view: SourceFrame.ResourceSourceFrame.ResourceSourceFrame|
                  SourceFrame.StreamingContentHexView.StreamingContentHexView;
              switch (obj.type) {
                case 'base64':
                  view = factory.createBase64View(e);
                  break;
                case 'hex':
                  view = factory.createHexView(e);
                  break;
                case 'utf8':
                  view = factory.createUtf8View(e);
                  break;
                default:
                  throw new Error('Unsupported view type');
              }
              if ('setPositionPercentage' in view) {
                view.setPositionPercentage(input.activePositionPercentage);
              }
              return view;
            })}>
          </devtools-widget>
        ` : nothing,
      )}
      <devtools-toolbar class="binary-view-toolbar">
          <select class="toolbar-item" title=${i18nString(UIStrings.binaryViewType)}
              aria-label=${i18nString(UIStrings.binaryViewType)}
              @change=${input.binaryViewTypeChanged}>
            ${input.binaryViewObjects.map(viewObject => html`
              <option value=${viewObject.type}
                  ?selected=${viewObject.type === input.binaryViewTypeSetting.get()}
                  jslog=${VisualLogging.item(viewObject.type).track({click: true})}>${viewObject.label}</option>
            `)}
          </select>
        <devtools-button class="toolbar-button toolbar-item" title=${i18nString(UIStrings.copyToClipboard)}
            @click=${input.copySelectedViewToClipboard}
            .data=${{
              variant: Buttons.Button.Variant.ICON,
              iconName: 'copy',
              jslogContext: 'copy',
            } as Buttons.Button.ButtonData}></devtools-button>
        ${input.copiedText.element}
      </devtools-toolbar>
    </div>
  `, target);
  // clang-format on
};

export class BinaryResourceView extends UI.Widget.VBox {
  private activePositionPercentage = 0;
  private readonly binaryResourceViewFactory: SourceFrame.BinaryResourceViewFactory.BinaryResourceViewFactory;
  private readonly streamingContent: TextUtils.StreamingContentData.StreamingContentData;
  private readonly contentUrl: Platform.DevToolsPath.UrlString;
  private readonly resourceType: Common.ResourceType.ResourceType;
  private readonly binaryViewObjects: BinaryViewObject[];
  private binaryViewTypeSetting: Common.Settings.Setting<string>;
  readonly copiedText: UI.Toolbar.ToolbarText;
  private addFadeoutSettimeoutId: number|null;
  private litContainer: HTMLElement;
  #view = defaultView;

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

    this.streamingContent = content;
    this.contentUrl = contentUrl;
    this.resourceType = resourceType;

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

    this.binaryViewObjects = [
      new BinaryViewObject(
          'base64', i18n.i18n.lockedString('Base64'), i18nString(UIStrings.copiedAsBase),
          this.binaryResourceViewFactory.base64.bind(this.binaryResourceViewFactory)),
      new BinaryViewObject(
          'hex', i18nString(UIStrings.hexViewer), i18nString(UIStrings.copiedAsHex),
          this.binaryResourceViewFactory.hex.bind(this.binaryResourceViewFactory)),
      new BinaryViewObject(
          'utf8', i18n.i18n.lockedString('UTF-8'), i18nString(UIStrings.copiedAsUtf),
          this.binaryResourceViewFactory.utf8.bind(this.binaryResourceViewFactory)),
    ];
    this.binaryViewTypeSetting = Common.Settings.Settings.instance().createSetting('binary-view-type', 'hex');

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

    this.addFadeoutSettimeoutId = null;

    this.litContainer = this.element.createChild('div', 'vbox flex-auto');

    this.performUpdate();
  }

  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;
  }

  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');
    const addFadeoutClass = (): void => {
      this.copiedText.element.classList.add('fadeout');
    };
    if (this.addFadeoutSettimeoutId) {
      clearTimeout(this.addFadeoutSettimeoutId);
      this.addFadeoutSettimeoutId = null;
    }
    this.addFadeoutSettimeoutId = window.setTimeout(addFadeoutClass, 2000);
  }

  override performUpdate(): void {
    const viewInput: ViewInput = {
      activePositionPercentage: this.activePositionPercentage,
      content: this.streamingContent,
      contentUrl: this.contentUrl,
      resourceType: this.resourceType,
      binaryViewObjects: this.binaryViewObjects,
      binaryViewTypeSetting: this.binaryViewTypeSetting,
      binaryViewTypeChanged: this.binaryViewTypeChanged.bind(this),
      copySelectedViewToClipboard: this.copySelectedViewToClipboard.bind(this),
      copiedText: this.copiedText,
    };
    this.#view(viewInput, undefined, this.litContainer);
  }

  binaryViewTypeChanged(event: Event): void {
    const newViewType = (event.target as HTMLSelectElement).value;
    if (this.binaryViewTypeSetting.get() === newViewType) {
      return;
    }

    const currentViewWidget = this.litContainer.querySelector('devtools-widget');
    if (currentViewWidget) {
      const view = UI.Widget.Widget.get(currentViewWidget) as SourceFrame.ResourceSourceFrame.ResourceSourceFrame |
          SourceFrame.StreamingContentHexView.StreamingContentHexView;
      if (view && 'getPositionPercentage' in view) {
        this.activePositionPercentage = view.getPositionPercentage();
      }
    }

    this.binaryViewTypeSetting.set(newViewType);
    this.performUpdate();
  }

  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;

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