// luma.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import type {Device} from '../device';
import type {PrimitiveTopology, RenderPipelineParameters} from '../types/parameters';
import type {ShaderLayout, Bindings, BindingsByGroup} from '../types/shader-layout';
import type {BufferLayout} from '../types/buffer-layout';
import type {
  TextureFormatColor,
  TextureFormatDepthStencil
} from '@luma.gl/core/shadertypes/texture-types/texture-formats';
import type {Shader} from './shader';
import type {SharedRenderPipeline} from './shared-render-pipeline';
import type {RenderPass} from './render-pass';
import {Resource, ResourceProps} from './resource';
import {VertexArray} from './vertex-array';
import {TransformFeedback} from './transform-feedback';

export type RenderPipelineProps = ResourceProps & {
  // Shaders and shader layout

  /** Compiled vertex shader */
  vs?: Shader | null;
  /** Name of vertex shader stage main function (defaults to 'main'). WGSL only */
  vertexEntryPoint?: string; //
  /** Constant values to apply to compiled vertex shader. Do not require re-compilation. (WGSL only) */
  vsConstants?: Record<string, number>; // WGSL only
  /** Compiled fragment shader */
  fs?: Shader | null;
  /** Name of fragment shader stage main function (defaults to 'main'). WGSL only */
  fragmentEntryPoint?: string; // WGSL only
  /** Constant values to apply to compiled fragment shader. Do not require re-compilation. (WGSL only) */
  fsConstants?: Record<string, number>;

  /** Describes the attributes and bindings exposed by the pipeline shader(s). */
  shaderLayout?: ShaderLayout | null;
  /** Describes the buffers accepted by this pipeline and how they are mapped to shader attributes. */
  bufferLayout?: BufferLayout[]; // Record<string, Omit<BufferLayout, 'name'>

  /** Determines how vertices are read from the 'vertex' attributes */
  topology?: PrimitiveTopology;

  // color attachment information (needed on WebGPU)

  /** Color attachments expected by this pipeline. Defaults to [device.preferredColorFormat]. Array needs not be contiguous. */
  colorAttachmentFormats?: (TextureFormatColor | null)[];
  /** Depth attachment expected by this pipeline. Defaults to device.preferredDepthFormat, if depthWriteEnables parameter is set */
  depthStencilAttachmentFormat?: TextureFormatDepthStencil;

  /** Parameters that are controlled by pipeline */
  parameters?: RenderPipelineParameters;

  /** Transform feedback varyings captured when linking a WebGL render pipeline. WebGL only. */
  varyings?: string[];
  /** Transform feedback buffer mode used when linking a WebGL render pipeline. WebGL only. */
  bufferMode?: number;

  /** Some applications intentionally supply unused attributes and bindings, and want to disable warnings */
  disableWarnings?: boolean;

  /** Internal hook for backend-specific shared pipeline implementations. */
  _sharedRenderPipeline?: SharedRenderPipeline;

  // Dynamic bindings (TODO - pipelines should be immutable, move to RenderPass)
  /** Buffers, Textures, Samplers for the shader bindings */
  bindings?: Bindings;
  /** Bindings grouped by bind-group index */
  bindGroups?: BindingsByGroup;
};

/**
 * A compiled and linked shader program
 */
export abstract class RenderPipeline extends Resource<RenderPipelineProps> {
  override get [Symbol.toStringTag](): string {
    return 'RenderPipeline';
  }

  abstract readonly vs: Shader;
  abstract readonly fs: Shader | null;

  /** The merged layout */
  shaderLayout: ShaderLayout;
  /** Buffer map describing buffer interleaving etc */
  readonly bufferLayout: BufferLayout[];
  /** The linking status of the pipeline. 'pending' if linking is asynchronous, and on production */
  linkStatus: 'pending' | 'success' | 'error' = 'pending';
  /** The hash of the pipeline */
  hash: string = '';
  /** Optional shared backend implementation */
  sharedRenderPipeline: SharedRenderPipeline | null = null;

  /** Whether shader or pipeline compilation/linking is still in progress */
  get isPending(): boolean {
    return (
      this.linkStatus === 'pending' ||
      this.vs.compilationStatus === 'pending' ||
      this.fs?.compilationStatus === 'pending'
    );
  }

  /** Whether shader or pipeline compilation/linking has failed */
  get isErrored(): boolean {
    return (
      this.linkStatus === 'error' ||
      this.vs.compilationStatus === 'error' ||
      this.fs?.compilationStatus === 'error'
    );
  }

  constructor(device: Device, props: RenderPipelineProps) {
    super(device, props, RenderPipeline.defaultProps);
    this.shaderLayout = this.props.shaderLayout!;
    this.bufferLayout = this.props.bufferLayout || [];
    this.sharedRenderPipeline = this.props._sharedRenderPipeline || null;
  }

  /** Draw call. Returns false if the draw call was aborted (due to resources still initializing) */
  abstract draw(options: {
    /** Render pass to draw into (targeting screen or framebuffer) */
    renderPass?: RenderPass;
    /** Parameters to be set during draw call. Note that most parameters can only be overridden in WebGL. */
    parameters?: RenderPipelineParameters;
    /** Topology. Note can only be overridden in WebGL. */
    topology?: PrimitiveTopology;
    /** vertex attributes */
    vertexArray: VertexArray;
    /** Use instanced rendering? */
    isInstanced?: boolean;
    /** Number of "rows" in 'instance' buffers */
    instanceCount?: number;
    /** Number of "rows" in 'vertex' buffers */
    vertexCount?: number;
    /** Number of "rows" in index buffer */
    indexCount?: number;
    /** First vertex to draw from */
    firstVertex?: number;
    /** First index to draw from */
    firstIndex?: number;
    /** First instance to draw from */
    firstInstance?: number;
    baseVertex?: number;
    /** Transform feedback. WebGL only. */
    transformFeedback?: TransformFeedback;
    /** Bindings applied for this draw (textures, samplers, uniform buffers) */
    bindings?: Bindings;
    /** Bindings grouped by bind-group index */
    bindGroups?: BindingsByGroup;
    /** Optional stable cache keys for backend bind-group reuse */
    _bindGroupCacheKeys?: Partial<Record<number, object>>;
    /** WebGL-only uniforms */
    uniforms?: Record<string, unknown>;
  }): boolean;

  static override defaultProps: Required<RenderPipelineProps> = {
    ...Resource.defaultProps,

    vs: null,
    vertexEntryPoint: 'vertexMain',
    vsConstants: {},

    fs: null,
    fragmentEntryPoint: 'fragmentMain',
    fsConstants: {},

    shaderLayout: null,
    bufferLayout: [],
    topology: 'triangle-list',

    colorAttachmentFormats: undefined!,
    depthStencilAttachmentFormat: undefined!,

    parameters: {},
    varyings: undefined!,
    bufferMode: undefined!,
    disableWarnings: false,
    _sharedRenderPipeline: undefined!,
    bindings: undefined!,
    bindGroups: undefined!
  };
}
