/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow strict-local
 * @format
 */

/* BOM */

declare var window: typeof globalThis;

type DevToolsColor =
  | 'primary'
  | 'primary-light'
  | 'primary-dark'
  | 'secondary'
  | 'secondary-light'
  | 'secondary-dark'
  | 'tertiary'
  | 'tertiary-light'
  | 'tertiary-dark'
  | 'warning'
  | 'error';

declare interface ConsoleTask {
  run<T>(f: () => T): T;
}

// $FlowExpectedError[libdef-override] Flow core definitions are incomplete.
declare var console: {
  // Logging
  log(...data: ReadonlyArray<unknown>): void,
  trace(...data: ReadonlyArray<unknown>): void,
  debug(...data: ReadonlyArray<unknown>): void,
  info(...data: ReadonlyArray<unknown>): void,
  warn(...data: ReadonlyArray<unknown>): void,
  error(...data: ReadonlyArray<unknown>): void,

  // Grouping
  group(...data: ReadonlyArray<unknown>): void,
  groupCollapsed(...data: ReadonlyArray<unknown>): void,
  groupEnd(): void,

  // Printing
  table(
    tabularData:
      | Readonly<{[key: string]: unknown, ...}>
      | ReadonlyArray<Readonly<{[key: string]: unknown, ...}>>
      | ReadonlyArray<ReadonlyArray<unknown>>,
  ): void,
  dir(...data: ReadonlyArray<unknown>): void,
  dirxml(...data: ReadonlyArray<unknown>): void,
  clear(): void,

  // Utilities
  assert(condition: unknown, ...data: ReadonlyArray<unknown>): void,

  // Profiling
  profile(name?: string): void,
  profileEnd(name?: string): void,

  // Counts
  count(label?: string): void,
  countReset(label?: string): void,

  // Timing / tracing
  time(label?: string): void,
  timeEnd(label?: string): void,
  timeStamp(
    label?: string,
    start?: string | number,
    end?: string | number,
    trackName?: string,
    trackGroup?: string,
    color?: DevToolsColor,
    detail?: {[string]: unknown},
  ): void,

  // Stack tagging
  createTask(label: string): ConsoleTask,

  ...
};

declare class Navigator {
  product: 'ReactNative';
  appName?: ?string;
}

declare var navigator: Navigator;

// https://www.w3.org/TR/hr-time-2/#dom-domhighrestimestamp
// https://developer.mozilla.org/en-US/docs/Web/API/DOMHighResTimeStamp
declare type DOMHighResTimeStamp = number;

type PerformanceEntryFilterOptions = {
  entryType: string,
  name: string,
  ...
};

// https://www.w3.org/TR/performance-timeline-2/
declare class PerformanceEntry {
  duration: DOMHighResTimeStamp;
  entryType: string;
  name: string;
  startTime: DOMHighResTimeStamp;
  toJSON(): {[string]: unknown};
}

// https://w3c.github.io/user-timing/#performancemark
declare class PerformanceMark extends PerformanceEntry {
  constructor(name: string, markOptions?: PerformanceMarkOptions): void;
  +detail: unknown;
}

// https://w3c.github.io/user-timing/#performancemeasure
declare class PerformanceMeasure extends PerformanceEntry {
  +detail: unknown;
}

// https://w3c.github.io/server-timing/#the-performanceservertiming-interface
declare class PerformanceServerTiming {
  description: string;
  duration: DOMHighResTimeStamp;
  name: string;
  toJSON(): {[string]: unknown};
}

// https://www.w3.org/TR/resource-timing-2/#sec-performanceresourcetiming
// https://w3c.github.io/server-timing/#extension-to-the-performanceresourcetiming-interface
declare class PerformanceResourceTiming extends PerformanceEntry {
  connectEnd: number;
  connectStart: number;
  decodedBodySize: number;
  domainLookupEnd: number;
  domainLookupStart: number;
  encodedBodySize: number;
  fetchStart: number;
  initiatorType: string;
  nextHopProtocol: string;
  redirectEnd: number;
  redirectStart: number;
  requestStart: number;
  responseEnd: number;
  responseStart: number;
  secureConnectionStart: number;
  serverTiming: Array<PerformanceServerTiming>;
  transferSize: number;
  workerStart: number;
}

