// 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 Extras from '../extras/extras.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';

export const UIStrings = {
  /**
   * @description Title of an insight that identifies multiple copies of the same JavaScript sources, and recommends removing the duplication.
   */
  title: 'Duplicated JavaScript',
  /**
   * @description Description of an insight that identifies multiple copies of the same JavaScript sources, and recommends removing the duplication.
   */
  description:
      'Remove large, [duplicate JavaScript modules](https://developer.chrome.com/docs/performance/insights/duplicated-javascript) from bundles to reduce unnecessary bytes consumed by network activity.',
  /** Label for a column in a data table; entries will be the locations of JavaScript or CSS code, e.g. the name of a Javascript package or module. */
  columnSource: 'Source',
  /** Label for a column in a data table; entries will be the number of wasted bytes due to duplication of a web resource. */
  columnDuplicatedBytes: 'Duplicated bytes',
} as const;

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

export type DuplicatedJavaScriptInsightModel = InsightModel<typeof UIStrings, {
  duplication: Extras.ScriptDuplication.ScriptDuplication,
  duplicationGroupedByNodeModules: Extras.ScriptDuplication.ScriptDuplication,
  scriptsWithDuplication: Handlers.ModelHandlers.Scripts.Script[],
  scripts: Handlers.ModelHandlers.Scripts.Script[],
  mainDocumentUrl: string,
}>;

function finalize(partialModel: PartialInsightModel<DuplicatedJavaScriptInsightModel>):
    DuplicatedJavaScriptInsightModel {
  const requests = partialModel.scriptsWithDuplication.map(script => script.request).filter(e => !!e);

  return {
    insightKey: InsightKeys.DUPLICATE_JAVASCRIPT,
    strings: UIStrings,
    title: i18nString(UIStrings.title),
    description: i18nString(UIStrings.description),
    docs: 'https://developer.chrome.com/docs/performance/insights/duplicated-javascript',
    category: InsightCategory.LCP,
    state: Boolean(partialModel.duplication.values().next().value) ? 'fail' : 'pass',
    relatedEvents: [...new Set(requests)],
    ...partialModel,
  };
}

export function isDuplicatedJavaScriptInsight(model: InsightModel): model is DuplicatedJavaScriptInsightModel {
  return model.insightKey === InsightKeys.DUPLICATE_JAVASCRIPT;
}

export function generateInsight(
    data: Handlers.Types.HandlerData, context: InsightSetContext): DuplicatedJavaScriptInsightModel {
  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);
  });

  const compressionRatios = new Map<string, number>();
  for (const script of scripts) {
    if (script.request) {
      compressionRatios.set(script.request.args.data.requestId, estimateCompressionRatioForScript(script));
    }
  }

  const {duplication, duplicationGroupedByNodeModules} =
      Extras.ScriptDuplication.computeScriptDuplication({scripts}, compressionRatios);
  const scriptsWithDuplication = [...duplication.values().flatMap(data => data.duplicates.map(d => d.script))];

  const wastedBytesByRequestId = new Map<string, number>();
  for (const {duplicates} of duplication.values()) {
    for (let i = 1; i < duplicates.length; i++) {
      const sourceData = duplicates[i];
      if (!sourceData.script.request) {
        continue;
      }

      const transferSize = sourceData.attributedSize;
      const requestId = sourceData.script.request.args.data.requestId;
      wastedBytesByRequestId.set(requestId, (wastedBytesByRequestId.get(requestId) || 0) + transferSize);
    }
  }

  return finalize({
    duplication,
    duplicationGroupedByNodeModules,
    scriptsWithDuplication: [...new Set(scriptsWithDuplication)],
    scripts,
    mainDocumentUrl: context.navigation?.args.data?.url ?? data.Meta.mainFrameURL,
    metricSavings: metricSavingsForWastedBytes(wastedBytesByRequestId, context),
    wastedBytes: wastedBytesByRequestId.values().reduce((acc, cur) => acc + cur, 0),
  });
}

export function createOverlays(model: DuplicatedJavaScriptInsightModel): Types.Overlays.Overlay[] {
  return model.scriptsWithDuplication.map(script => script.request).filter(e => !!e).map(request => {
    return {
      type: 'ENTRY_OUTLINE',
      entry: request,
      outlineReason: 'ERROR',
    };
  });
}
