/**
 * @license
 * Copyright 2018 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import {Protocol as Crdp} from 'devtools-protocol/types/protocol.js';
import * as TraceEngine from '@paulirish/trace_engine';
import * as Lantern from '../core/lib/lantern/lantern.js';

import {parseManifest} from '../core/lib/manifest-parser.js';
import {LighthouseError} from '../core/lib/lh-error.js';
import {NetworkRequest as _NetworkRequest} from '../core/lib/network-request.js';
import speedline from 'speedline-core';
import * as CDTSourceMap from '../core/lib/cdt/generated/SourceMap.js';
import {ArbitraryEqualityMap} from '../core/lib/arbitrary-equality-map.js';
import type { TaskNode as _TaskNode } from '../core/lib/tracehouse/main-thread-tasks.js';
import AuditDetails from './lhr/audit-details.js'
import Config from './config.js';
import Gatherer from './gatherer.js';
import {IEntity} from 'third-party-web';
import {IcuMessage} from './lhr/i18n.js';
import LHResult from './lhr/lhr.js'
import Protocol from './protocol.js';
import Util from './utility-types.js';
import Audit from './audit.js';

export type Artifacts = BaseArtifacts & GathererArtifacts;

/**
 * Artifacts always created by gathering. These artifacts are available to Lighthouse plugins.
 * NOTE: any breaking changes here are considered breaking Lighthouse changes that must be done
 * on a major version bump.
 */
export type BaseArtifacts = UniversalBaseArtifacts & ContextualBaseArtifacts;

/**
 * The set of base artifacts that are available in every mode of Lighthouse operation.
 */
interface UniversalBaseArtifacts {
  /** The ISO-8601 timestamp of when the test page was fetched and artifacts collected. */
  fetchTime: string;
  /** A set of warnings about unexpected things encountered while loading and testing the page. */
  LighthouseRunWarnings: Array<string | IcuMessage>;
  /** The benchmark index that indicates rough device class. */
  BenchmarkIndex: number;
  /** An object containing information about the testing configuration used by Lighthouse. */
  settings: Config.Settings;
  /** The timing instrumentation of the gather portion of a run. */
  Timing: Artifacts.MeasureEntry[];
  /** The host's device pixel ratio. */
  HostDPR: number;
  /** Device which Chrome is running on. */
  HostFormFactor: 'desktop'|'mobile';
  /** The user agent string of the version of Chrome used. */
  HostUserAgent: string;
  /** The product string of the version of Chrome used. Example: HeadlessChrome/123.2.2.0 would be from old headless. */
  HostProduct: string;
  /** Information about how Lighthouse artifacts were gathered. */
  GatherContext: {gatherMode: Gatherer.GatherMode};
}

/**
 * The set of base artifacts whose semantics differ or may be valueless in certain Lighthouse gather modes.
 */
interface ContextualBaseArtifacts {
  /** The URL initially requested and the post-redirects URL that was actually loaded. */
  URL: Artifacts.URL;
  /** If loading the page failed, value is the error that caused it. Otherwise null. */
  PageLoadError: LighthouseError | null;
}

/**
 * Artifacts provided by the default gatherers that are exposed to plugins with a hardended API.
 * NOTE: any breaking changes here are considered breaking Lighthouse changes that must be done
 * on a major version bump.
 */
interface PublicGathererArtifacts {
  /** ConsoleMessages deprecation and intervention warnings, console API calls, and exceptions logged by Chrome during page load. */
  ConsoleMessages: Artifacts.ConsoleMessage[];
  /** The primary log of devtools protocol activity. */
  DevtoolsLog: DevtoolsLog;
  /** Information on size and loading for all the images in the page. Natural size information for `picture` and CSS images is only available if the image was one of the largest 50 images. */
  ImageElements: Artifacts.ImageElement[];
  /** All the link elements on the page or equivalently declared in `Link` headers. @see https://html.spec.whatwg.org/multipage/links.html */
  LinkElements: Artifacts.LinkElement[];
  /** The contents of the main HTML document network resource. */
  MainDocumentContent: string;
  /** The values of the <meta> elements in the head. */
  MetaElements: Array<{name?: string, content?: string, property?: string, httpEquiv?: string, charset?: string, node: Artifacts.NodeDetails}>;
  /** Information on all scripts in the page. */
  Scripts: Artifacts.Script[];
  /** The primary trace taken over the entire run. */
  Trace: Trace;
  /** The dimensions and devicePixelRatio of the loaded viewport. */
  ViewportDimensions: Artifacts.ViewportDimensions;
}