// https://w3c.github.io/event-timing/#sec-performance-event-timing
declare class PerformanceEventTiming extends PerformanceEntry {
  cancelable: boolean;
  interactionId: number;
  processingEnd: number;
  processingStart: number;
  target: ?Node;
}

// https://w3c.github.io/longtasks/#taskattributiontiming
declare class TaskAttributionTiming extends PerformanceEntry {
  containerId: string;
  containerName: string;
  containerSrc: string;
  containerType: string;
}

// https://w3c.github.io/longtasks/#sec-PerformanceLongTaskTiming
declare class PerformanceLongTaskTiming extends PerformanceEntry {
  attribution: ReadonlyArray<TaskAttributionTiming>;
}

// https://www.w3.org/TR/user-timing/#extensions-performance-interface
declare type PerformanceMarkOptions = {
  detail?: unknown,
  startTime?: number,
};

declare type PerformanceMeasureOptions = {
  detail?: unknown,
  duration?: number,
  end?: number | string,
  start?: number | string,
};

type EventCountsForEachCallbackType =
  | (() => void)
  | ((value: number) => void)
  | ((value: number, key: string) => void)
  | ((value: number, key: string, map: Map<string, number>) => void);

// https://www.w3.org/TR/event-timing/#eventcounts
declare interface EventCounts {
  entries(): Iterator<[string, number]>;

  forEach(callback: EventCountsForEachCallbackType): void;
  get(key: string): ?number;
  has(key: string): boolean;
  keys(): Iterator<string>;
  size: number;
  values(): Iterator<number>;
}

declare class Performance {
  +eventCounts: EventCounts;
  +timeOrigin: DOMHighResTimeStamp;

  clearMarks(name?: string): void;
  clearMeasures(name?: string): void;
  getEntries: (
    options?: PerformanceEntryFilterOptions,
  ) => Array<PerformanceEntry>;
  getEntriesByName: (name: string, type?: string) => Array<PerformanceEntry>;
  getEntriesByType: (type: string) => Array<PerformanceEntry>;
  mark(name: string, options?: PerformanceMarkOptions): PerformanceMark;
  measure(
    name: string,
    startMarkOrOptions?: string | PerformanceMeasureOptions,
    endMark?: string,
  ): PerformanceMeasure;
  now(): DOMHighResTimeStamp;
  toJSON(): {[string]: unknown};
}

declare var performance: Performance;

type PerformanceEntryList = Array<PerformanceEntry>;

declare interface PerformanceObserverEntryList {
  getEntries(): PerformanceEntryList;
  getEntriesByName(name: string, type: ?string): PerformanceEntryList;
  getEntriesByType(type: string): PerformanceEntryList;
}

type PerformanceObserverInit = {
  buffered?: boolean,
  entryTypes?: Array<string>,
  type?: string,
  ...
};

declare class PerformanceObserver {
  constructor(
    callback: (
      entries: PerformanceObserverEntryList,
      observer: PerformanceObserver,
    ) => unknown,
  ): void;

  disconnect(): void;
  observe(options: ?PerformanceObserverInit): void;
  static supportedEntryTypes: Array<string>;

  takeRecords(): PerformanceEntryList;
}

type FormDataEntryValue = string | File;

declare class FormData {
  append(name: string, value: string): void;

  append(name: string, value: Blob, filename?: string): void;
  append(name: string, value: File, filename?: string): void;
  constructor(form?: HTMLFormElement, submitter?: HTMLElement | null): void;

  delete(name: string): void;
  entries(): Iterator<[string, FormDataEntryValue]>;
  get(name: string): ?FormDataEntryValue;

