// Copyright 2014 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 * as Common from '../../core/common/common.js';
import * as i18n from '../../core/i18n/i18n.js';
import * as Formatter from '../../models/formatter/formatter.js';
import * as Persistence from '../../models/persistence/persistence.js';
import * as TextUtils from '../../models/text_utils/text_utils.js';
import * as Workspace from '../../models/workspace/workspace.js';
import * as UI from '../../ui/legacy/legacy.js';

import {
  type EditorAction,
  type EditorClosedEvent,
  Events,
  registerEditorAction,
  type SourcesView,
} from './SourcesView.js';
import type {UISourceCodeFrame} from './UISourceCodeFrame.js';

const UIStrings = {
  /**
   * @description Title of the format button in the Sources panel
   * @example {file name} PH1
   */
  formatS: 'Format {PH1}',
  /**
   * @description Tooltip text that appears when hovering over the largeicon pretty print button in the Inplace Formatter Editor Action of the Sources panel
   */
  format: 'Format',
} as const;
const str_ = i18n.i18n.registerUIStrings('panels/sources/InplaceFormatterEditorAction.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);

let inplaceFormatterEditorActionInstance: InplaceFormatterEditorAction;

export class InplaceFormatterEditorAction implements EditorAction {
  private button!: UI.Toolbar.ToolbarButton;
  private sourcesView!: SourcesView;
  private uiSourceCodeTitleChangedEvent: Common.EventTarget.EventDescriptor|null = null;
  static instance(opts: {
    forceNew: boolean|null,
  } = {forceNew: null}): InplaceFormatterEditorAction {
    const {forceNew} = opts;
    if (!inplaceFormatterEditorActionInstance || forceNew) {
      inplaceFormatterEditorActionInstance = new InplaceFormatterEditorAction();
    }

    return inplaceFormatterEditorActionInstance;
  }

  private editorSelected(event: Common.EventTarget.EventTargetEvent<Workspace.UISourceCode.UISourceCode>): void {
    const uiSourceCode = event.data;
    this.updateButton(uiSourceCode);
  }

  private editorClosed(event: Common.EventTarget.EventTargetEvent<EditorClosedEvent>): void {
    const {wasSelected} = event.data;
    if (wasSelected) {
      this.updateButton(null);
    }
  }

  private updateButton(uiSourceCode: Workspace.UISourceCode.UISourceCode|null): void {
    if (this.uiSourceCodeTitleChangedEvent) {
      Common.EventTarget.removeEventListeners([this.uiSourceCodeTitleChangedEvent]);
    }
    this.uiSourceCodeTitleChangedEvent = uiSourceCode ?
        uiSourceCode.addEventListener(
            Workspace.UISourceCode.Events.TitleChanged, event => this.updateButton(event.data), this) :
        null;
    const isFormattable = this.isFormattable(uiSourceCode);
    this.button.element.classList.toggle('hidden', !isFormattable);
    if (uiSourceCode && isFormattable) {
      this.button.setTitle(i18nString(UIStrings.formatS, {PH1: uiSourceCode.name()}));
    }
  }

  getOrCreateButton(sourcesView: SourcesView): UI.Toolbar.ToolbarButton {
    if (this.button) {
      return this.button;
    }

    this.sourcesView = sourcesView;
    this.sourcesView.addEventListener(Events.EDITOR_SELECTED, this.editorSelected.bind(this));
    this.sourcesView.addEventListener(Events.EDITOR_CLOSED, this.editorClosed.bind(this));

    this.button = new UI.Toolbar.ToolbarButton(i18nString(UIStrings.format), 'brackets');
    this.button.addEventListener(UI.Toolbar.ToolbarButton.Events.CLICK, this.formatSourceInPlace, this);
    this.updateButton(sourcesView.currentUISourceCode());

    return this.button;
  }

  private isFormattable(uiSourceCode: Workspace.UISourceCode.UISourceCode|null): boolean {
    if (!uiSourceCode) {
      return false;
    }
    // Only show Format button for editable files
    if (!Persistence.Persistence.PersistenceImpl.instance().hasEditableContent(uiSourceCode)) {
      return false;
    }
    // Only show Format button for JavaScript files. For other file types (JSON, CSS),
    // the pretty-print toggle in the status bar should be used instead, which provides
    // reversible formatting (fixes issue 378870233).
    const mimeType = Common.ResourceType.ResourceType.simplifyContentType(uiSourceCode.mimeType());
    return Common.ResourceType.ResourceType.isJavaScriptMimeType(mimeType);
  }

  private formatSourceInPlace(): void {
    const sourceFrame = this.sourcesView.currentSourceFrame();
    if (!sourceFrame) {
      return;
    }
    const uiSourceCode = sourceFrame.uiSourceCode();
    if (!this.isFormattable(uiSourceCode)) {
      return;
    }

    if (uiSourceCode.isDirty()) {
      void this.contentLoaded(uiSourceCode, sourceFrame, uiSourceCode.workingCopy());
    } else {
      void uiSourceCode.requestContentData()
          .then(contentDataOrError => TextUtils.ContentData.ContentData.textOr(contentDataOrError, ''))
          .then(content => {
            void this.contentLoaded(uiSourceCode, sourceFrame, content);
          });
    }
  }

  private async contentLoaded(
      uiSourceCode: Workspace.UISourceCode.UISourceCode, sourceFrame: UISourceCodeFrame,
      content: string): Promise<void> {
    const {formattedContent, formattedMapping} =
        await Formatter.ScriptFormatter.format(uiSourceCode.contentType(), sourceFrame.contentType, content);
    if (uiSourceCode.workingCopy() === formattedContent) {
      return;
    }
    const selection = sourceFrame.textEditor.toLineColumn(sourceFrame.textEditor.state.selection.main.head);
    const [lineNumber, columnNumber] =
        formattedMapping.originalToFormatted(selection.lineNumber, selection.columnNumber);
    uiSourceCode.setWorkingCopy(formattedContent);
    this.sourcesView.showSourceLocation(uiSourceCode, {lineNumber, columnNumber});
  }
}

registerEditorAction(InplaceFormatterEditorAction.instance);