/**
 * Artifacts provided by the default gatherers. Augment this interface when adding additional
 * gatherers. Changes to these artifacts are not considered a breaking Lighthouse change.
 */
export interface GathererArtifacts extends PublicGathererArtifacts {
  /** The results of running the aXe accessibility tests on the page. */
  Accessibility: Artifacts.Accessibility;
  /** Array of all anchors on the page. */
  AnchorElements: Artifacts.AnchorElement[];
  /** Errors when attempting to use the back/forward cache. */
  BFCacheFailures: Artifacts.BFCacheFailure[];
  /** CSS coverage information for styles used by page's final state. */
  CSSUsage: Crdp.CSS.RuleUsage[];
  /** The log of devtools protocol activity if there was a page load error and Chrome navigated to a `chrome-error://` page. */
  DevtoolsLogError: DevtoolsLog;
  /** Information on the document's doctype(or null if not present), specifically the name, publicId, and systemId.
      All properties default to an empty string if not present */
  Doctype: Artifacts.Doctype | null;
  /** All the iframe elements in the page. */
  IFrameElements: Artifacts.IFrameElement[];
  /** All the input elements, including associated form and label elements. */
  Inputs: {inputs: Artifacts.InputElement[]; forms: Artifacts.FormElement[]; labels: Artifacts.LabelElement[]};
  /** Screenshot of the entire page (rather than just the above the fold content). */
  FullPageScreenshot: LHResult.FullPageScreenshot | null;
  /** The issues surfaced in the devtools Issues panel */
  InspectorIssues: Artifacts.InspectorIssues;
  /** JS coverage information for code used during audit. Keyed by script id. */
  // 'url' is excluded because it can be overridden by a magic sourceURL= comment, which makes keeping it a dangerous footgun!
  JsUsage: Record<string, Omit<Crdp.Profiler.ScriptCoverage, 'url'>>;
  /** The user agent string that Lighthouse used to load the page. Set to the empty string if unknown. */
  NetworkUserAgent: string;
  /** Information on fetching and the content of the /robots.txt file. */
  RobotsTxt: {status: number|null, content: string|null, errorMessage?: string};
  /** Source maps of scripts executed in the page. */
  SourceMaps: Array<Artifacts.SourceMap>;
  /** Information on detected tech stacks (e.g. JS libraries) used by the page. */
  Stacks: Artifacts.DetectedStack[];
  /** CSS stylesheets found on the page. */
  Stylesheets: Artifacts.CSSStyleSheetInfo[];
  /** The trace if there was a page load error and Chrome navigated to a `chrome-error://` page. */
  TraceError: Trace;
  /** Elements associated with metrics (ie: Largest Contentful Paint element). */
  TraceElements: Artifacts.TraceElement[];
}

declare module Artifacts {
  type ComputedContext = Util.Immutable<{
    computedCache: Map<string, ArbitraryEqualityMap>;
  }>;

  type NetworkRequest = _NetworkRequest;
  type TaskNode = _TaskNode;
  type TBTImpactTask = TaskNode & {tbtImpact: number, selfTbtImpact: number, selfBlockingTime: number};
  type MetaElement = Artifacts['MetaElements'][0];

  interface URL {
    /**
     * URL of the initially requested URL during a Lighthouse navigation.
     * Will be `undefined` in timespan/snapshot.
     */
    requestedUrl?: string;
    /**
     * URL of the last document request during a Lighthouse navigation.
     * Will be `undefined` in timespan/snapshot.
     */
    mainDocumentUrl?: string;
    /** URL displayed on the page after Lighthouse finishes. */
    finalDisplayedUrl: string;
  }

  type Rect = AuditDetails.Rect;

  interface NodeDetails {
    lhId: string,
    devtoolsNodePath: string,
    selector: string,
    boundingRect: Rect,
    snippet: string,
    nodeLabel: string,
    explanation?: string,
  }

