// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/* eslint-disable rulesdir/no_underscored_properties */

import * as Common from '../common/common.js';
import * as Components from '../components/components.js';
import * as Host from '../host/host.js';
import * as i18n from '../i18n/i18n.js';
import * as Platform from '../platform/platform.js';
import * as Root from '../root/root.js';
import * as SDK from '../sdk/sdk.js';
import * as ThemeSupport from '../theme_support/theme_support.js';
import * as Timeline from '../timeline/timeline.js';
import * as UI from '../ui/ui.js';
import * as Workspace from '../workspace/workspace.js';

import * as ReportRenderer from './LighthouseReporterTypes.js';  // eslint-disable-line no-unused-vars

export const UIStrings = {
  /**
  *@description Label for view trace button when simulated throttling is enabled
  */
  viewOriginalTrace: 'View Original Trace',
  /**
  *@description Text of the timeline button in Lighthouse Report Renderer
  */
  viewTrace: 'View Trace',
  /**
  *@description Help text for 'View Trace' button
  */
  thePerformanceMetricsAboveAre:
      'The performance metrics above are simulated and won\'t match the timings found in this trace. Disable simulated throttling in "Lighthouse Settings" if you want the timings to match.',
};
const str_ = i18n.i18n.registerUIStrings('lighthouse/LighthouseReportRenderer.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
const MaxLengthForLinks = 40;

// @ts-ignore https://github.com/GoogleChrome/lighthouse/issues/11628
export class LighthouseReportRenderer extends self.ReportRenderer {
  constructor(dom: DOM) {
    super(dom);
  }

  static addViewTraceButton(el: Element, artifacts?: ReportRenderer.RunnerResultArtifacts): void {
    if (!artifacts || !artifacts.traces || !artifacts.traces.defaultPass) {
      return;
    }

    const simulated = artifacts.settings.throttlingMethod === 'simulate';
    const container = el.querySelector('.lh-audit-group');
    if (!container) {
      return;
    }
    const disclaimerEl = container.querySelector('.lh-metrics__disclaimer');
    // If it was a PWA-only run, we'd have a trace but no perf category to add the button to
    if (!disclaimerEl) {
      return;
    }

    const defaultPassTrace = artifacts.traces.defaultPass;
    const label = simulated ? i18nString(UIStrings.viewOriginalTrace) : i18nString(UIStrings.viewTrace);
    const timelineButton = UI.UIUtils.createTextButton(label, onViewTraceClick, 'view-trace');
    if (simulated) {
      UI.Tooltip.Tooltip.install(timelineButton, i18nString(UIStrings.thePerformanceMetricsAboveAre));
    }
    container.insertBefore(timelineButton, disclaimerEl.nextSibling);

    async function onViewTraceClick(): Promise<void> {
      Host.userMetrics.actionTaken(Host.UserMetrics.Action.LighthouseViewTrace);
      await UI.InspectorView.InspectorView.instance().showPanel('timeline');
      Timeline.TimelinePanel.TimelinePanel.instance().loadFromEvents(defaultPassTrace.traceEvents);
    }
  }

  static async linkifyNodeDetails(el: Element): Promise<void> {
    const mainTarget = SDK.SDKModel.TargetManager.instance().mainTarget();
    if (!mainTarget) {
      return;
    }
    const domModel = mainTarget.model(SDK.DOMModel.DOMModel);
    if (!domModel) {
      return;
    }

    for (const origElement of el.getElementsByClassName('lh-node')) {
      const origHTMLElement = origElement as HTMLElement;
      const detailsItem = origHTMLElement.dataset as unknown as ReportRenderer.NodeDetailsJSON;
      if (!detailsItem.path) {
        continue;
      }

      const nodeId = await domModel.pushNodeByPathToFrontend(detailsItem.path);

      if (!nodeId) {
        continue;
      }
      const node = domModel.nodeForId(nodeId);
      if (!node) {
        continue;
      }

      const element = await Common.Linkifier.Linkifier.linkify(
          node, {tooltip: detailsItem.snippet, preventKeyboardFocus: undefined});
      UI.Tooltip.Tooltip.install(origHTMLElement, '');

      const screenshotElement = origHTMLElement.querySelector('.lh-element-screenshot');
      origHTMLElement.textContent = '';
      if (screenshotElement) {
        origHTMLElement.append(screenshotElement);
      }
      origHTMLElement.appendChild(element);
    }
  }

  static async linkifySourceLocationDetails(el: Element): Promise<void> {
    for (const origElement of el.getElementsByClassName('lh-source-location')) {
      const origHTMLElement = origElement as HTMLElement;
      const detailsItem = origHTMLElement.dataset as ReportRenderer.SourceLocationDetailsJSON;
      if (!detailsItem.sourceUrl || !detailsItem.sourceLine || !detailsItem.sourceColumn) {
        continue;
      }
      const url = detailsItem.sourceUrl;
      const line = Number(detailsItem.sourceLine);
      const column = Number(detailsItem.sourceColumn);
      const element = await Components.Linkifier.Linkifier.linkifyURL(url, {
        lineNumber: line,
        columnNumber: column,
        maxLength: MaxLengthForLinks,
        bypassURLTrimming: undefined,
        className: undefined,
        preventClick: undefined,
        tabStop: undefined,
        text: undefined,
      });
      UI.Tooltip.Tooltip.install(origHTMLElement, '');
      origHTMLElement.textContent = '';
      origHTMLElement.appendChild(element);
    }
  }

  static handleDarkMode(el: Element): void {
    if (ThemeSupport.ThemeSupport.instance().themeName() === 'dark') {
      el.classList.add('dark');
    }
  }
}

// @ts-ignore https://github.com/GoogleChrome/lighthouse/issues/11628
export class LighthouseReportUIFeatures extends self.ReportUIFeatures {
  _beforePrint: (() => void)|null;
  _afterPrint: (() => void)|null;

  constructor(dom: DOM) {
    super(dom);
    this._beforePrint = null;
    this._afterPrint = null;
  }

  setBeforePrint(beforePrint: (() => void)|null): void {
    this._beforePrint = beforePrint;
  }

  setAfterPrint(afterPrint: (() => void)|null): void {
    this._afterPrint = afterPrint;
  }

  /**
   * Returns the html that recreates this report.
   */
  getReportHtml(): string {
    this.resetUIState();
    // @ts-ignore https://github.com/GoogleChrome/lighthouse/issues/11628
    return Lighthouse.ReportGenerator.generateReportHtml(this.json);
  }

  /**
   * Downloads a file (blob) using the system dialog prompt.
   */
  async _saveFile(blob: Blob|File): Promise<void> {
    // @ts-ignore https://github.com/GoogleChrome/lighthouse/issues/11628
    const domain = new Common.ParsedURL.ParsedURL(this.json.finalUrl).domain();
    const sanitizedDomain = domain.replace(/[^a-z0-9.-]+/gi, '_');
    // @ts-ignore https://github.com/GoogleChrome/lighthouse/issues/11628
    const timestamp = Platform.DateUtilities.toISO8601Compact(new Date(this.json.fetchTime));
    const ext = blob.type.match('json') ? '.json' : '.html';
    const basename = `${sanitizedDomain}-${timestamp}${ext}`;
    const text = await blob.text();
    Workspace.FileManager.FileManager.instance().save(basename, text, true /* forceSaveAs */);
  }

  async _print(): Promise<void> {
    const document = this.getDocument();
    const clonedReport = (document.querySelector('.lh-root') as HTMLElement).cloneNode(true);
    const printWindow = window.open('', '_blank', 'channelmode=1,status=1,resizable=1');
    if (!printWindow) {
      return;
    }
    const style = printWindow.document.createElement('style');
    style.textContent = Root.Runtime.cachedResources.get('third_party/lighthouse/report-assets/report.css') || '';
    printWindow.document.head.appendChild(style);
    printWindow.document.body.replaceWith(clonedReport);
    // Linkified nodes are shadow elements, which aren't exposed via `cloneNode`.
    await LighthouseReportRenderer.linkifyNodeDetails(clonedReport as HTMLElement);

    if (this._beforePrint) {
      this._beforePrint();
    }
    printWindow.focus();
    printWindow.print();
    printWindow.close();
    if (this._afterPrint) {
      this._afterPrint();
    }
  }

  getDocument(): Document {
    // @ts-ignore https://github.com/GoogleChrome/lighthouse/issues/11628
    return this._document;
  }
  resetUIState(): void {
    // @ts-ignore https://github.com/GoogleChrome/lighthouse/issues/11628
    this._resetUIState();
  }
}
