/**
 * @license
 * Copyright Google LLC All Rights Reserved.
 *
 * Use of this source code is governed by an MIT-style license that can be
 * found in the LICENSE file at https://angular.dev/license
 */

import type {Provider} from '../di/interface/provider';
import type {LContainer} from '../render3/interfaces/container';
import type {DependencyType} from '../render3/interfaces/definition';
import type {TNode} from '../render3/interfaces/node';
import type {LView} from '../render3/interfaces/view';

/**
 * Basic set of data structures used for identifying a defer block
 * and triggering defer blocks
 */
export interface DehydratedDeferBlock {
  lView: LView;
  tNode: TNode;
  lContainer: LContainer;
}

/**
 * Describes the shape of a function generated by the compiler
 * to download dependencies that can be defer-loaded.
 */
export type DependencyResolverFn = () => Array<Promise<DependencyType>>;

/**
 * Defines types of defer block triggers.
 */
export const enum TriggerType {
  /**
   * Represents regular triggers (e.g. `@defer (on idle) { ... }`).
   */
  Regular,

  /**
   * Represents prefetch triggers (e.g. `@defer (prefetch on idle) { ... }`).
   */
  Prefetch,

  /**
   * Represents hydrate triggers (e.g. `@defer (hydrate on idle) { ... }`).
   */
  Hydrate,
}

/**
 * Describes the state of defer block dependency loading.
 */
export enum DeferDependenciesLoadingState {
  /** Initial state, dependency loading is not yet triggered */
  NOT_STARTED,

  /** Dependency loading is in progress */
  IN_PROGRESS,

  /** Dependency loading has completed successfully */
  COMPLETE,

  /** Dependency loading has failed */
  FAILED,
}

/** Slot index where `minimum` parameter value is stored. */
export const MINIMUM_SLOT = 0;

/** Slot index where `after` parameter value is stored. */
export const LOADING_AFTER_SLOT = 1;

/** Configuration object for a loading block as it is stored in the component constants. */
export type DeferredLoadingBlockConfig = [minimumTime: number | null, afterTime: number | null];

/** Configuration object for a placeholder block as it is stored in the component constants. */
export type DeferredPlaceholderBlockConfig = [minimumTime: number | null];

/**
 * Describes the data shared across all instances of a defer block.
 */
export interface TDeferBlockDetails {
  /**
   * Index in an LView and TData arrays where a template for the primary content
   * can be found.
   */
  primaryTmplIndex: number;

  /**
   * Index in an LView and TData arrays where a template for the loading block can be found.
   */
  loadingTmplIndex: number | null;

  /**
   * Extra configuration parameters (such as `after` and `minimum`) for the loading block.
   */
  loadingBlockConfig: DeferredLoadingBlockConfig | null;

  /**
   * Index in an LView and TData arrays where a template for the placeholder block can be found.
   */
  placeholderTmplIndex: number | null;

  /**
   * Extra configuration parameters (such as `after` and `minimum`) for the placeholder block.
   */
  placeholderBlockConfig: DeferredPlaceholderBlockConfig | null;

  /**
   * Index in an LView and TData arrays where a template for the error block can be found.
   */
  errorTmplIndex: number | null;

  /**
   * Compiler-generated function that loads all dependencies for a defer block.
   */
  dependencyResolverFn: DependencyResolverFn | null;

  /**
   * Keeps track of the current loading state of defer block dependencies.
   */
  loadingState: DeferDependenciesLoadingState;

  /**
   * Dependency loading Promise. This Promise is helpful for cases when there
   * are multiple instances of a defer block (e.g. if it was used inside of an *ngFor),
   * which all await the same set of dependencies.
   */
  loadingPromise: Promise<unknown> | null;

  /**
   * List of providers collected from all NgModules that were imported by
   * standalone components used within this defer block.
   */
  providers: Provider[] | null;

  /**
   * List of hydrate triggers for a given block
   */
  hydrateTriggers: Map<DeferBlockTrigger, HydrateTriggerDetails | null> | null;

  /**
   * Defer block flags, which should be used for all
   * instances of a given defer block (the flags that should be
   * placed into the `TDeferDetails` at runtime).
   */
  flags: TDeferDetailsFlags;

  /**
   * Tracks debugging information about the deferred block.
   */
  debug: {
    /** Text representations of the block's triggers. */
    triggers?: Set<string>;
  } | null;
}

/**
 * Specifies defer block flags, which should be used for all
 * instances of a given defer block (the flags that should be
 * placed into the `TDeferDetails` at runtime).
 */
export const enum TDeferDetailsFlags {
  Default = 0,

  /**
   * Whether or not the defer block has hydrate triggers.
   */
  HasHydrateTriggers = 1 << 0,
}

/**
 * Describes the current state of this defer block instance.
 *
 * @publicApi
 */
export enum DeferBlockState {
  /** The placeholder block content is rendered */
  Placeholder = 0,

  /** The loading block content is rendered */
  Loading = 1,

  /** The main content block content is rendered */
  Complete = 2,

  /** The error block content is rendered */
  Error = 3,
}