  interface RuleExecutionError {
    name: string;
    message: string;
  }

  interface AxeRuleResult {
    id: string;
    impact?: string;
    tags: Array<string>;
    nodes: Array<{
      target: Array<string|string[]>;
      failureSummary?: string;
      node: NodeDetails;
      relatedNodes: NodeDetails[];
    }>;
    error?: RuleExecutionError;
  }

  interface Accessibility {
    violations: Array<AxeRuleResult>;
    notApplicable: Array<Pick<AxeRuleResult, 'id'>>;
    passes: Array<Pick<AxeRuleResult, 'id'>>;
    incomplete: Array<AxeRuleResult>;
    version: string;
  }

  interface CSSStyleSheetInfo {
    header: Crdp.CSS.CSSStyleSheetHeader;
    content: string;
  }

  interface Doctype {
    name: string;
    publicId: string;
    systemId: string;
    documentCompatMode: string;
  }

  interface IFrameElement {
    /** The `id` attribute of the iframe. */
    id: string,
    /** Details for node in DOM for the iframe element */
    node: NodeDetails,
    /** The `src` attribute of the iframe. */
    src: string,
    /** The iframe's ClientRect. @see https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect */
    clientRect: {
      top: number;
      bottom: number;
      left: number;
      right: number;
      width: number;
      height: number;
    },
    /** If the iframe or an ancestor of the iframe is fixed in position. */
    isPositionFixed: boolean,
  }

  /** @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#Attributes */
  interface LinkElement {
    /** The `rel` attribute of the link, normalized to lower case. @see https://developer.mozilla.org/en-US/docs/Web/HTML/Link_types */
    rel: 'alternate'|'canonical'|'dns-prefetch'|'preconnect'|'preload'|'stylesheet'|string;
    /** The `href` attribute of the link or `null` if it was invalid in the header. */
    href: string | null
    /** The raw value of the `href` attribute. Only different from `href` when source is 'headers' */
    hrefRaw: string
    /** The `hreflang` attribute of the link */
    hreflang: string
    /** The `as` attribute of the link */
    as: string
    /** The `crossOrigin` attribute of the link */
    crossOrigin: string | null
    /** Where the link was found, either in the DOM or in the headers of the main document */
    source: 'head'|'body'|'headers'
    node: NodeDetails | null
    /** The fetch priority hint for preload links. */
    fetchPriority?: string;
  }

  interface Script extends Omit<Crdp.Debugger.ScriptParsedEvent, 'url'|'embedderName'> {
    /**
     * Set by a sourceURL= magic comment if present, otherwise this is the same as the URL.
     * Use this field for presentational purposes only.
     */
    name: string;
    url: string;
    content?: string;
  }

  /** @see https://sourcemaps.info/spec.html#h.qz3o9nc69um5 */
  type RawSourceMap = {
    /** File version and must be a positive integer. */
    version: number
    /** A list of original source files used by the `mappings` entry. */
    sources: string[]
    /** A list of symbol names used by the `mappings` entry. */
    names?: string[]
    /** An optional source root, useful for relocating source files on a server or removing repeated values in the `sources` entry. This value is prepended to the individual entries in the `source` field. */
    sourceRoot?: string
    /** An optional list of source content, useful when the `source` can’t be hosted. The contents are listed in the same order as the sources. */
    sourcesContent?: string[]
    /** A string with the encoded mapping data. */
    mappings: string
    /** An optional name of the generated code (the bundled code that was the result of this build process) that this source map is associated with. */
    file?: string
    /**
     * An optional array of maps that are associated with an offset into the generated code.
     * `map` is optional because the spec defines that either `url` or `map` must be defined.
     * We explicitly only support `map` here.
    */
    sections?: Array<{offset: {line: number, column: number}, map?: RawSourceMap}>
  }

  /**
   * Source map for a given script found at scriptUrl. If there is an error in fetching or
   * parsing the map, errorMessage will be defined instead of map.
   */
  type SourceMap = {
    /** The DevTools protocol script identifier. */
    scriptId: string;
    /** URL of code that source map applies to. */
    scriptUrl: string
    /** URL of the source map. undefined if from data URL. */
    sourceMapUrl?: string
    /** Source map data structure. */
    map: RawSourceMap
  } | {
    /** The DevTools protocol script identifier. */
    scriptId: string;
    /** URL of code that source map applies to. */
    scriptUrl: string
    /** URL of the source map. undefined if from data URL. */
    sourceMapUrl?: string
    /** Error that occurred during fetching or parsing of source map. */
    errorMessage: string
    /** No map on account of error. */
    map?: undefined;
  }

