// Copyright 2025 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 LegacyJavaScriptLib from '../../../third_party/legacy-javascript/legacy-javascript.js';
import type * as Handlers from '../handlers/handlers.js';
import * as Helpers from '../helpers/helpers.js';
import type * as Types from '../types/types.js';

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

const {detectLegacyJavaScript} = LegacyJavaScriptLib.LegacyJavaScript;

export const UIStrings = {
  /**
   * @description Title of an insight that identifies polyfills for modern JavaScript features, and recommends their removal.
   */
  title: 'Legacy JavaScript',
  /**
   * @description Description of an insight that identifies polyfills for modern JavaScript features, and recommends their removal.
   */
  description:
      'Polyfills and transforms enable older browsers to use new JavaScript features. However, many aren\'t necessary for modern browsers. Consider modifying your JavaScript build process to not transpile [Baseline](https://web.dev/articles/baseline-and-polyfills) features, unless you know you must support older browsers. [Learn why most sites can deploy ES6+ code without transpiling](https://developer.chrome.com/docs/performance/insights/legacy-javascript)',
  /** Label for a column in a data table; entries will be the individual JavaScript scripts. */
  columnScript: 'Script',
  /** Label for a column in a data table; entries will be the number of wasted bytes (aka the estimated savings in terms of bytes). */
  columnWastedBytes: 'Wasted bytes',
} as const;

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

export interface PatternMatchResult {
  name: string;
  line: number;
  column: number;
}

interface LegacyJavaScriptResult {
  matches: PatternMatchResult[];
  estimatedByteSavings: number;
}

type LegacyJavaScriptResults = Map<Handlers.ModelHandlers.Scripts.Script, LegacyJavaScriptResult>;

export type LegacyJavaScriptInsightModel = InsightModel<typeof UIStrings, {
  legacyJavaScriptResults: LegacyJavaScriptResults,
}>;

const BYTE_THRESHOLD = 5000;

function finalize(partialModel: PartialInsightModel<LegacyJavaScriptInsightModel>): LegacyJavaScriptInsightModel {
  const requests = [...partialModel.legacyJavaScriptResults.keys()].map(script => script.request).filter(e => !!e);

  return {
    insightKey: InsightKeys.LEGACY_JAVASCRIPT,
    strings: UIStrings,
    title: i18nString(UIStrings.title),
    description: i18nString(UIStrings.description),
    docs: 'https://developer.chrome.com/docs/performance/insights/legacy-javascript',
    category: InsightCategory.ALL,
    state: requests.length ? 'fail' : 'pass',
    relatedEvents: [...new Set(requests)],
    ...partialModel,
  };
}

export function isLegacyJavaScript(model: InsightModel): model is LegacyJavaScriptInsightModel {
  return model.insightKey === InsightKeys.LEGACY_JAVASCRIPT;
}

export function generateInsight(
    data: Handlers.Types.HandlerData, context: InsightSetContext): LegacyJavaScriptInsightModel {
  const scripts = data.Scripts.scripts.filter(script => {
    if (script.frame !== context.frameId) {
      return false;
    }

    if (script.url?.startsWith('chrome-extension://')) {
      return false;
    }

    return Helpers.Timing.timestampIsInBounds(context.bounds, script.ts) ||
        (script.request && Helpers.Timing.eventIsInBounds(script.request, context.bounds));
  });

  const legacyJavaScriptResults: LegacyJavaScriptResults = new Map();
  const wastedBytesByRequestId = new Map<string, number>();

  for (const script of scripts) {
    if (!script.content || script.content.length < BYTE_THRESHOLD) {
      continue;
    }

    const result = detectLegacyJavaScript(script.content, script.sourceMap);
    if (result.estimatedByteSavings < BYTE_THRESHOLD) {
      continue;
    }

    // Translate from resource size to transfer size.
    const compressionRatio = estimateCompressionRatioForScript(script);
    const transferSize = Math.round(result.estimatedByteSavings * compressionRatio);
    result.estimatedByteSavings = transferSize;

    legacyJavaScriptResults.set(script, result);

    if (script.request) {
      const requestId = script.request.args.data.requestId;
      wastedBytesByRequestId.set(requestId, transferSize);
    }
  }

  const sorted =
      new Map([...legacyJavaScriptResults].sort((a, b) => b[1].estimatedByteSavings - a[1].estimatedByteSavings));

  return finalize({
    legacyJavaScriptResults: sorted,
    metricSavings: metricSavingsForWastedBytes(wastedBytesByRequestId, context),
    wastedBytes: wastedBytesByRequestId.values().reduce((acc, cur) => acc + cur, 0),
  });
}
export function createOverlays(model: LegacyJavaScriptInsightModel): Types.Overlays.Overlay[] {
  return [...model.legacyJavaScriptResults.keys()].map(script => script.request).filter(e => !!e).map(request => {
    return {
      type: 'ENTRY_OUTLINE',
      entry: request,
      outlineReason: 'ERROR',
    };
  });
}