  getAll(name: string): Array<FormDataEntryValue>;
  has(name: string): boolean;
  keys(): Iterator<string>;

  set(name: string, value: string): void;

  set(name: string, value: Blob, filename?: string): void;
  set(name: string, value: File, filename?: string): void;
  values(): Iterator<FormDataEntryValue>;
}

declare class DOMRectReadOnly {
  +bottom: number;
  constructor(x: number, y: number, width: number, height: number): void;
  static fromRect(rectangle?: {
    height: number,
    width: number,
    x: number,
    y: number,
    ...
  }): DOMRectReadOnly;
  +height: number;
  +left: number;
  +right: number;
  +top: number;
  +width: number;
  +x: number;
  +y: number;
}

declare class DOMRect extends DOMRectReadOnly {
  bottom: number;
  static fromRect(rectangle?: {
    height: number,
    width: number,
    x: number,
    y: number,
    ...
  }): DOMRect;
  height: number;
  left: number;
  right: number;
  top: number;
  width: number;
  x: number;
  y: number;
}

declare class DOMRectList {
  @@iterator(): Iterator<DOMRect>;
  item(index: number): DOMRect;
  length: number;
  [index: number]: DOMRect;
}

declare class MutationRecord {
  // Always an empty NodeList for `attributes` and `characterData` mutations.
  // React Native currently only supports `childList`, so this contains the
  // added nodes for that mutation type.
  +addedNodes: NodeList<Node>;
  // Always `null` in React Native (only `childList` mutations are supported).
  +attributeName: null;
  // Always `null` in React Native (only `childList` mutations are supported).
  +nextSibling: null;
  // Always `null` in React Native (only `childList` mutations are supported,
  // and `attributeOldValue`/`characterDataOldValue` are not supported).
  +oldValue: null;
  // Always `null` in React Native (only `childList` mutations are supported).
  +previousSibling: null;
  // Always an empty NodeList for `attributes` and `characterData` mutations.
  // React Native currently only supports `childList`, so this contains the
  // removed nodes for that mutation type.
  +removedNodes: NodeList<Node>;
  +target: Node;
  // React Native currently only supports `childList` mutations.
  +type: 'childList';
}

// React Native currently only supports `childList` mutations, so `childList`
// is required and must be `true`. The `attributes`/`attributeFilter`/
// `attributeOldValue`/`characterData`/`characterDataOldValue` options are not
// supported and will throw if provided.
declare type MutationObserverInit = {
  +childList: true,
  +subtree?: boolean,
  ...
};

declare class MutationObserver {
  constructor(
    callback: (
      arr: Array<MutationRecord>,
      observer: MutationObserver,
    ) => unknown,
  ): void;
  disconnect(): void;
  observe(target: Node, options: MutationObserverInit): void;
}

declare type IntersectionObserverEntry = {
  +boundingClientRect: DOMRectReadOnly,
  +intersectionRatio: number,
  +intersectionRect: DOMRectReadOnly,
  +isIntersecting: boolean,
  // Always non-null in React Native.
  +rootBounds: DOMRectReadOnly,
  // React Native-specific extension. Equivalent to `intersectionRatio` but
  // computed against the `rnRootThreshold` root-relative thresholds.
  +rnRootIntersectionRatio: number,
  +target: Element,
  +time: DOMHighResTimeStamp,
  ...
};

declare type IntersectionObserverCallback = (
  entries: Array<IntersectionObserverEntry>,
  observer: IntersectionObserver,
) => unknown;

declare type IntersectionObserverOptions = {
  root?: Node | null,
  rootMargin?: string,
  threshold?: number | Array<number>,
  // React Native-specific extension. Thresholds expressed as a fraction of the
  // root's size (instead of the target's size).
  rnRootThreshold?: number | Array<number>,
  ...
};