  interface Bundle {
    rawMap: RawSourceMap;
    script: Artifacts.Script;
    map: CDTSourceMap;
    sizes: {
      // TODO(cjamcl): Rename to `sources`.
      files: Record<string, number>;
      unmappedBytes: number;
      totalBytes: number;
    } | {errorMessage: string};
  }

  /** @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#Attributes */
  interface AnchorElement {
    rel: string
    /** The computed href property: https://www.w3.org/TR/DOM-Level-2-HTML/html.html#ID-88517319, use `rawHref` for the exact attribute value */
    href: string
    /** The exact value of the href attribute value, as it is in the DOM */
    rawHref: string
    name?: string
    text: string
    textLang?: string
    role: string
    target: string
    node: NodeDetails
    onclick: string
    id: string
    attributeNames: Array<string>
    listeners?: Array<{
      type: Crdp.DOMDebugger.EventListener['type']
    }>
  }

  type BFCacheReasonMap = {
    [key in Crdp.Page.BackForwardCacheNotRestoredReason]?: string[];
  };

  type BFCacheNotRestoredReasonsTree = Record<Crdp.Page.BackForwardCacheNotRestoredReasonType, BFCacheReasonMap>;

  interface BFCacheFailure {
    notRestoredReasonsTree: BFCacheNotRestoredReasonsTree;
  }

  // TODO(bckenny): real type for parsed manifest.
  type Manifest = ReturnType<typeof parseManifest>;

  interface ImageElement {
    /** The resolved source URL of the image. Similar to `currentSrc`, but resolved for CSS images as well. */
    src: string;
    /** The srcset attribute value. @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/srcset */
    srcset: string;
    /** The displayed width of the image, uses img.width when available falling back to clientWidth. See https://codepen.io/patrickhulce/pen/PXvQbM for examples. */
    displayedWidth: number;
    /** The displayed height of the image, uses img.height when available falling back to clientHeight. See https://codepen.io/patrickhulce/pen/PXvQbM for examples. */
    displayedHeight: number;
    /** The raw width attribute of the image element. CSS images will be set to null. */
    attributeWidth: string | null;
    /** The raw height attribute of the image element. CSS images will be set to null. */
    attributeHeight: string | null;
    /**
     * The natural width and height of the underlying image resource, uses img.naturalHeight/img.naturalWidth. See https://codepen.io/patrickhulce/pen/PXvQbM for examples.
     * Set to `undefined` if the data could not be collected.
     */
    naturalDimensions?: {
      width: number;
      height: number;
    };
    /**
     * The width/height of the element as defined by matching CSS rules.
     * These are distinct from the `computedStyles` properties in that they are the raw property value.
     * e.g. `width` would be `"100vw"`, not the computed width in pixels.
     * Set to `undefined` if the data was not collected.
     */
    cssEffectiveRules?: {
      /** The width of the image as expressed by CSS rules. Set to `null` if there was no width set in CSS. */
      width: string | null;
      /** The height of the image as expressed by CSS rules. Set to `null` if there was no height set in CSS. */
      height: string | null;
      /** The aspect ratio of the image as expressed by CSS rules. Set to `null` if there was no aspect ratio set in CSS. */
      aspectRatio: string | null;
    };
    /** The computed styles as determined by `getComputedStyle`. */
    computedStyles: {
      /** CSS `position` property. */
      position: string;
      /** CSS `object-fit` property. */
      objectFit: string;
      /** CSS `image-rendering` propertry. */
      imageRendering: string;
    };
    /** The BoundingClientRect of the element. */
    clientRect: {
      top: number;
      bottom: number;
      left: number;
      right: number;
    };
    /** Flags whether this element was an image via CSS background-image rather than <img> tag. */
    isCss: boolean;
    /** Flags whether this element was contained within a <picture> tag. */
    isPicture: boolean;
    /** Flags whether this element was contained within a ShadowRoot */
    isInShadowDOM: boolean;
    /** Details for node in DOM for the image element */
    node: NodeDetails;
    /** The loading attribute of the image. */
    loading?: string;
    /** The fetch priority hint for HTMLImageElements. */
    fetchPriority?: string;
  }

