// Copyright 2018-2025 the Deno authors. MIT license.

/** Return type for {@linkcode describeTextureFormat}. */
export interface TextureFormatInfo {
  /** The specific feature needed to use the format, if any. */
  requiredFeature?: GPUFeatureName;
  /** Type of sampling that is valid for the texture. */
  sampleType: GPUTextureSampleType;
  /** Valid bits of {@linkcode GPUTextureUsage}. */
  allowedUsages: number;
  /** Dimension of a "block" of texels. This is always `[1, 1]` on
   * uncompressed textures. */
  blockDimensions: [number, number];
  /** Size in bytes of a "block" of texels. This is the size per pixel on
   * uncompressed textures. */
  blockSize: number;
  /** Count of components in the texture. This determines which components
   * there will be actual data in the shader for. */
  components: number;
}

const basic = GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST |
  GPUTextureUsage.TEXTURE_BINDING;
const attachment = basic | GPUTextureUsage.RENDER_ATTACHMENT;
const storage = basic | GPUTextureUsage.STORAGE_BINDING;
const allFlags = GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST |
  GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING |
  GPUTextureUsage.RENDER_ATTACHMENT;

/**
 * Get various information about a specific {@linkcode GPUTextureFormat}.
 *
 * @example Basic usage
 * ```ts
 * import { describeTextureFormat } from "@std/webgpu/describe-texture-format";
 * import { assertEquals } from "@std/assert";
 *
 * assertEquals(describeTextureFormat("rgba8unorm"), {
 *   sampleType: "float",
 *   allowedUsages: 31,
 *   blockDimensions: [1, 1],
 *   blockSize: 4,
 *   components: 4,
 * });
 * ```
 *
 * @param format The format to get the information about.
 * @returns An object describing various properties for the provided format.
 */
