// 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.

import '../../../ui/kit/kit.js';

import * as i18n from '../../../core/i18n/i18n.js';
import * as Platform from '../../../core/platform/platform.js';
import * as SDK from '../../../core/sdk/sdk.js';
import * as Trace from '../../../models/trace/trace.js';
import * as PerfUI from '../../../ui/legacy/components/perf_ui/perf_ui.js';
import * as UI from '../../../ui/legacy/legacy.js';
import * as Lit from '../../../ui/lit/lit.js';
import * as TimelineUtils from '../utils/utils.js';

import networkRequestTooltipStyles from './networkRequestTooltip.css.js';
import {colorForNetworkRequest, networkResourceCategory} from './Utils.js';

const {html, nothing, Directives: {classMap, ifDefined}} = Lit;
const {widget} = UI.Widget;

const MAX_URL_LENGTH = 60;

const UIStrings = {
  /**
   * @description Text that refers to the priority of network request
   */
  priority: 'Priority',
  /**
   * @description Text for the duration of a network request
   */
  duration: 'Duration',
  /**
   * @description Text that refers to the queueing and connecting time of a network request
   */
  queuingAndConnecting: 'Queuing and connecting',
  /**
   * @description Text that refers to the request sent and waiting time of a network request
   */
  requestSentAndWaiting: 'Request sent and waiting',
  /**
   * @description Text that refers to the content downloading time of a network request
   */
  contentDownloading: 'Content downloading',
  /**
   * @description Text that refers to the waiting on main thread time of a network request
   */
  waitingOnMainThread: 'Waiting on main thread',
  /**
   * @description Text that refers to a network request is render-blocking
   */
  renderBlocking: 'Render-blocking',
  /**
   * @description Text to refer to the list of redirects.
   */
  redirects: 'Redirects',
  /**
   * @description Cell title in Network Data Grid Node of the Network panel
   * @example {Fast 4G} PH1
   */
  wasThrottled: 'Request was throttled ({PH1})',
} as const;

