// Copyright 2017 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 Trace from '../../models/trace/trace.js';
import * as DataGrid from '../../ui/legacy/components/data_grid/data_grid.js';
import * as UI from '../../ui/legacy/legacy.js';
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';

import {Category, IsLong} from './TimelineFilters.js';
import type {TimelineModeViewDelegate} from './TimelinePanel.js';
import {selectionIsEvent, type TimelineSelection} from './TimelineSelection.js';
import {TimelineTreeView} from './TimelineTreeView.js';
import {TimelineUIUtils} from './TimelineUIUtils.js';

const UIStrings = {
  /**
   * @description Text for the start time of an activity
   */
  startTime: 'Start time',
  /**
   * @description Screen reader label for a select box that filters the Performance panel Event Log by duration.
   */
  durationFilter: 'Duration filter',
  /**
   * @description Text for everything
   */
  all: 'All',
} as const;
const str_ = i18n.i18n.registerUIStrings('panels/timeline/EventsTimelineTreeView.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
export class EventsTimelineTreeView extends TimelineTreeView {
  private readonly filtersControl: Filters;
  private readonly delegate: TimelineModeViewDelegate;
  private currentTree!: Trace.Extras.TraceTree.Node;
  constructor(delegate: TimelineModeViewDelegate) {
    super();
    this.element.setAttribute('jslog', `${VisualLogging.pane('event-log').track({resize: true})}`);
    this.filtersControl = new Filters();
    this.filtersControl.addEventListener(Events.FILTER_CHANGED, this.onFilterChanged, this);
    this.init();
    this.delegate = delegate;
    this.dataGrid.markColumnAsSortedBy('start-time', DataGrid.DataGrid.Order.Ascending);
    this.splitWidget.showBoth();
  }

  override filters(): Trace.Extras.TraceFilter.TraceFilter[] {
    return [...super.filters(), ...this.filtersControl.filters()];
  }

  override set activeSelection(selection: TimelineSelection) {
    super.activeSelection = selection;
    if (selectionIsEvent(selection)) {
      this.selectEvent(selection.event, true);
    }
  }

  override buildTree(): Trace.Extras.TraceTree.Node {
    this.currentTree = this.buildTopDownTree(true, null);
    return this.currentTree;
  }

  private onFilterChanged(): void {
    const lastSelectedNode = this.lastSelectedNode();
    const selectedEvent = lastSelectedNode?.event;
    this.refreshTree();
    if (selectedEvent) {
      this.selectEvent(selectedEvent, false);
    }
  }

  private selectEvent(event: Trace.Types.Events.Event, expand?: boolean): void {
    const node = this.eventToTreeNode.get(event);
    if (!node) {
      return;
    }
    this.selectProfileNode(node, false);
    if (expand) {
      const dataGridNode = this.dataGridNodeForTreeNode(node);
      if (dataGridNode) {
        dataGridNode.expand();
      }
    }
  }

  override populateColumns(columns: DataGrid.DataGrid.ColumnDescriptor[]): void {
    columns.push({
      id: 'start-time',
      title: i18nString(UIStrings.startTime),
      width: '80px',
      fixedWidth: true,
      sortable: true,
    });
    super.populateColumns(columns);
    columns.filter(c => c.fixedWidth).forEach(c => {
      c.width = '80px';
    });
  }

  override populateToolbar(toolbar: UI.Toolbar.Toolbar): void {
    super.populateToolbar(toolbar);
    this.filtersControl.populateToolbar(toolbar);
  }

  override showDetailsForNode(node: Trace.Extras.TraceTree.Node): boolean {
    const parsedTrace = this.parsedTrace;
    if (!parsedTrace) {
      return false;
    }
    const traceEvent = node.event;
    if (!traceEvent) {
      return false;
    }
    void TimelineUIUtils.buildTraceEventDetails(parsedTrace, traceEvent, this.linkifier, false, null)
        .then(fragment => this.detailsView.element.appendChild(fragment));
    return true;
  }

  override onHover(node: Trace.Extras.TraceTree.Node|null): void {
    this.delegate.highlightEvent(node?.event ?? null);
  }
}

export class Filters extends Common.ObjectWrapper.ObjectWrapper<EventTypes> {
  private readonly categoryFilter: Category;
  private readonly durationFilter: IsLong;
  readonly #filters: Array<IsLong|Category>;
  constructor() {
    super();
    this.categoryFilter = new Category();
    this.durationFilter = new IsLong();
    this.#filters = [this.categoryFilter, this.durationFilter];
  }

  filters(): Trace.Extras.TraceFilter.TraceFilter[] {
    return this.#filters;
  }

  populateToolbar(toolbar: UI.Toolbar.Toolbar): void {
    const durationFilterUI = new UI.Toolbar.ToolbarComboBox(
        durationFilterChanged.bind(this), i18nString(UIStrings.durationFilter), undefined, 'duration');
    for (const durationMs of Filters.durationFilterPresetsMs) {
      durationFilterUI.addOption(durationFilterUI.createOption(
          durationMs ? `≥ ${i18n.TimeUtilities.millisToString(durationMs)}` : i18nString(UIStrings.all),
          String(durationMs)));
    }
    toolbar.appendToolbarItem(durationFilterUI);

    const categoryFiltersUI = new Map<string, UI.Toolbar.ToolbarCheckbox>();
    const categories = Trace.Styles.getCategoryStyles();
    for (const categoryName in categories) {
      const category = categories[categoryName as Trace.Styles.EventCategory];
      if (!category.visible) {
        continue;
      }
      const checkbox = new UI.Toolbar.ToolbarCheckbox(
          category.title, undefined, categoriesFilterChanged.bind(this, categoryName as Trace.Styles.EventCategory),
          categoryName);
      checkbox.setChecked(true);
      categoryFiltersUI.set(category.name, checkbox);
      toolbar.appendToolbarItem(checkbox);
    }

    function durationFilterChanged(this: Filters): void {
      const duration = (durationFilterUI.selectedOption() as HTMLOptionElement).value;
      const minimumRecordDuration = parseInt(duration, 10);
      this.durationFilter.setMinimumRecordDuration(Trace.Types.Timing.Milli(minimumRecordDuration));
      this.notifyFiltersChanged();
    }

    function categoriesFilterChanged(this: Filters, name: Trace.Styles.EventCategory): void {
      const categories = Trace.Styles.getCategoryStyles();
      const checkBox = categoryFiltersUI.get(name);
      categories[name].hidden = !checkBox?.checked();
      this.notifyFiltersChanged();
    }
  }

  private notifyFiltersChanged(): void {
    this.dispatchEventToListeners(Events.FILTER_CHANGED);
  }

  private static readonly durationFilterPresetsMs = [0, 1, 15];
}

const enum Events {
  FILTER_CHANGED = 'FilterChanged',
}

interface EventTypes {
  [Events.FILTER_CHANGED]: void;
}