export function describeTextureFormat(
  format: GPUTextureFormat,
): TextureFormatInfo {
  let info: [
    requiredFeatures: GPUFeatureName | undefined,
    sampleType: GPUTextureSampleType,
    usage: number,
    blockDimensions: [number, number],
    blockSize: number,
    components: number,
  ];

  switch (format) {
    case "r8unorm":
      info = [undefined, "float", attachment, [1, 1], 1, 1];
      break;
    case "r8snorm":
      info = [undefined, "float", basic, [1, 1], 1, 1];
      break;
    case "r8uint":
      info = [undefined, "uint", attachment, [1, 1], 1, 1];
      break;
    case "r8sint":
      info = [undefined, "sint", attachment, [1, 1], 1, 1];
      break;

    case "r16uint":
      info = [undefined, "uint", attachment, [1, 1], 2, 1];
      break;
    case "r16sint":
      info = [undefined, "sint", attachment, [1, 1], 2, 1];
      break;
    case "r16float":
      info = [undefined, "float", attachment, [1, 1], 2, 1];
      break;
    case "rg8unorm":
      info = [undefined, "float", attachment, [1, 1], 2, 2];
      break;
    case "rg8snorm":
      info = [undefined, "float", attachment, [1, 1], 2, 2];
      break;
    case "rg8uint":
      info = [undefined, "uint", attachment, [1, 1], 2, 2];
      break;
    case "rg8sint":
      info = [undefined, "sint", basic, [1, 1], 2, 2];
      break;

    case "r32uint":
      info = [undefined, "uint", allFlags, [1, 1], 4, 1];
      break;
    case "r32sint":
      info = [undefined, "sint", allFlags, [1, 1], 4, 1];
      break;
    case "r32float":
      info = [undefined, "unfilterable-float", allFlags, [1, 1], 4, 1];
      break;
    case "rg16uint":
      info = [undefined, "uint", attachment, [1, 1], 4, 2];
      break;
    case "rg16sint":
      info = [undefined, "sint", attachment, [1, 1], 4, 2];
      break;
    case "rg16float":
      info = [undefined, "float", attachment, [1, 1], 4, 2];
      break;
    case "rgba8unorm":
      info = [undefined, "float", allFlags, [1, 1], 4, 4];
      break;
    case "rgba8unorm-srgb":
      info = [undefined, "float", attachment, [1, 1], 4, 4];
      break;
    case "rgba8snorm":
      info = [undefined, "float", storage, [1, 1], 4, 4];
      break;
    case "rgba8uint":
      info = [undefined, "uint", allFlags, [1, 1], 4, 4];
      break;
    case "rgba8sint":
      info = [undefined, "sint", allFlags, [1, 1], 4, 4];
      break;
    case "bgra8unorm":
      info = [undefined, "float", attachment, [1, 1], 4, 4];
      break;
    case "bgra8unorm-srgb":
      info = [undefined, "float", attachment, [1, 1], 4, 4];
      break;

    case "rgb9e5ufloat":
      info = [undefined, "float", basic, [1, 1], 4, 3];
      break;

    case "rgb10a2unorm":
      info = [undefined, "float", attachment, [1, 1], 4, 4];
      break;
    case "rg11b10ufloat":
      info = [undefined, "float", basic, [1, 1], 4, 3];
      break;

    case "rg32uint":
      info = [undefined, "uint", allFlags, [1, 1], 8, 2];
      break;
    case "rg32sint":
      info = [undefined, "sint", allFlags, [1, 1], 8, 2];
      break;
    case "rg32float":
      info = [undefined, "unfilterable-float", allFlags, [1, 1], 8, 2];
      break;
    case "rgba16uint":
      info = [undefined, "uint", allFlags, [1, 1], 8, 4];
      break;
    case "rgba16sint":
      info = [undefined, "sint", allFlags, [1, 1], 8, 4];
      break;
    case "rgba16float":
      info = [undefined, "float", allFlags, [1, 1], 8, 4];
      break;

    case "rgba32uint":
      info = [undefined, "uint", allFlags, [1, 1], 16, 4];
      break;
    case "rgba32sint":
      info = [undefined, "sint", allFlags, [1, 1], 16, 4];
      break;
    case "rgba32float":
      info = [undefined, "float", allFlags, [1, 1], 16, 4];
      break;

    case "stencil8":
      info = [undefined, "uint", attachment, [1, 1], 1, 1];
      break;
    case "depth16unorm":
      info = [undefined, "depth", attachment, [1, 1], 2, 1];
      break;
    case "depth24plus":
      info = [undefined, "depth", attachment, [1, 1], 4, 1];
      break;
    case "depth24plus-stencil8":
      info = [undefined, "depth", attachment, [1, 1], 4, 2];
      break;
    case "depth32float":
      info = [undefined, "depth", attachment, [1, 1], 4, 1];
      break;

    case "depth32float-stencil8":
      info = ["depth32float-stencil8", "depth", attachment, [1, 1], 4, 2];
      break;

    case "bc1-rgba-unorm":
      info = ["texture-compression-bc", "float", basic, [4, 4], 8, 4];
      break;
    case "bc1-rgba-unorm-srgb":
      info = ["texture-compression-bc", "float", basic, [4, 4], 8, 4];
      break;
    case "bc2-rgba-unorm":
      info = ["texture-compression-bc", "float", basic, [4, 4], 16, 4];
      break;
    case "bc2-rgba-unorm-srgb":
      info = ["texture-compression-bc", "float", basic, [4, 4], 16, 4];
      break;
    case "bc3-rgba-unorm":
      info = ["texture-compression-bc", "float", basic, [4, 4], 16, 4];
      break;
    case "bc3-rgba-unorm-srgb":
      info = ["texture-compression-bc", "float", basic, [4, 4], 16, 4];
      break;
    case "bc4-r-unorm":
      info = ["texture-compression-bc", "float", basic, [4, 4], 8, 1];
      break;
    case "bc4-r-snorm":
      info = ["texture-compression-bc", "float", basic, [4, 4], 8, 1];
      break;
    case "bc5-rg-unorm":
      info = ["texture-compression-bc", "float", basic, [4, 4], 16, 2];
      break;
    case "bc5-rg-snorm":
      info = ["texture-compression-bc", "float", basic, [4, 4], 16, 2];
      break;
    case "bc6h-rgb-ufloat":
      info = ["texture-compression-bc", "float", basic, [4, 4], 16, 3];
      break;
    case "bc6h-rgb-float":
      info = ["texture-compression-bc", "float", basic, [4, 4], 16, 3];
      break;
    case "bc7-rgba-unorm":
      info = ["texture-compression-bc", "float", basic, [4, 4], 16, 4];
      break;
    case "bc7-rgba-unorm-srgb":
      info = ["texture-compression-bc", "float", basic, [4, 4], 16, 4];
      break;

    case "etc2-rgb8unorm":
      info = ["texture-compression-etc2", "float", basic, [4, 4], 8, 3];
      break;
    case "etc2-rgb8unorm-srgb":
      info = ["texture-compression-etc2", "float", basic, [4, 4], 8, 3];
      break;
    case "etc2-rgb8a1unorm":
      info = ["texture-compression-etc2", "float", basic, [4, 4], 8, 4];
      break;
    case "etc2-rgb8a1unorm-srgb":
      info = ["texture-compression-etc2", "float", basic, [4, 4], 8, 4];
      break;
    case "etc2-rgba8unorm":
      info = ["texture-compression-etc2", "float", basic, [4, 4], 16, 4];
      break;
    case "etc2-rgba8unorm-srgb":
      info = ["texture-compression-etc2", "float", basic, [4, 4], 16, 4];
      break;
    case "eac-r11unorm":
      info = ["texture-compression-etc2", "float", basic, [4, 4], 8, 1];
      break;
    case "eac-r11snorm":
      info = ["texture-compression-etc2", "float", basic, [4, 4], 8, 1];
      break;
    case "eac-rg11unorm":
      info = ["texture-compression-etc2", "float", basic, [4, 4], 16, 2];
      break;
    case "eac-rg11snorm":
      info = ["texture-compression-etc2", "float", basic, [4, 4], 16, 2];
      break;

    case "astc-4x4-unorm":
      info = ["texture-compression-astc", "float", basic, [4, 4], 16, 4];
      break;
    case "astc-4x4-unorm-srgb":
      info = ["texture-compression-astc", "float", basic, [4, 4], 16, 4];
      break;
    case "astc-5x4-unorm":
      info = ["texture-compression-astc", "float", basic, [5, 4], 16, 4];
      break;
    case "astc-5x4-unorm-srgb":
      info = ["texture-compression-astc", "float", basic, [5, 4], 16, 4];
      break;
    case "astc-5x5-unorm":
      info = ["texture-compression-astc", "float", basic, [5, 5], 16, 4];
      break;
    case "astc-5x5-unorm-srgb":
      info = ["texture-compression-astc", "float", basic, [5, 5], 16, 4];
      break;
    case "astc-6x5-unorm":
      info = ["texture-compression-astc", "float", basic, [6, 5], 16, 4];
      break;
    case "astc-6x5-unorm-srgb":
      info = ["texture-compression-astc", "float", basic, [6, 5], 16, 4];
      break;
    case "astc-6x6-unorm":
      info = ["texture-compression-astc", "float", basic, [6, 6], 16, 4];
      break;
    case "astc-6x6-unorm-srgb":
      info = ["texture-compression-astc", "float", basic, [6, 6], 16, 4];
      break;
    case "astc-8x5-unorm":
      info = ["texture-compression-astc", "float", basic, [8, 5], 16, 4];
      break;
    case "astc-8x5-unorm-srgb":
      info = ["texture-compression-astc", "float", basic, [8, 5], 16, 4];
      break;
    case "astc-8x6-unorm":
      info = ["texture-compression-astc", "float", basic, [8, 6], 16, 4];
      break;
    case "astc-8x6-unorm-srgb":
      info = ["texture-compression-astc", "float", basic, [8, 6], 16, 4];
      break;
    case "astc-8x8-unorm":
      info = ["texture-compression-astc", "float", basic, [8, 8], 16, 4];
      break;
    case "astc-8x8-unorm-srgb":
      info = ["texture-compression-astc", "float", basic, [8, 8], 16, 4];
      break;
    case "astc-10x5-unorm":
      info = ["texture-compression-astc", "float", basic, [10, 5], 16, 4];
      break;
    case "astc-10x5-unorm-srgb":
      info = ["texture-compression-astc", "float", basic, [10, 5], 16, 4];
      break;
    case "astc-10x6-unorm":
      info = ["texture-compression-astc", "float", basic, [10, 6], 16, 4];
      break;
    case "astc-10x6-unorm-srgb":
      info = ["texture-compression-astc", "float", basic, [10, 6], 16, 4];
      break;
    case "astc-10x8-unorm":
      info = ["texture-compression-astc", "float", basic, [10, 8], 16, 4];
      break;
    case "astc-10x8-unorm-srgb":
      info = ["texture-compression-astc", "float", basic, [10, 8], 16, 4];
      break;
    case "astc-10x10-unorm":
      info = ["texture-compression-astc", "float", basic, [10, 10], 16, 4];
      break;
    case "astc-10x10-unorm-srgb":
      info = ["texture-compression-astc", "float", basic, [10, 10], 16, 4];
      break;
    case "astc-12x10-unorm":
      info = ["texture-compression-astc", "float", basic, [12, 10], 16, 4];
      break;
    case "astc-12x10-unorm-srgb":
      info = ["texture-compression-astc", "float", basic, [12, 10], 16, 4];
      break;
    case "astc-12x12-unorm":
      info = ["texture-compression-astc", "float", basic, [12, 12], 16, 4];
      break;
    case "astc-12x12-unorm-srgb":
      info = ["texture-compression-astc", "float", basic, [12, 12], 16, 4];
      break;

    default:
      throw new TypeError(`Unsupported GPUTextureFormat '${format}'`);
  }

  const ret: TextureFormatInfo = {
    sampleType: info[1],
    allowedUsages: info[2],
    blockDimensions: info[3],
    blockSize: info[4],
    components: info[5],
  };
  if (info[0] !== undefined) {
    ret.requiredFeature = info[0];
  }
  return ret;
}