  interface TraceElement {
    traceEventType: 'trace-engine'|'layout-shift'|'animation';
    node: NodeDetails;
    nodeId: number;
    animations?: {name?: string, failureReasonsMask?: number, unsupportedProperties?: string[]}[];
    type?: string;
  }

  interface TraceEngineResult {
    data: TraceEngine.Handlers.Types.HandlerData;
    insights: TraceEngine.Insights.Types.TraceInsightSets;
  }

  interface TraceEngineRootCauses {
    layoutShifts: Map<TraceEngine.Types.Events.SyntheticLayoutShift, TraceEngine.Insights.Models.CLSCulprits.LayoutShiftRootCausesData>;
  }

  interface ViewportDimensions {
    innerWidth: number;
    innerHeight: number;
    outerWidth: number;
    outerHeight: number;
    devicePixelRatio: number;
  }

  type Replace<T extends string, S extends string, D extends string,
    A extends string = ""> = T extends `${infer L}${S}${infer R}` ?
    Replace<R, S, D, `${A}${L}${D}`> : `${A}${T}`;

  export type InspectorIssuesKeyToArtifactKey<T extends string> = Replace<T, 'Details', ''>;

  export type InspectorIssues = {
    [x in keyof Crdp.Audits.InspectorIssueDetails as InspectorIssuesKeyToArtifactKey<x>]: Array<Exclude<Crdp.Audits.InspectorIssueDetails[x], undefined>>
  };

  // Computed artifact types below.
  type CriticalRequestNode = {
    [id: string]: {
      request: Artifacts.NetworkRequest;
      children: CriticalRequestNode;
    }
  }

  type MeasureEntry = LHResult.MeasureEntry;

  interface MetricComputationDataInput {
    devtoolsLog: DevtoolsLog;
    trace: Trace;
    settings: Audit.Context['settings'];
    gatherContext: Artifacts['GatherContext'];
    simulator: Gatherer.Simulation.Simulator | null;
    URL: Artifacts['URL'];
    SourceMaps: Artifacts['SourceMaps'];
    HostDPR: Artifacts['HostDPR'];
  }

  interface MetricComputationData extends MetricComputationDataInput {
    networkRecords: Array<Artifacts.NetworkRequest>;
    processedTrace: ProcessedTrace;
    processedNavigation?: ProcessedNavigation;
  }

  interface NavigationMetricComputationData extends MetricComputationData {
    processedNavigation: ProcessedNavigation;
  }

  interface Metric {
    timing: number;
    timestamp?: number;
  }

  interface NetworkAnalysis {
    rtt: number;
    additionalRttByOrigin: Map<string, number>;
    serverResponseTimeByOrigin: Map<string, number>;
    throughput: number;
  }

  type LanternMetric = Lantern.Metrics.MetricResult<Artifacts.NetworkRequest>;

  type Speedline = speedline.Output<'speedIndex'>;

  interface TraceTimes {
    timeOrigin: number;
    traceEnd: number;
  }

  interface NavigationTraceTimes {
    timeOrigin: number;
    firstPaint?: number;
    firstContentfulPaint: number;
    firstContentfulPaintAllFrames: number;
    largestContentfulPaint?: number;
    largestContentfulPaintAllFrames?: number;
    traceEnd: number;
    load?: number;
    domContentLoaded?: number;
  }

