// 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 i18n from '../../../core/i18n/i18n.js';
import * as Platform from '../../../core/platform/platform.js';
import type * as Handlers from '../handlers/handlers.js';
import * as Helpers from '../helpers/helpers.js';
import * as Types from '../types/types.js';

import {
  InsightCategory,
  InsightKeys,
  type InsightModel,
  type InsightSetContext,
  type PartialInsightModel,
} from './types.js';

export const UIStrings = {
  /** Title of an insight that provides details about the fonts used on the page, and the value of their `font-display` properties. */
  title: 'Font display',
  /**
   * @description Text to tell the user about the font-display CSS feature to help improve a the UX of a page.
   */
  description:
      'Consider setting [`font-display`](https://developer.chrome.com/docs/performance/insights/font-display) to `swap` or `optional` to ensure text is consistently visible. `swap` can be further optimized to mitigate layout shifts with [font metric overrides](https://developer.chrome.com/blog/font-fallbacks).',
  /** Column for a font loaded by the page to render text. */
  fontColumn: 'Font',
  /** Column for the amount of time wasted. */
  wastedTimeColumn: 'Wasted time',
} as const;

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

export interface RemoteFont {
  name?: string;
  request: Types.Events.SyntheticNetworkRequest;
  display: string;
  wastedTime: Types.Timing.Milli;
}

export type FontDisplayInsightModel = InsightModel<typeof UIStrings, {
  fonts: RemoteFont[],
}>;

function finalize(partialModel: PartialInsightModel<FontDisplayInsightModel>): FontDisplayInsightModel {
  return {
    insightKey: InsightKeys.FONT_DISPLAY,
    strings: UIStrings,
    title: i18nString(UIStrings.title),
    description: i18nString(UIStrings.description),
    docs: 'https://developer.chrome.com/docs/performance/insights/font-display',
    category: InsightCategory.INP,
    state: partialModel.fonts.find(font => font.wastedTime > 0) ? 'fail' : 'pass',
    ...partialModel,
  };
}

export function isFontDisplayInsight(model: InsightModel): model is FontDisplayInsightModel {
  return model.insightKey === InsightKeys.FONT_DISPLAY;
}

export function generateInsight(data: Handlers.Types.HandlerData, context: InsightSetContext): FontDisplayInsightModel {
  const fonts: RemoteFont[] = [];
  for (const remoteFont of data.LayoutShifts.remoteFonts) {
    const event = remoteFont.beginRemoteFontLoadEvent;
    if (!Helpers.Timing.eventIsInBounds(event, context.bounds)) {
      continue;
    }

    const requestId = `${event.pid}.${event.args.id}`;
    const request = data.NetworkRequests.byId.get(requestId);
    if (!request) {
      continue;
    }

    if (!/^(block|fallback|auto)$/.test(remoteFont.display)) {
      continue;
    }

    const wastedTimeMicro =
        Types.Timing.Micro(request.args.data.syntheticData.finishTime - request.args.data.syntheticData.sendStartTime);
    // TODO(crbug.com/352244504): should really end at the time of the next Commit trace event.
    let wastedTime =
        Platform.NumberUtilities.floor(Helpers.Timing.microToMilli(wastedTimeMicro), 1 / 5) as Types.Timing.Milli;
    if (wastedTime === 0) {
      continue;
    }

    // All browsers wait for no more than 3s.
    wastedTime = Math.min(wastedTime, 3000) as Types.Timing.Milli;

    fonts.push({
      name: remoteFont.name,
      request,
      display: remoteFont.display,
      wastedTime,
    });
  }

  fonts.sort((a, b) => b.wastedTime - a.wastedTime);

  const savings = Math.max(...fonts.map(f => f.wastedTime)) as Types.Timing.Milli;

  return finalize({
    relatedEvents: fonts.map(f => f.request),
    fonts,
    metricSavings: {FCP: savings},
  });
}

export function createOverlays(model: FontDisplayInsightModel): Types.Overlays.Overlay[] {
  return model.fonts.map(font => ({
                           type: 'ENTRY_OUTLINE',
                           entry: font.request,
                           outlineReason: font.wastedTime ? 'ERROR' : 'INFO',
                         }));
}
