// 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 * as Common from '../../core/common/common.js';
import * as i18n from '../../core/i18n/i18n.js';

import * as Handlers from './handlers/handlers.js';
import type {ParsedTrace} from './ModelImpl.js';
import {getEventStyle} from './Styles.js';
import * as Types from './types/types.js';

const UIStrings = {
  /**
   * @description Text shown for an entry in the flame chart that has no explicit name.
   */
  anonymous: '(anonymous)',
  /**
   * @description Text used to show an EventDispatch event which has a type associated with it
   * @example {click} PH1
   */
  eventDispatchS: 'Event: {PH1}',
  /**
   * @description Text shown for an entry in the flame chart that represents a frame.
   */
  frame: 'Frame',
  /**
   * @description Text in Timeline Flame Chart Data Provider of the Performance panel
   */
  wsConnectionOpened: 'WebSocket opened',
  /**
   * @description Text in Timeline Flame Chart Data Provider of the Performance panel
   * @example {ws://example.com} PH1
   */
  wsConnectionOpenedWithUrl: 'WebSocket opened: {PH1}',
  /**
   * @description Text in Timeline Flame Chart Data Provider of the Performance panel
   */
  wsConnectionClosed: 'WebSocket closed',
  /**
   * @description Text in Timeline Flame Chart Data Provider of the Performance panel
   */
  layoutShift: 'Layout shift',
} as const;

const str_ = i18n.i18n.registerUIStrings('models/trace/Name.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);

/**
 * Calculates the display name for a given entry.
 * @param parsedTrace If the trace data is provided
 * as the second argument it can be used to find source map resolved names for
 * profile calls.
 * Use this function to customize the user visible name for an entry. If no
 * custom name is found, we will fallback to the `name` property in the trace
 * entry.
 */
export function forEntry(
    entry: Types.Events.Event,
    parsedTrace?: ParsedTrace,
    ): string {
  if (Types.Events.isProfileCall(entry)) {
    if (parsedTrace) {
      const potentialCallName =
          Handlers.ModelHandlers.Samples.getProfileCallFunctionName(parsedTrace.data.Samples, entry);
      // We need this extra check because the call name could be the empty
      // string. If it is, we want to fallback.
      if (potentialCallName) {
        return potentialCallName;
      }
    }
    return entry.callFrame.functionName || i18nString(UIStrings.anonymous);
  }

  if (Types.Events.isLegacyTimelineFrame(entry)) {
    return i18n.i18n.lockedString(UIStrings.frame);
  }

  if (Types.Events.isDispatch(entry)) {
    // EventDispatch represent user actions such as clicks, so in this case
    // rather than show the event title (which is always just "Event"), we
    // add the type ("click") to help the user understand the event.
    return i18nString(UIStrings.eventDispatchS, {PH1: entry.args.data.type});
  }
  if (Types.Events.isSyntheticNetworkRequest(entry)) {
    const parsedURL = new Common.ParsedURL.ParsedURL(entry.args.data.url);
    const text =
        parsedURL.isValid ? `${parsedURL.displayName} (${parsedURL.host})` : entry.args.data.url || 'Network request';
    return text;
  }

  if (Types.Events.isWebSocketCreate(entry)) {
    if (entry.args.data.url) {
      return i18nString(UIStrings.wsConnectionOpenedWithUrl, {PH1: entry.args.data.url});
    }

    return i18nString(UIStrings.wsConnectionOpened);
  }

  if (Types.Events.isWebSocketDestroy(entry)) {
    return i18nString(UIStrings.wsConnectionClosed);
  }

  if (Types.Events.isSyntheticInteraction(entry)) {
    return nameForInteractionEvent(entry);
  }

  if (Types.Events.isSyntheticLayoutShift(entry)) {
    return i18nString(UIStrings.layoutShift);
  }

  if (Types.Events.isSyntheticAnimation(entry) && entry.args.data.beginEvent.args.data.displayName) {
    return entry.args.data.beginEvent.args.data.displayName;
  }

  const eventStyleCustomName = getEventStyle(entry.name as Types.Events.Name)?.title;

  return eventStyleCustomName || entry.name;
}

function nameForInteractionEvent(event: Types.Events.SyntheticInteractionPair): string {
  const category = Handlers.ModelHandlers.UserInteractions.categoryOfInteraction(event);
  // Because we hide nested interactions, we do not want to show the
  // specific type of the interaction that was not hidden, so instead we
  // show just the category of that interaction.
  if (category === 'OTHER') {
    return 'Other';
  }
  if (category === 'KEYBOARD') {
    return 'Keyboard';
  }
  if (category === 'POINTER') {
    return 'Pointer';
  }
  return event.type;
}
