// Copyright 2024 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 rulesdir/no-imperative-dom-api */

import * as Common from '../../core/common/common.js';
import * as i18n from '../../core/i18n/i18n.js';
import * as SDK from '../../core/sdk/sdk.js';
import type * as Protocol from '../../generated/protocol.js';
import type * as DataGrid from '../../ui/legacy/components/data_grid/data_grid.js';
import * as UI from '../../ui/legacy/legacy.js';

import {HeapDetachedElementsDataGrid, HeapDetachedElementsDataGridNode} from './HeapDetachedElementsDataGrid.js';
import {
  type DataDisplayDelegate,
  ProfileEvents as ProfileTypeEvents,
  type ProfileHeader,
  ProfileType,
} from './ProfileHeader.js';
import {WritableProfileHeader} from './ProfileView.js';

const UIStrings = {
  /**
   * @description Button text to obtain the detached elements retained by JS
   */
  startDetachedElements: 'Obtain detached elements',
  /**
   * @description The title for the collection of profiles that are gathered from various snapshots of the heap, using a sampling (e.g. every 1/100) technique.
   */
  detachedElementsTitle: 'Detached elements',
  /**
   * @description Description in Heap Profile View of a profiler tool
   */
  detachedElementsDescription: 'Detached elements shows objects that are retained by a JS reference.',
  /**
   * @description Name of a profile
   * @example {2} PH1
   */
  detachedElementProfile: 'Detached elements {PH1}',
} as const;
const str_ = i18n.i18n.registerUIStrings('panels/profiler/HeapDetachedElementsView.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);

export class DetachedElementsProfileView extends UI.View.SimpleView implements DataDisplayDelegate {
  readonly selectedSizeText: UI.Toolbar.ToolbarText;
  dataGrid: DataGrid.DataGrid.DataGridImpl<unknown>;
  profile: DetachedElementsProfileHeader;
  readonly parentDataDisplayDelegate: DataDisplayDelegate;

  constructor(dataDisplayDelegate: DataDisplayDelegate, profile: DetachedElementsProfileHeader) {
    super({
      title: i18nString(UIStrings.detachedElementsTitle),
      viewId: 'detached-elements',
    });
    this.element.classList.add('detached-elements-view');
    this.profile = profile;
    this.parentDataDisplayDelegate = dataDisplayDelegate;
    this.selectedSizeText = new UI.Toolbar.ToolbarText();
    this.dataGrid = new HeapDetachedElementsDataGrid();
    this.populateElementsGrid(profile.detachedElements);
    this.dataGrid.asWidget().show(this.element);
  }

  showProfile(profile: ProfileHeader|null): UI.Widget.Widget|null {
    return this.parentDataDisplayDelegate.showProfile(profile);
  }

  showObject(objectId: string, perspectiveName: string): void {
    this.parentDataDisplayDelegate.showObject(objectId, perspectiveName);
  }

  async linkifyObject(): Promise<Element|null> {
    return null;
  }

  populateElementsGrid(detachedElements: Protocol.DOM.DetachedElementInfo[]|null): void {
    if (!detachedElements) {
      return;
    }

    const heapProfilerModel = this.profile.heapProfilerModel();
    const domModel = heapProfilerModel?.target().model(SDK.DOMModel.DOMModel);
    if (!domModel) {
      return;
    }

    for (const detachedElement of detachedElements) {
      this.dataGrid.rootNode().appendChild(new HeapDetachedElementsDataGridNode(detachedElement, domModel));
    }
  }

  override async toolbarItems(): Promise<UI.Toolbar.ToolbarItem[]> {
    return [...await super.toolbarItems(), this.selectedSizeText];
  }
}

export class DetachedElementsProfileType extends
    Common.ObjectWrapper.eventMixin<DetachedElementsProfileType.EventTypes, typeof ProfileType>(ProfileType) {
  constructor(typeId?: string, description?: string) {
    super(
        typeId || i18nString(UIStrings.detachedElementsTitle),
        description || i18nString(UIStrings.detachedElementsTitle));
  }

  override profileBeingRecorded(): DetachedElementsProfileHeader|null {
    return super.profileBeingRecorded() as DetachedElementsProfileHeader | null;
  }

  override get buttonTooltip(): Common.UIString.LocalizedString {
    return i18nString(UIStrings.startDetachedElements);
  }

  override buttonClicked(): boolean {
    void this.getDetachedElements();
    return false;
  }

  async getDetachedElements(): Promise<void> {
    if (this.profileBeingRecorded()) {
      return;
    }
    const heapProfilerModel = UI.Context.Context.instance().flavor(SDK.HeapProfilerModel.HeapProfilerModel);
    const target = heapProfilerModel?.target();
    const domModel = target?.model(SDK.DOMModel.DOMModel);
    if (!heapProfilerModel || !target || !domModel) {
      return;
    }

    const animationModel = target?.model(SDK.AnimationModel.AnimationModel);
    if (animationModel) {
      // TODO(b/406904348): Remove this once we correctly release animations on the backend.
      await animationModel.releaseAllAnimations();
    }
    const data = await domModel.getDetachedDOMNodes();

    const profile: DetachedElementsProfileHeader = new DetachedElementsProfileHeader(heapProfilerModel, this, data);
    this.addProfile(profile);

    this.dispatchEventToListeners(ProfileTypeEvents.PROFILE_COMPLETE, profile);
  }

  override get treeItemTitle(): Common.UIString.LocalizedString {
    return i18nString(UIStrings.detachedElementsTitle);
  }

  override get description(): string {
    return i18nString(UIStrings.detachedElementsDescription);
  }

  override isInstantProfile(): boolean {
    return true;
  }

  // eslint-disable-next-line @typescript-eslint/naming-convention
  static readonly TypeId = 'DetachedElements';
}

export namespace DetachedElementsProfileType {
  export const enum Events {
    RECORDING_STOPPED = 'RecordingStopped',
    STATS_UPDATE = 'StatsUpdate',
    DETACHED_ELEMENTS_OBTAINED = 'DetachedElementsObtained',
  }

  export interface EventTypes {
    [Events.RECORDING_STOPPED]: void;
    [Events.STATS_UPDATE]: Protocol.HeapProfiler.SamplingHeapProfile|null;
    [Events.DETACHED_ELEMENTS_OBTAINED]: Protocol.DOM.DetachedElementInfo[]|null;
  }
}

export class DetachedElementsProfileHeader extends WritableProfileHeader {
  readonly #heapProfilerModel: SDK.HeapProfilerModel.HeapProfilerModel|null;
  readonly detachedElements: Protocol.DOM.DetachedElementInfo[]|null;
  constructor(
      heapProfilerModel: SDK.HeapProfilerModel.HeapProfilerModel|null, type: DetachedElementsProfileType,
      detachedElements: Protocol.DOM.DetachedElementInfo[]|null, title?: string) {
    super(
        heapProfilerModel?.debuggerModel() ?? null, type,
        title || i18nString(UIStrings.detachedElementProfile, {PH1: type.nextProfileUid()}));
    this.detachedElements = detachedElements;
    this.#heapProfilerModel = heapProfilerModel;
  }

  override createView(dataDisplayDelegate: DataDisplayDelegate): DetachedElementsProfileView {
    return new DetachedElementsProfileView(dataDisplayDelegate, this);
  }

  heapProfilerModel(): SDK.HeapProfilerModel.HeapProfilerModel|null {
    return this.#heapProfilerModel;
  }

  override profileType(): DetachedElementsProfileType {
    return super.profileType() as DetachedElementsProfileType;
  }
}
