// 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 type * as Common from '../../core/common/common.js';
import * as i18n from '../../core/i18n/i18n.js';
import * as DataGrid from '../../ui/legacy/components/data_grid/data_grid.js';
import * as SourceFrame from '../../ui/legacy/components/source_frame/source_frame.js';
import * as UI from '../../ui/legacy/legacy.js';
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';

import eventDisplayTableStyles from './eventDisplayTable.css.js';
import type {PlayerEvent} from './MediaModel.js';

const UIStrings = {
  /**
   * @description Text for timestamps of items
   */
  timestamp: 'Timestamp',
  /**
   * @description The column header for event names.
   */
  eventName: 'Event name',
  /**
   * @description Text for the value of something
   */
  value: 'Value',
  /**
   * @description The accessible name of a table that displays information about events that occurred
   * while a video/media player was present on the page.
   */
  eventDisplay: 'Event display',
} as const;
const str_ = i18n.i18n.registerUIStrings('panels/media/EventDisplayTable.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
export interface EventDisplayColumnConfig {
  id: string;
  title: string;
  sortable: boolean;
  weight?: number;
}

export const enum MediaEventColumnKeys {
  TIMESTAMP = 'display-timestamp',
  EVENT = 'event',
  VALUE = 'value',
}

export class EventNode extends DataGrid.DataGrid.DataGridNode<EventNode> {
  private expandableElement: SourceFrame.JSONView.JSONView|null;

  constructor(event: PlayerEvent) {
    super(event, false);
    this.expandableElement = null;
  }

  override createCell(columnId: string): HTMLElement {
    const cell = this.createTD(columnId);
    const cellData = this.data[columnId] as string;
    if (columnId === MediaEventColumnKeys.VALUE) {
      const enclosed = cell.createChild('div', 'event-display-table-contents-json-wrapper');
      this.expandableElement =
          new SourceFrame.JSONView.JSONView(new SourceFrame.JSONView.ParsedJSON(cellData, '', ''), true);
      this.expandableElement.markAsRoot();
      this.expandableElement.show(enclosed);
    } else {
      cell.classList.add('event-display-table-basic-text-table-entry');
      UI.UIUtils.createTextChild(cell, cellData);
    }
    return cell;
  }
}

export class PlayerEventsView extends UI.Widget.VBox {
  private readonly dataGrid: DataGrid.DataGrid.DataGridImpl<EventNode>;
  private firstEventTime: number;

  constructor() {
    super({jslog: `${VisualLogging.pane('events')}`});
    this.registerRequiredCSS(eventDisplayTableStyles);

    // Set up element styles.

    this.contentElement.classList.add('event-display-table-contents-table-container');

    this.dataGrid = this.createDataGrid([
      {
        id: MediaEventColumnKeys.TIMESTAMP,
        title: i18nString(UIStrings.timestamp),
        weight: 1,
        sortable: false,
      },
      {id: MediaEventColumnKeys.EVENT, title: i18nString(UIStrings.eventName), weight: 2, sortable: false},
      {
        id: MediaEventColumnKeys.VALUE,
        title: i18nString(UIStrings.value),
        weight: 7,
        sortable: false,
      },
    ]);

    this.firstEventTime = 0;
    this.dataGrid.setStriped(true);
    this.dataGrid.asWidget().show(this.contentElement);
  }

  private createDataGrid(headers: EventDisplayColumnConfig[]): DataGrid.DataGrid.DataGridImpl<EventNode> {
    const gridColumnDescs = [];
    for (const headerDesc of headers) {
      gridColumnDescs.push(PlayerEventsView.convertToGridDescriptor(headerDesc));
    }

    // TODO(tmathmeyer) SortableDataGrid doesn't play nice with nested JSON
    // renderers, since they can change size, and this breaks the visible
    // element computation in ViewportDataGrid.
    const datagrid = new DataGrid.DataGrid.DataGridImpl({
      displayName: i18nString(UIStrings.eventDisplay),
      columns: gridColumnDescs,
    });
    datagrid.asWidget().contentElement.classList.add('no-border-top-datagrid');
    return datagrid;
  }

  onEvent(event: PlayerEvent): void {
    if (this.firstEventTime === 0 && typeof event.timestamp === 'number') {
      this.firstEventTime = event.timestamp;
    }

    event = this.subtractFirstEventTime(event);
    const stringified = event.value;
    try {
      const json = JSON.parse(stringified);
      event.event = json.event;
      delete json['event'];
      event.value = json;
      const node = new EventNode(event);
      const scroll = this.dataGrid.scrollContainer;
      const isAtBottom = scroll.scrollTop === (scroll.scrollHeight - scroll.offsetHeight);
      this.dataGrid.rootNode().appendChild(node as DataGrid.DataGrid.DataGridNode<EventNode>);
      if (isAtBottom) {
        scroll.scrollTop = scroll.scrollHeight;
      }
    } catch {
      // If this is a legacy message event, ignore it for now until they
      // are handled.
    }
  }

  private subtractFirstEventTime(event: PlayerEvent): PlayerEvent {
    if (typeof event.timestamp === 'number') {
      event.displayTimestamp = (event.timestamp - this.firstEventTime).toFixed(3);
    }
    return event;
  }

  private static convertToGridDescriptor(columnConfig: EventDisplayColumnConfig): DataGrid.DataGrid.ColumnDescriptor {
    return {
      id: columnConfig.id,
      title: columnConfig.title as Common.UIString.LocalizedString,
      sortable: columnConfig.sortable,
      weight: columnConfig.weight || 0,
      sort: DataGrid.DataGrid.Order.Ascending,
    };
  }
}
