// 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 type * as Lantern from '../types/types.js';

import {BaseNode} from './BaseNode.js';

const NON_NETWORK_SCHEMES = [
  'blob',        // @see https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL
  'data',        // @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
  'intent',      // @see https://developer.chrome.com/docs/multidevice/android/intents/
  'file',        // @see https://en.wikipedia.org/wiki/File_URI_scheme
  'filesystem',  // @see https://developer.mozilla.org/en-US/docs/Web/API/FileSystem
  'chrome-extension',
];

/**
 * Note: the `protocol` field from CDP can be 'h2', 'http', (not 'https'!) or it'll be url's scheme.
 *   https://source.chromium.org/chromium/chromium/src/+/main:content/browser/devtools/protocol/network_handler.cc;l=598-611;drc=56d4a9a9deb30be73adcee8737c73bcb2a5ab64f
 * However, a `new URL(href).protocol` has a colon suffix.
 *   https://url.spec.whatwg.org/#dom-url-protocol
 * A URL's `scheme` is specced as the `protocol` sans-colon, but isn't exposed on a URL object.
 * This method can take all 3 of these string types as a parameter.
 *
 * @param protocol Either a networkRequest's `protocol` per CDP or a `new URL(href).protocol`
 */
function isNonNetworkProtocol(protocol: string): boolean {
  // Strip off any colon
  const urlScheme = protocol.includes(':') ? protocol.slice(0, protocol.indexOf(':')) : protocol;
  return NON_NETWORK_SCHEMES.includes(urlScheme);
}

class NetworkNode<T = Lantern.AnyNetworkObject> extends BaseNode<T> {
  _request: Lantern.NetworkRequest<T>;

  constructor(networkRequest: Lantern.NetworkRequest<T>) {
    super(networkRequest.requestId);
    this._request = networkRequest;
  }

  override get type(): 'network' {
    return BaseNode.types.NETWORK;
  }

  override get startTime(): number {
    return this._request.rendererStartTime * 1000;
  }

  override get endTime(): number {
    return this._request.networkEndTime * 1000;
  }

  get rawRequest(): Readonly<T> {
    return this._request.rawRequest as Required<T>;
  }

  get request(): Lantern.NetworkRequest<T> {
    return this._request;
  }

  get initiatorType(): string {
    return this._request.initiator.type;
  }

  get fromDiskCache(): boolean {
    return Boolean(this._request.fromDiskCache);
  }

  get isNonNetworkProtocol(): boolean {
    // The 'protocol' field in devtools a string more like a `scheme`
    return isNonNetworkProtocol(this.request.protocol) ||
        // But `protocol` can fail to be populated if the request fails, so fallback to scheme.
        isNonNetworkProtocol(this.request.parsedURL.scheme);
  }

  /**
   * Returns whether this network request can be downloaded without a TCP connection.
   * During simulation we treat data coming in over a network connection separately from on-device data.
   */
  get isConnectionless(): boolean {
    return this.fromDiskCache || this.isNonNetworkProtocol;
  }

  hasRenderBlockingPriority(): boolean {
    const priority = this._request.priority;
    const isScript = this._request.resourceType === 'Script';
    const isDocument = this._request.resourceType === 'Document';
    const isBlockingScript = priority === 'High' && isScript;
    const isBlockingHtmlImport = priority === 'High' && isDocument;
    return priority === 'VeryHigh' || isBlockingScript || isBlockingHtmlImport;
  }

  override cloneWithoutRelationships(): NetworkNode<T> {
    const node = new NetworkNode(this._request);
    node.setIsMainDocument(this._isMainDocument);
    return node;
  }
}

export {NetworkNode};