const str_ = i18n.i18n.registerUIStrings('panels/timeline/components/NetworkRequestTooltip.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);

interface ViewInput {
  networkRequest: Trace.Types.Events.SyntheticNetworkRequest;
  entityMapper?: Trace.EntityMapper.EntityMapper;
  throttlingTitle?: string;
}

type View = (input: ViewInput, output: undefined, target: HTMLElement) => void;

export const DEFAULT_VIEW: View = (input, output, target) => {
  const {
    networkRequest,
    entityMapper,
    throttlingTitle,
  } = input;

  const chipStyle = {
    backgroundColor: `${colorForNetworkRequest(networkRequest)}`,
  };
  const url = new URL(networkRequest.args.data.url);
  const entity = entityMapper ? entityMapper.entityForEvent(networkRequest) : null;
  const originWithEntity = TimelineUtils.Helpers.formatOriginWithEntity(url, entity, true);

  const redirectsHtml = NetworkRequestTooltip.renderRedirects(networkRequest);

  // clang-format off
  Lit.render(html`
    <style>${networkRequestTooltipStyles}</style>
    <div class="performance-card">
      <div class="url">${Platform.StringUtilities.trimMiddle(url.href.replace(url.origin, ''), MAX_URL_LENGTH)}</div>
      <div class="url url--host">${originWithEntity}</div>

      <div class="divider"></div>
      <div class="network-category">
        <span class="network-category-chip" style=${Lit.Directives.styleMap(chipStyle)}>
        </span>${networkResourceCategory(networkRequest)}
      </div>
      <div class="priority-row">${i18nString(UIStrings.priority)}: ${NetworkRequestTooltip.renderPriorityValue(networkRequest)}</div>
      ${throttlingTitle ? html`
        <div class="throttled-row">
          ${i18nString(UIStrings.wasThrottled, {PH1: throttlingTitle})}
        </div>` : nothing}
      ${Trace.Helpers.Network.isSyntheticNetworkRequestEventRenderBlocking(networkRequest) ?
        html`<div class="render-blocking"> ${i18nString(UIStrings.renderBlocking)} </div>` :  Lit.nothing
      }
      <div class="divider"></div>

      ${NetworkRequestTooltip.renderTimings(networkRequest)}

      ${redirectsHtml ? html `
        <div class="divider"></div>
        ${redirectsHtml}
      ` : Lit.nothing}
    </div>
  `, target);
  // clang-format on
};

export class NetworkRequestTooltip extends UI.Widget.Widget {
  static createWidgetElement(
      request: Trace.Types.Events.SyntheticNetworkRequest,
      entityMapper?: Trace.EntityMapper.EntityMapper): Lit.LitTemplate {
    return html`${widget(NetworkRequestTooltip, {networkRequest: request, entityMapper})}`;
  }

  #view: View;
  #networkRequest?: Trace.Types.Events.SyntheticNetworkRequest;
  #entityMapper?: Trace.EntityMapper.EntityMapper;

  constructor(element?: HTMLElement, view: View = DEFAULT_VIEW) {
    super(element, {useShadowDom: true});
    this.#view = view;
  }

  set networkRequest(networkRequest: Trace.Types.Events.SyntheticNetworkRequest) {
    this.#networkRequest = networkRequest;
    this.requestUpdate();
  }

  set entityMapper(entityMapper: Trace.EntityMapper.EntityMapper|undefined) {
    this.#entityMapper = entityMapper;
    this.requestUpdate();
  }

  static renderPriorityValue(networkRequest: Trace.Types.Events.SyntheticNetworkRequest): Lit.TemplateResult {
    if (networkRequest.args.data.priority === networkRequest.args.data.initialPriority) {
      return html`${PerfUI.NetworkPriorities.uiLabelForNetworkPriority(networkRequest.args.data.priority)}`;
    }
    return html`${PerfUI.NetworkPriorities.uiLabelForNetworkPriority(networkRequest.args.data.initialPriority)}
        <devtools-icon name="arrow-forward" class="priority"></devtools-icon>
        ${PerfUI.NetworkPriorities.uiLabelForNetworkPriority(networkRequest.args.data.priority)}`;
  }

  static renderTimings(networkRequest: Trace.Types.Events.SyntheticNetworkRequest): Lit.TemplateResult|null {
    const syntheticData = networkRequest.args.data.syntheticData;
    const queueing = (syntheticData.sendStartTime - networkRequest.ts) as Trace.Types.Timing.Micro;
    const requestPlusWaiting = (syntheticData.downloadStart - syntheticData.sendStartTime) as Trace.Types.Timing.Micro;
    const download = (syntheticData.finishTime - syntheticData.downloadStart) as Trace.Types.Timing.Micro;
    const waitingOnMainThread =
        (networkRequest.ts + networkRequest.dur - syntheticData.finishTime) as Trace.Types.Timing.Micro;

    const color = colorForNetworkRequest(networkRequest);
    const styleForWaiting = {
      backgroundColor: `color-mix(in srgb, ${color}, hsla(0, 100%, 100%, 0.8))`,
    };
    const styleForDownloading = {
      backgroundColor: color,
    };

    const sdkNetworkRequest = SDK.TraceObject.RevealableNetworkRequest.create(networkRequest);
    const wasThrottled = sdkNetworkRequest &&
        SDK.NetworkManager.MultitargetNetworkManager.instance().appliedRequestConditions(
            sdkNetworkRequest.networkRequest);
    const throttledTitle = wasThrottled ? i18nString(UIStrings.wasThrottled, {
      PH1: typeof wasThrottled.conditions.title === 'string' ? wasThrottled.conditions.title :
                                                               wasThrottled.conditions.title()
    }) :
                                          undefined;

    // The outside spans are transparent with a border on the outside edge.
    // The inside spans are 1px tall rectangles, vertically centered, with background color.
    //                   |
    //                   |----
    //   whisker-left->  |  ^ horizontal
    const leftWhisker = html`<span class="whisker-left"> <span class="horizontal"></span> </span>`;
    const rightWhisker = html`<span class="whisker-right"> <span class="horizontal"></span> </span>`;

    const classes = classMap({
      ['timings-row timings-row--duration']: true,
      throttled: Boolean(wasThrottled?.urlPattern),
    });
    return html`
      <div
        class=${classes}
        title=${ifDefined(throttledTitle)}>
        ${
        wasThrottled?.urlPattern ? html`<devtools-icon
          class=indicator
          name=watch
          ></devtools-icon>` :
                                   html`<span class="indicator"></span>`}
        ${i18nString(UIStrings.duration)}
         <span class="time"> ${i18n.TimeUtilities.formatMicroSecondsTime(networkRequest.dur)} </span>
      </div>
      <div class="timings-row">
        ${leftWhisker}
        ${i18nString(UIStrings.queuingAndConnecting)}
        <span class="time"> ${i18n.TimeUtilities.formatMicroSecondsTime(queueing)} </span>
      </div>
      <div class="timings-row">
        <span class="indicator" style=${Lit.Directives.styleMap(styleForWaiting)}></span>
        ${i18nString(UIStrings.requestSentAndWaiting)}
        <span class="time"> ${i18n.TimeUtilities.formatMicroSecondsTime(requestPlusWaiting)} </span>
      </div>
      <div class="timings-row">
        <span class="indicator" style=${Lit.Directives.styleMap(styleForDownloading)}></span>
        ${i18nString(UIStrings.contentDownloading)}
        <span class="time"> ${i18n.TimeUtilities.formatMicroSecondsTime(download)} </span>
      </div>
      <div class="timings-row">
        ${rightWhisker}
        ${i18nString(UIStrings.waitingOnMainThread)}
        <span class="time"> ${i18n.TimeUtilities.formatMicroSecondsTime(waitingOnMainThread)} </span>
      </div>
    `;
  }

  static renderRedirects(networkRequest: Trace.Types.Events.SyntheticNetworkRequest): Lit.TemplateResult|null {
    const redirectRows = [];
    if (networkRequest.args.data.redirects.length > 0) {
      redirectRows.push(html`
        <div class="redirects-row">
          ${i18nString(UIStrings.redirects)}
        </div>
      `);
      for (const redirect of networkRequest.args.data.redirects) {
        redirectRows.push(html`<div class="redirects-row"> ${redirect.url}</div>`);
      }
      return html`${redirectRows}`;
    }

    return null;
  }

  override performUpdate(): void {
    if (!this.#networkRequest) {
      return;
    }

    // TODO(crbug.com/466124088): Seems broken.
    const sdkNetworkRequest = SDK.TraceObject.RevealableNetworkRequest.create(this.#networkRequest);
    const networkConditions = sdkNetworkRequest &&
        SDK.NetworkManager.MultitargetNetworkManager.instance().appliedRequestConditions(
            sdkNetworkRequest.networkRequest);
    let throttlingTitle: string|undefined = undefined;
    if (networkConditions) {
      throttlingTitle = typeof networkConditions.conditions.title === 'string' ? networkConditions.conditions.title :
                                                                                 networkConditions.conditions.title();
    }

    const input: ViewInput = {
      networkRequest: this.#networkRequest,
      entityMapper: this.#entityMapper,
      throttlingTitle,
    };
    this.#view(input, undefined, this.contentElement);
  }
}