// The `delay`, `scrollMargin` and `trackVisibility` options are not supported
// in React Native and will throw if provided.
declare class IntersectionObserver {
  constructor(
    callback: IntersectionObserverCallback,
    options?: IntersectionObserverOptions,
  ): void;
  disconnect(): void;
  observe(target: Element): void;
  +root: Element | null;
  +rootMargin: string;
  // React Native-specific extension. The thresholds expressed as a fraction of
  // the root's size (set via the `rnRootThreshold` option).
  +rnRootThresholds: ReadonlyArray<number> | null;
  +thresholds: ReadonlyArray<number>;
  unobserve(target: Element): void;
}

declare class CloseEvent extends Event {
  code: number;
  reason: string;
  wasClean: boolean;
}

declare class WebSocket extends EventTarget {
  binaryType: 'blob' | 'arraybuffer';
  bufferedAmount: number;
  close(code?: number, reason?: string): void;
  static CLOSED: 3;
  CLOSED: 3;
  static CLOSING: 2;
  CLOSING: 2;
  static CONNECTING: 0;
  CONNECTING: 0;
  constructor(url: string, protocols?: string | Array<string>): void;
  extensions: string;
  onclose: (ev: CloseEvent) => unknown;
  onerror: (ev: $FlowFixMe) => unknown;
  onmessage: (ev: MessageEvent) => unknown;
  onopen: (ev: $FlowFixMe) => unknown;
  static OPEN: 1;
  OPEN: 1;
  protocol: string;
  readyState: number;
  send(data: string): void;
  send(data: Blob): void;
  send(data: ArrayBuffer): void;
  send(data: $ArrayBufferView): void;
  url: string;
}

declare class XMLHttpRequest extends EventTarget {
  abort(): void;
  static DONE: number;
  DONE: number;
  getAllResponseHeaders(): string;
  getResponseHeader(header: string): string;
  static HEADERS_RECEIVED: number;
  HEADERS_RECEIVED: number;
  static LOADING: number;
  LOADING: number;
  msCaching: string;
  msCachingEnabled(): boolean;
  onabort: ProgressEventHandler;
  onerror: ProgressEventHandler;
  onload: ProgressEventHandler;
  onloadend: ProgressEventHandler;
  onloadstart: ProgressEventHandler;
  onprogress: ProgressEventHandler;
  onreadystatechange: (ev: $FlowFixMe) => unknown;
  ontimeout: ProgressEventHandler;
  open(
    method: string,
    url: string,
    async?: boolean,
    user?: string,
    password?: string,
  ): void;
  static OPENED: number;
  OPENED: number;
  overrideMimeType(mime: string): void;
  readyState: number;
  response: $FlowFixMe;
  responseBody: $FlowFixMe;
  responseText: string;
  responseType: string;
  responseURL: string;
  responseXML: $FlowFixMe;
  send(data?: $FlowFixMe): void;
  setRequestHeader(header: string, value: string): void;
  statics: {create(): XMLHttpRequest, ...};
  status: number;
  statusText: string;
  timeout: number;
  static UNSENT: number;
  UNSENT: number;
  upload: XMLHttpRequestEventTarget;

  withCredentials: boolean;
}

declare class XMLHttpRequestEventTarget extends EventTarget {
  onabort: ProgressEventHandler;
  onerror: ProgressEventHandler;
  onload: ProgressEventHandler;
  onloadend: ProgressEventHandler;
  onloadstart: ProgressEventHandler;
  onprogress: ProgressEventHandler;
  ontimeout: ProgressEventHandler;
}

// this part of spec is not finished yet, apparently
// https://stackoverflow.com/questions/35296664/can-fetch-get-object-as-headers
type HeadersInit =
  | Headers
  | Array<[string, string]>
  | {[key: string]: string, ...};

