// A 3D GPU texture, decoded natively by creator-gl. Load from a URL or an already-fetched response.

import { fetch, type FetchResponse, type File } from "../runtime/fetch"

export class Texture {
  /** @internal native texture handle. */
  readonly _id: number
  readonly width: number
  readonly height: number

  /** Horizontal wrap mode (U). */
  wrapS = 1
  /** Vertical wrap mode (V). */
  wrapT = 1

  constructor(width: number, height: number, id: number) {
    this.width = width
    this.height = height
    this._id = id
  }

  // Wrap a Canvas's baked surface as a 3D texture (RGBA8, already rasterized → synchronous, no decode).
  // The id is cached on the canvas (opaque _tex3dId), so backing several materials shares one texture and
  // canvas.update() re-uploads it. Structural type avoids a Canvas import (keeps 3D free of 2D-canvas code).
  static fromCanvas(canvas: { _bakeSurface(): number, width: number, height: number, pixelRatio: number, _tex3dId: number }): Texture {
    if (!canvas._tex3dId) {
      const surfaceId = canvas._bakeSurface()
      canvas._tex3dId = _creator.createTextureFromCanvas(surfaceId)
    }
    return new Texture(Math.round(canvas.width * canvas.pixelRatio), Math.round(canvas.height * canvas.pixelRatio), canvas._tex3dId)
  }

  /** A texture from raw pixels: `channels` (1–4, default 4) UBYTE per pixel, row-major, `width ×
   *  height × channels` bytes; `srgb` (default true for colour) picks the sRGB internal format —
   *  pass false for data (a heightmap, a mask). No mips. Throws on a host without the call. */
  static fromPixels(width: number, height: number, data: Uint8Array, opts: { channels?: 1 | 2 | 3 | 4, srgb?: boolean } = {}): Texture {
    if (!_creator.createTexturePixels) throw new Error("Texture.fromPixels needs a newer host")
    const channels = opts.channels ?? 4
    if (data.length < width * height * channels) throw new Error(`Texture.fromPixels: ${data.length} bytes for ${width}×${height}×${channels}`)
    const id = _creator.createTexturePixels(width, height, channels, data, opts.srgb ?? true)
    if (id === 0xFFFFFFFF) throw new Error("Texture.fromPixels: the host refused the texture")
    return new Texture(width, height, id)
  }

  /** Re-upload a rectangle of a `fromPixels` texture (same channel count). */
  update(x: number, y: number, width: number, height: number, data: Uint8Array): void {
    _creator.updateTexturePixels?.(this._id, x, y, width, height, data)
  }

  static load(source: string | FetchResponse | File): Promise<Texture> {
    if (typeof source === "string") {
      return fetch(source, { useOnce: true }).then((resp) => {
        if (resp.status >= 400) {
          return Promise.reject(new Error(`Failed to fetch texture from ${source}. HTTP ${resp.status}`))
        }
        return Texture.load(resp)
      })
    }
    return new Promise<Texture>((resolve, reject) => {
      _creator.createTexture((source as any)._id, (id, w, h) => resolve(new Texture(w, h, id)), reject)
    })
  }
}
