async function adaptInsightToAuditProduct(artifacts, context, insightName, createDetails) {
  const {insights, parsedTrace} = await getInsightSet(artifacts, context);
  if (!insights) {
    return {
      scoreDisplayMode: Audit.SCORING_MODES.NOT_APPLICABLE,
      score: null,
    };
  }

  const insight = insights.model[insightName];
  if (insight instanceof Error) {
    return {
      errorMessage: insight.message,
      errorStack: insight.stack,
      score: null,
    };
  }

  const details = createDetails(insight, {
    parsedTrace,
    insights,
  });
  if (!details || (details.type === 'table' && details.items.length === 0)) {
    return {
      scoreDisplayMode: Audit.SCORING_MODES.NOT_APPLICABLE,
      score: null,
      details,
    };
  }

  if (insight.wastedBytes !== undefined) {
    if (!details.debugData) {
      details.debugData = {type: 'debugdata'};
    }
    details.debugData.wastedBytes = insight.wastedBytes;
  }

  // This hack is to add metric adorners if an insight category links it to a metric,
  // but doesn't output a metric savings for that metric.
  let metricSavings = insight.metricSavings;
  if (insight.category === 'INP' && !metricSavings?.INP) {
    metricSavings = {...metricSavings, INP: /** @type {any} */ (0)};
  } else if (insight.category === 'CLS' && !metricSavings?.CLS) {
    metricSavings = {...metricSavings, CLS: /** @type {any} */ (0)};
  } else if (insight.category === 'LCP' && !metricSavings?.LCP) {
    metricSavings = {...metricSavings, LCP: /** @type {any} */ (0)};
  }

  // TODO: consider adding a `estimatedSavingsText` to InsightModel, which can capture
  // the exact i18n string used by RPP; and include the same est. timing savings.
  let displayValue;
  if (insight.wastedBytes) {
    displayValue = str_(i18n.UIStrings.displayValueByteSavings, {wastedBytes: insight.wastedBytes});
  }

  let score;
  let scoreDisplayMode;
  if (insight.state === 'fail' || insight.state === 'pass') {
    score = insight.state === 'fail' ? 0 : 1;
    scoreDisplayMode =
      insight.metricSavings ? Audit.SCORING_MODES.METRIC_SAVINGS : Audit.SCORING_MODES.NUMERIC;
  } else {
    score = null;
    scoreDisplayMode = Audit.SCORING_MODES.INFORMATIVE;
  }

  return {
    scoreDisplayMode,
    score,
    metricSavings,
    warnings: insight.warnings,
    displayValue,
    details,
  };
}
@param {LH.Artifacts} artifacts
@param {LH.Audit.Context} context
@param {T} insightName
@param {(insight: import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModels[T], extras: CreateDetailsExtras) => LH.Audit.Details|undefined} createDetails
@template {keyof import('@paulirish/trace_engine/models/trace/insights/types.js').InsightModelsType} T
@return {Promise<LH.Audit.Product>}