// TODO Heades and URLSearchParams are almost the same thing.
// Could it somehow be abstracted away?
declare class Headers {
  @@iterator(): Iterator<[string, string]>;
  append(name: string, value: string): void;
  constructor(init?: HeadersInit): void;
  delete(name: string): void;
  entries(): Iterator<[string, string]>;
  forEach<This>(
    callback: (
      this: This,
      value: string,
      name: string,
      headers: Headers,
    ) => unknown,
    thisArg: This,
  ): void;
  get(name: string): null | string;
  has(name: string): boolean;
  keys(): Iterator<string>;
  set(name: string, value: string): void;
  values(): Iterator<string>;
}

declare class URLSearchParams {
  @@iterator(): Iterator<[string, string]>;

  append(name: string, value: string): void;

  constructor(
    init?:
      | string
      | URLSearchParams
      | Array<[string, string]>
      | {[string]: string, ...},
  ): void;
  delete(name: string, value?: string): void;
  entries(): Iterator<[string, string]>;
  forEach<This>(
    callback: (
      this: This,
      value: string,
      name: string,
      params: URLSearchParams,
    ) => unknown,
    thisArg: This,
  ): void;
  get(name: string): null | string;
  getAll(name: string): Array<string>;
  has(name: string, value?: string): boolean;
  keys(): Iterator<string>;
  set(name: string, value: string): void;
  size: number;
  sort(): void;
  toString(): string;
  values(): Iterator<string>;
}

type CacheType =
  | 'default'
  | 'no-store'
  | 'reload'
  | 'no-cache'
  | 'force-cache'
  | 'only-if-cached';
type CredentialsType = 'omit' | 'same-origin' | 'include';
type ModeType = 'cors' | 'no-cors' | 'same-origin' | 'navigate';
type RedirectType = 'follow' | 'error' | 'manual';
type ReferrerPolicyType =
  | ''
  | 'no-referrer'
  | 'no-referrer-when-downgrade'
  | 'same-origin'
  | 'origin'
  | 'strict-origin'
  | 'origin-when-cross-origin'
  | 'strict-origin-when-cross-origin'
  | 'unsafe-url';

type ResponseType =
  | 'basic'
  | 'cors'
  | 'default'
  | 'error'
  | 'opaque'
  | 'opaqueredirect';

type BodyInit =
  | string
  | URLSearchParams
  | FormData
  | Blob
  | ArrayBuffer
  | $ArrayBufferView
  | ReadableStream;

type RequestInfo = Request | URL | string;

type RequestOptions = {
  body?: ?BodyInit,
  cache?: CacheType,
  credentials?: CredentialsType,
  headers?: HeadersInit,
  integrity?: string,
  keepalive?: boolean,
  method?: string,
  mode?: ModeType,
  redirect?: RedirectType,
  referrer?: string,
  referrerPolicy?: ReferrerPolicyType,
  signal?: ?AbortSignal,
  window?: $FlowFixMe,
  ...
};

type ResponseOptions = {
  headers?: HeadersInit,
  status?: number,
  statusText?: string,
  ...
};

declare class Response {
  arrayBuffer(): Promise<ArrayBuffer>;
  blob(): Promise<Blob>;
  body: ?ReadableStream;
  // Body methods and attributes
  bodyUsed: boolean;

  clone(): Response;
  constructor(input?: ?BodyInit, init?: ResponseOptions): void;
  static error(): Response;
  formData(): Promise<FormData>;
  headers: Headers;
  json(): Promise<$FlowFixMe>;
  ok: boolean;
  static redirect(url: string, status?: number): Response;

  redirected: boolean;
  status: number;

  statusText: string;
  text(): Promise<string>;
  trailer: Promise<Headers>;
  type: ResponseType;
  url: string;
}

declare class Request {
  arrayBuffer(): Promise<ArrayBuffer>;
  blob(): Promise<Blob>;

  // Body methods and attributes
  bodyUsed: boolean;

  cache: CacheType;
  clone(): Request;
  constructor(input: RequestInfo, init?: RequestOptions): void;
  credentials: CredentialsType;
  formData(): Promise<FormData>;
  headers: Headers;
  integrity: string;
  json(): Promise<$FlowFixMe>;
  method: string;
  mode: ModeType;