  interface ProcessedTrace {
    /** The raw timestamps of key events, in microseconds. */
    timestamps: TraceTimes;
    /** The relative times from timeOrigin to key events, in milliseconds. */
    timings: TraceTimes;
    /** The subset of trace events from the main frame's process, sorted by timestamp. Due to cross-origin navigations, the main frame may have multiple processes, so events may be from more than one process.  */
    processEvents: Array<TraceEvent>;
    /** The subset of trace events from the page's main thread, sorted by timestamp. */
    mainThreadEvents: Array<TraceEvent>;
    /** The subset of trace events from the main frame, sorted by timestamp. */
    frameEvents: Array<TraceEvent>;
    /** The subset of trace events from the main frame and any child frames, sorted by timestamp. */
    frameTreeEvents: Array<TraceEvent>;
    /** IDs for the trace's main frame, and process. The startingPid is the initial process id, however cross-origin navigations may incur changes to the pid while the frame ID remains identical. */
    mainFrameInfo: {startingPid: number, frameId: string};
    /** The list of frames committed in the trace. */
    frames: Array<{id: string, url: string}>;
    /** The trace event marking the time at which the run should consider to have begun. Typically the same as the navigationStart but might differ due to SPA navigations, client-side redirects, etc. In the timespan case, this event is injected by Lighthouse itself. */
    timeOriginEvt: TraceEvent;
    /** All received trace events subsetted to important categories. */
    _keyEvents: Array<TraceEvent>;
    /** Map where keys are process IDs and their values are thread IDs */
    _rendererPidToTid: Map<number, number>;
  }

  interface ProcessedNavigation {
    /** The raw timestamps of key metric events, in microseconds. */
    timestamps: NavigationTraceTimes;
    /** The relative times from navigationStart to key metric events, in milliseconds. */
    timings: NavigationTraceTimes;
    /** The trace event marking firstPaint, if it was found. */
    firstPaintEvt?: TraceEvent;
    /** The trace event marking firstContentfulPaint, if it was found. */
    firstContentfulPaintEvt: TraceEvent;
    /** The trace event marking firstContentfulPaint from all frames, if it was found. */
    firstContentfulPaintAllFramesEvt: TraceEvent;
    /** The trace event marking largestContentfulPaint, if it was found. */
    largestContentfulPaintEvt?: TraceEvent;
    /** The trace event marking largestContentfulPaint from all frames, if it was found. */
    largestContentfulPaintAllFramesEvt?: TraceEvent;
    /** The trace event marking loadEventEnd, if it was found. */
    loadEvt?: TraceEvent;
    /** The trace event marking domContentLoadedEventEnd, if it was found. */
    domContentLoadedEvt?: TraceEvent;
    /** Whether LCP was invalidated without a new candidate. */
    lcpInvalidated: boolean;
  }

  /** Information on a tech stack (e.g. a JS library) used by the page. */
  interface DetectedStack {
    /** The identifier for how this stack was detected. */
    detector: 'js';
    /** The unique string ID for the stack. */
    id: string;
    /** The name of the stack. */
    name: string;
    /** The version of the stack, if it could be detected. */
    version?: string;
    /** The package name on NPM, if it exists. */
    npm?: string;
  }

  interface TimingSummary {
    firstContentfulPaint: number | undefined;
    firstContentfulPaintTs: number | undefined;
    firstContentfulPaintAllFrames: number | undefined;
    firstContentfulPaintAllFramesTs: number | undefined;
    largestContentfulPaint: number | undefined;
    largestContentfulPaintTs: number | undefined;
    largestContentfulPaintAllFrames: number | undefined;
    largestContentfulPaintAllFramesTs: number | undefined;
    timeToFirstByte: number | undefined;
    timeToFirstByteTs: number | undefined;
    lcpLoadDelay: number | undefined;
    lcpLoadDuration: number | undefined;
    lcpRenderDelay: number | undefined;
    interactive: number | undefined;
    interactiveTs: number | undefined;
    speedIndex: number | undefined;
    speedIndexTs: number | undefined;
    maxPotentialFID: number | undefined;
    cumulativeLayoutShift: number | undefined;
    cumulativeLayoutShiftMainFrame: number | undefined;
    totalBlockingTime: number | undefined;
    observedTimeOrigin: number;
    observedTimeOriginTs: number;
    observedNavigationStart: number | undefined;
    observedNavigationStartTs: number | undefined;
    observedCumulativeLayoutShift: number | undefined;
    observedCumulativeLayoutShiftMainFrame: number | undefined;
    observedFirstPaint: number | undefined;
    observedFirstPaintTs: number | undefined;
    observedFirstContentfulPaint: number | undefined;
    observedFirstContentfulPaintTs: number | undefined;
    observedFirstContentfulPaintAllFrames: number | undefined;
    observedFirstContentfulPaintAllFramesTs: number | undefined;
    observedLargestContentfulPaint: number | undefined;
    observedLargestContentfulPaintTs: number | undefined;
    observedLargestContentfulPaintAllFrames: number | undefined;
    observedLargestContentfulPaintAllFramesTs: number | undefined;
    observedTraceEnd: number | undefined;
    observedTraceEndTs: number | undefined;
    observedLoad: number | undefined;
    observedLoadTs: number | undefined;
    observedDomContentLoaded: number | undefined;
    observedDomContentLoadedTs: number | undefined;
    observedFirstVisualChange: number;
    observedFirstVisualChangeTs: number;
    observedLastVisualChange: number;
    observedLastVisualChangeTs: number;
    observedSpeedIndex: number;
    observedSpeedIndexTs: number;
  }

