// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import {App} from '../index.mjs'

import {FocusEvent, ToolTipEvent} from './events.mjs';
import {CollapsableElement, DOM, groupBy, LazyTable} from './helper.mjs';

DOM.defineCustomElement(
    'view/list-panel',
    (templateText) => class ListPanel extends CollapsableElement {
      _selectedLogEntries = [];
      _displayedLogEntries = [];
      _timeline;

      _detailsClickHandler = this._handleDetailsClick.bind(this);
      _logEntryClickHandler = this._handleLogEntryClick.bind(this);
      _logEntryMouseOverHandler = this._logEntryMouseOverHandler.bind(this);

      constructor() {
        super(templateText);
        this.groupKey.addEventListener('change', e => this.requestUpdate());
        this.showAllRadio.onclick = _ => this._showEntries(this._timeline);
        this.showTimerangeRadio.onclick = _ =>
            this._showEntries(this._timeline.selectionOrSelf);
        this.showSelectionRadio.onclick = _ =>
            this._showEntries(this._selectedLogEntries);
      }

      static get observedAttributes() {
        return ['title'];
      }

      attributeChangedCallback(name, oldValue, newValue) {
        if (name == 'title') {
          this.$('#title').innerHTML = newValue;
        }
      }

      set timeline(timeline) {
        console.assert(timeline !== undefined, 'timeline undefined!');
        this._timeline = timeline;
        this.$('.panel').style.display =
            timeline.isEmpty() ? 'none' : 'inherit';
        this._initGroupKeySelect();
      }

      set selectedLogEntries(entries) {
        if (entries === this._timeline) {
          this.showAllRadio.click();
        } else if (entries === this._timeline.selection) {
          this.showTimerangeRadio.click();
        } else {
          this._selectedLogEntries = entries;
          this.showSelectionRadio.click();
        }
      }

      get entryClass() {
        return this._timeline.at(0)?.constructor;
      }

      get groupKey() {
        return this.$('#group-key');
      }

      get table() {
        return this.$('#table');
      }

      get showAllRadio() {
        return this.$('#show-all');
      }

      get showTimerangeRadio() {
        return this.$('#show-timerange');
      }

      get showSelectionRadio() {
        return this.$('#show-selection');
      }

      get _propertyNames() {
        return this.entryClass?.propertyNames ?? [];
      }

      _initGroupKeySelect() {
        const select = this.groupKey;
        select.options.length = 0;
        for (const propertyName of this._propertyNames) {
          const option = DOM.element('option');
          option.text = propertyName;
          select.add(option);
        }
      }

      _showEntries(entries) {
        this._displayedLogEntries = entries;
        this.requestUpdate();
      }

      _update() {
        if (this._timeline.isEmpty()) return;
        DOM.removeAllChildren(this.table);
        if (this._displayedLogEntries.length == 0) return;
        const propertyName = this.groupKey.selectedOptions[0].text;
        const groups = groupBy(
            this._displayedLogEntries, each => each[propertyName], true);
        this._render(groups, this.table);
      }

      createSubgroups(group) {
        const map = new Map();
        const tempGroups = [];
        for (let propertyName of this._propertyNames) {
          map.set(
              propertyName,
              groupBy(group.entries, each => each[propertyName], true));
        }
        return map;
      }

      _handleLogEntryClick(e) {
        const group = e.currentTarget.group;
        this.dispatchEvent(new FocusEvent(group.key));
      }

      _logEntryMouseOverHandler(e) {
        const group = e.currentTarget.group;
        this.dispatchEvent(
            new ToolTipEvent(group.key, e.currentTarget, e.ctrlKey));
      }

      _handleDetailsClick(event) {
        event.stopPropagation();
        const tr = event.target.parentNode;
        const group = tr.group;
        // Create subgroup in-place if the don't exist yet.
        if (tr.groups === undefined) {
          const groups = tr.groups = this.createSubgroups(group);
          this.renderDrilldown(groups, tr);
        }
        const detailsTr = tr.nextSibling;
        if (tr.classList.contains('open')) {
          tr.classList.remove('open');
          detailsTr.style.display = 'none';
        } else {
          tr.classList.add('open');
          detailsTr.style.display = 'table-row';
        }
      }

      renderDrilldown(groups, previousSibling) {
        const tr = DOM.tr('entry-details');
        tr.style.display = 'none';
        // indent by one td.
        tr.appendChild(DOM.td());
        const td = DOM.td();
        td.colSpan = 3;
        groups.forEach((group, key) => {
          this.renderDrilldownGroup(td, group, key);
        });
        tr.appendChild(td);
        // Append the new TR after previousSibling.
        previousSibling.parentNode.insertBefore(
            tr, previousSibling.nextSibling);
      }

      renderDrilldownGroup(td, groups, key) {
        const div = DOM.div('drilldown-group-title');
        div.textContent = `Grouped by ${key}: ${groups[0]?.parentTotal ?? 0}#`;
        td.appendChild(div);
        const table = DOM.table();
        this._render(groups, table, false)
        td.appendChild(table);
      }

      _render(groups, table) {
        let last;
        new LazyTable(table, groups, group => {
          last = group;
          const tr = DOM.tr();
          tr.group = group;
          const details = tr.appendChild(DOM.td('', 'toggle'));
          details.onclick = this._detailsClickHandler;
          tr.appendChild(DOM.td(`${group.percent.toFixed(2)}%`, 'percentage'));
          tr.appendChild(DOM.td(group.length, 'count'));
          const valueTd = tr.appendChild(DOM.td(group.key?.toString(), 'key'));
          if (App.isClickable(group.key)) {
            tr.onclick = this._logEntryClickHandler;
            tr.onmouseover = this._logEntryMouseOverHandler;
            valueTd.classList.add('clickable');
          }
          return tr;
        }, 10);
      }
    });