  redirect: RedirectType;

  referrer: string;
  referrerPolicy: ReferrerPolicyType;
  +signal: AbortSignal;
  text(): Promise<string>;
  url: string;
}

declare class AbortController {
  abort(reason?: $FlowFixMe): void;
  constructor(): void;
  +signal: AbortSignal;
}

declare class AbortSignal extends EventTarget {
  static abort(reason?: $FlowFixMe): AbortSignal;
  static any(signals: Iterable<AbortSignal>): AbortSignal;
  +aborted: boolean;
  onabort: (event: Event) => unknown;
  +reason: $FlowFixMe;
  throwIfAborted(): void;
  static timeout(time: number): AbortSignal;
}

declare function fetch(
  input: RequestInfo,
  init?: RequestOptions,
): Promise<Response>;

declare class Location {
  ancestorOrigins: Array<string>;
  assign(url: string): void;
  hash: string;
  host: string;
  hostname: string;
  href: string;
  origin: string;
  pathname: string;
  port: string;
  protocol: string;
  reload(flag?: boolean): void;
  replace(url: string): void;
  search: string;
  toString(): string;
}

declare class CanvasCaptureMediaStream extends MediaStream {
  canvas: HTMLCanvasElement;
  requestFrame(): void;
}

declare type FileSystemHandleKind = 'file' | 'directory';

// https://wicg.github.io/file-system-access/#api-filesystemhandle
declare class FileSystemHandle {
  +kind: FileSystemHandleKind;
  +name: string;

  isSameEntry: (other: FileSystemHandle) => Promise<boolean>;
  queryPermission?: (
    descriptor: FileSystemHandlePermissionDescriptor,
  ) => Promise<PermissionStatus>;
  requestPermission?: (
    descriptor: FileSystemHandlePermissionDescriptor,
  ) => Promise<PermissionStatus>;
}

// https://fs.spec.whatwg.org/#api-filesystemfilehandle
declare class FileSystemFileHandle extends FileSystemHandle {
  +kind: 'file';

  constructor(name: string): void;

  getFile(): Promise<File>;
  createSyncAccessHandle(): Promise<FileSystemSyncAccessHandle>;
  createWritable(options?: {|
    keepExistingData?: boolean,
  |}): Promise<FileSystemWritableFileStream>;
}

// https://fs.spec.whatwg.org/#api-filesystemdirectoryhandle
declare class FileSystemDirectoryHandle extends FileSystemHandle {
  +kind: 'directory';

  constructor(name: string): void;

  getDirectoryHandle(
    name: string,
    options?: {|create?: boolean|},
  ): Promise<FileSystemDirectoryHandle>;
  getFileHandle(
    name: string,
    options?: {|create?: boolean|},
  ): Promise<FileSystemFileHandle>;
  removeEntry(name: string, options?: {|recursive?: boolean|}): Promise<void>;
  resolve(possibleDescendant: FileSystemHandle): Promise<Array<string> | null>;

  // Async iterator functions
  @@asyncIterator(): AsyncIterator<[string, FileSystemHandle]>;
  entries(): AsyncIterator<[string, FileSystemHandle]>;
  keys(): AsyncIterator<string>;
  values(): AsyncIterator<FileSystemHandle>;
}

// https://fs.spec.whatwg.org/#api-filesystemsyncaccesshandle
declare class FileSystemSyncAccessHandle {
  close(): void;
  flush(): void;
  getSize(): number;
  read(buffer: ArrayBuffer, options?: {|at: number|}): number;
  truncate(newSize: number): void;
  write(buffer: ArrayBuffer, options?: {|at: number|}): number;
}

declare class MediaStream {}
declare class USBDevice {}
declare class PermissionStatus {}
declare class FileSystemWritableFileStream {}

type PermissionState = 'granted' | 'denied' | 'prompt';

type FileSystemHandlePermissionDescriptor = {|
  mode: 'read' | 'readwrite',
|};