  interface FormElement {
    id: string;
    name: string;
    autocomplete: string;
    node: NodeDetails;
  }

  /** Attributes collected for every input element in the inputs array from the forms interface. */
  interface InputElement {
    /** If set, the parent form is the index into the associated FormElement array. Otherwise, the input element has no parent form. */
    parentFormIndex?: number;
    /** Array of indices into associated LabelElement array. */
    labelIndices: number[];
    id: string;
    name: string;
    type: string;
    placeholder?: string;
    autocomplete: {
      property: string;
      attribute: string | null;
      prediction: string | null;
    };
    preventsPaste?: boolean;
    node: NodeDetails;
  }

  /** Attributes collected for every label element in the labels array from the forms interface */
  interface LabelElement {
    for: string;
    node: NodeDetails;
  }

  /** Describes a generic console message. */
  interface BaseConsoleMessage {
    /**
     * The text printed to the console, as shown on the browser console.
     *
     * For console API calls, all values are formatted into the text. Primitive values and
     * function will be printed as-is while objects will be formatted as if the object were
     * passed to String(). For example, a div will be formatted as "[object HTMLDivElement]".
     *
     * For exceptions the text will be the same as err.message at runtime.
     */
    text: string;
    /** Time of the console log in milliseconds since epoch. */
    timestamp: number;
    /** The stack trace of the log/exception, if known. */
    stackTrace?: Crdp.Runtime.StackTrace;
    /** The URL of the log/exception, if known. */
    url?: string;
    /** The script id of the log/exception, if known. */
    scriptId?: string;
    /** Line number in the script (0-indexed), if known. */
    lineNumber?: number;
    /** Column number in the script (0-indexed), if known. */
    columnNumber?: number;
  }

  /** Describes a console message logged by a script using the console API. */
  interface ConsoleAPICall extends BaseConsoleMessage {
    eventType: 'consoleAPI';
    /** The console API invoked. Only the following console API calls are gathered. */
    source: 'console.warn' | 'console.error';
    /** Corresponds to the API call. */
    level: 'warning' | 'error';
  }

  interface ConsoleException extends BaseConsoleMessage {
    eventType: 'exception';
    source: 'exception';
    level: 'error';
  }

  /**
   * Describes a report logged to the console by the browser regarding interventions,
   * deprecations, violations, and more.
   */
  interface ConsoleProtocolLog extends BaseConsoleMessage {
    source: Crdp.Log.LogEntry['source'],
    level: Crdp.Log.LogEntry['level'],
    eventType: 'protocolLog';
  }

  type ConsoleMessage = ConsoleAPICall | ConsoleException | ConsoleProtocolLog;

  interface ImageElementRecord extends ImageElement {
    /** The MIME type of the underlying image file. */
    mimeType?: string;
  }

  interface Entity extends IEntity {
    isUnrecognized?: boolean;
  }

  interface EntityClassification {
    urlsByEntity: Map<Entity, Set<string>>;
    entityByUrl: Map<string, Entity>;
    firstParty?: Entity;

    // Convenience methods.
    isFirstParty: (url: string) => boolean;
  }

  interface TraceImpactedNode {
    node_id: number;
    old_rect?: Array<number>;
    new_rect?: Array<number>;
  }
}

export interface Trace {
  traceEvents: TraceEvent[];
  metadata?: {
    'cpu-family'?: number;
  };
  [futureProps: string]: any;
}