/**
 * Represents defer trigger types.
 */
export const enum DeferBlockTrigger {
  Idle,
  Immediate,
  Viewport,
  Interaction,
  Hover,
  Timer,
  When,
  Never,
}

/** Describes specified delay (in ms) in the `hydrate on timer()` trigger. */
export interface HydrateTimerTriggerDetails {
  type: DeferBlockTrigger.Timer;
  delay?: number;
}

/** Describes the config for a `hydrate on viewport` trigger. */
export interface HydrateViewportTriggerDetails {
  type: DeferBlockTrigger.Viewport;
  intersectionObserverOptions?: IntersectionObserverInit;
}

/** * Describes all possible hydration trigger details specified in a template. */
export type HydrateTriggerDetails = HydrateTimerTriggerDetails | HydrateViewportTriggerDetails;

/**
 * Describes the initial state of this defer block instance.
 *
 * Note: this state is internal only and *must* be represented
 * with a number lower than any value in the `DeferBlockState` enum.
 */
export enum DeferBlockInternalState {
  /** Initial state. Nothing is rendered yet. */
  Initial = -1,
}

export const NEXT_DEFER_BLOCK_STATE = 0;
// Note: it's *important* to keep the state in this slot, because this slot
// is used by runtime logic to differentiate between LViews, LContainers and
// other types (see `isLView` and `isLContainer` functions). In case of defer
// blocks, this slot would always be a number.
export const DEFER_BLOCK_STATE = 1;
export const STATE_IS_FROZEN_UNTIL = 2;
export const LOADING_AFTER_CLEANUP_FN = 3;
export const TRIGGER_CLEANUP_FNS = 4;
export const PREFETCH_TRIGGER_CLEANUP_FNS = 5;
export const SSR_UNIQUE_ID = 6;
export const SSR_BLOCK_STATE = 7;
export const ON_COMPLETE_FNS = 8;
export const HYDRATE_TRIGGER_CLEANUP_FNS = 9;

/**
 * Describes instance-specific defer block data.
 *
 * Note: currently there is only the `state` slot, but more slots
 * would be added later to keep track of `after` and `maximum` features
 * (which would require per-instance state).
 */
export interface LDeferBlockDetails extends Array<unknown> {
  /**
   * Currently rendered block state.
   */
  [DEFER_BLOCK_STATE]: DeferBlockState | DeferBlockInternalState;

  /**
   * Block state that was requested when another state was rendered.
   */
  [NEXT_DEFER_BLOCK_STATE]: DeferBlockState | null;

  /**
   * Timestamp indicating when the current state can be switched to
   * the next one, in case the current state has `minimum` parameter.
   */
  [STATE_IS_FROZEN_UNTIL]: number | null;

  /**
   * Contains a reference to a cleanup function which cancels a timeout
   * when Angular waits before rendering loading state. This is used when
   * the loading block has the `after` parameter configured.
   */
  [LOADING_AFTER_CLEANUP_FN]: VoidFunction | null;

  /**
   * List of cleanup functions for regular triggers.
   */
  [TRIGGER_CLEANUP_FNS]: VoidFunction[] | null;

  /**
   * List of cleanup functions for prefetch triggers.
   */
  [PREFETCH_TRIGGER_CLEANUP_FNS]: VoidFunction[] | null;

  /**
   * Unique id of this defer block assigned during SSR.
   */
  [SSR_UNIQUE_ID]: string | null;

  /**
   * Defer block state after SSR.
   */
  [SSR_BLOCK_STATE]: number | null;

  /**
   * A set of callbacks to be invoked once the main content is rendered.
   */
  [ON_COMPLETE_FNS]: VoidFunction[] | null;

  /**
   * List of cleanup functions for hydrate triggers.
   */
  [HYDRATE_TRIGGER_CLEANUP_FNS]: VoidFunction[] | null;
}

/**
 * Internal structure used for configuration of defer block behavior.
 * */
export interface DeferBlockConfig {
  behavior: DeferBlockBehavior;
}

/**
 * Options for configuring defer blocks behavior.
 * @publicApi
 */
export enum DeferBlockBehavior {
  /**
   * Manual triggering mode for defer blocks. Provides control over when defer blocks render
   * and which state they render.
   */
  Manual,

  /**
   * Playthrough mode for defer blocks. This mode behaves like defer blocks would in a browser.
   * This is the default behavior in test environments.
   */
  Playthrough,
}

/**
 * **INTERNAL**, avoid referencing it in application code.
 *
 * Describes a helper class that allows to intercept a call to retrieve current
 * dependency loading function and replace it with a different implementation.
 * This interceptor class is needed to allow testing blocks in different states
 * by simulating loading response.
 */
export interface DeferBlockDependencyInterceptor {
  /**
   * Invoked for each defer block when dependency loading function is accessed.
   */
  intercept(dependencyFn: DependencyResolverFn | null): DependencyResolverFn | null;

  /**
   * Allows to configure an interceptor function.
   */
  setInterceptor(interceptorFn: (current: DependencyResolverFn) => DependencyResolverFn): void;
}