/** The type of the Profile & ProfileChunk event in Chromium traces. Note that this is subtly different from Crdp.Profiler.Profile. */
export interface TraceCpuProfile {
  nodes?: Array<{id: number, callFrame: {functionName: string, url?: string}, parent?: number}>
  samples?: Array<number>
  timeDeltas?: Array<number>
}

/**
 * @see https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview
 */
export interface TraceEvent {
  name: string;
  cat: string;
  args: {
    fileName?: string;
    snapshot?: string;
    sync_id?: string;
    beginData?: {
      frame?: string;
      startLine?: number;
      url?: string;
    };
    data?: {
      frame?: string;
      parent?: string;
      frameID?: string;
      frameTreeNodeId?: number;
      isMainFrame?: boolean;
      persistentIds?: boolean,
      processId?: number;
      isLoadingMainFrame?: boolean;
      documentLoaderURL?: string;
      navigationId?: string;
      frames?: {
        frame: string;
        url: string;
        parent?: string;
        processId?: number;
        name?: string;
      }[];
      page?: string;
      readyState?: number;
      requestId?: string;
      startTime?: number;
      timeDeltas?: TraceCpuProfile['timeDeltas'];
      cpuProfile?: TraceCpuProfile;
      callFrame?: Required<TraceCpuProfile>['nodes'][0]['callFrame']
      /** Marker for each synthetic CPU profiler event for the range of _potential_ ts values. */
      _syntheticProfilerRange?: {
        earliestPossibleTimestamp: number
        latestPossibleTimestamp: number
      }
      stackTrace?: {
        url: string
      }[];
      styleSheetUrl?: string;
      timerId?: string;
      url?: string;
      is_main_frame?: boolean;
      cumulative_score?: number;
      id?: string;
      nodeId?: number;
      DOMNodeId?: number;
      imageUrl?: string;
      impacted_nodes?: Artifacts.TraceImpactedNode[];
      score?: number;
      weighted_score_delta?: number;
      had_recent_input?: boolean;
      compositeFailed?: number;
      unsupportedProperties?: string[];
      size?: number;
      /** Responsiveness data. */
      interactionType?: 'drag'|'keyboard'|'tapOrClick';
      maxDuration?: number;
      type?: string;
      functionName?: string;
      name?: string;
      duration?: number;
      blockingDuration?: number;
      candidateIndex?: number;
      priority?: string;
      requestMethod?: string;
      resourceType?: string;
      fromCache?: boolean;
      fromServiceWorker?: boolean;
      mimeType?: string;
      statusCode?: number;
      timing?: any;
      connectionId?: number;
      connectionReused?: boolean;
      encodedDataLength?: number;
      decodedBodyLength?: number;
      initiator?: {type: string, url?: string, stack?: any};
      protocol?: string;
      finishTime?: number;
      headers?: Array<{name: string, value: string}>;
    };
    frame?: string;
    name?: string;
    labels?: string;
  };
  pid: number;
  tid: number;
  /** Timestamp of the event in microseconds. */
  ts: number;
  dur: number;
  ph: 'B'|'b'|'D'|'E'|'e'|'F'|'I'|'M'|'N'|'n'|'O'|'R'|'S'|'T'|'X';
  s?: 't';
  id?: string;
  id2?: {
    local?: string;
  };
}

declare module Trace {
  /**
   * Base event of a `ph: 'X'` 'complete' event. Extend with `name` and `args` as
   * needed.
   */
  interface CompleteEvent {
    ph: 'X';
    cat: string;
    pid: number;
    tid: number;
    dur: number;
    ts: number;
    tdur: number;
    tts: number;
  }

  /**
   * Base event of a `ph: 'b'|'e'|'n'` async event. Extend with `name`, `args`, and
   * more specific `ph` (if needed).
   */
  interface AsyncEvent {
    ph: 'b'|'e'|'n';
    cat: string;
    pid: number;
    tid: number;
    ts: number;
    id: string;
    scope?: string;
    // TODO(bckenny): No dur on these. Sort out optional `dur` on trace events.
    /** @deprecated there is no `dur` on async events. */
    dur: number;
  }
}

/**
 * A record of DevTools Debugging Protocol events.
 */
export type DevtoolsLog = Array<Protocol.RawEventMessage>;
