import type { FetchResponse } from "../runtime/fetch"
import type { SvgSourceValue as SvgSource } from "../runtime/misc"
import type { File } from "../runtime/fetch"
import { Canvas, isCanvas } from "../canvas/Canvas"
import { Element, type Color, type ElementStyle, type UIElementBase } from "./UINode"

export type UIImageStyle = ElementStyle & { borderRadius?: number, objectFit?: "cover" | "contain" | "fill", tintColor?: Color }

/** A baked Canvas surface as an image source. The UI backend resolves it to a UI texture — see
 *  docs/canvas-contract.md ("Pending"). */
export type CanvasImageSource = { canvasSurface: number }

/** Accepted image sources. A Canvas is baked to a surface marker on assign. */
export type ImageSource = string | FetchResponse | File | SvgSource | Canvas

const toSrc = (src: ImageSource): string | FetchResponse | File | SvgSource | CanvasImageSource =>
  // _bakeSurfaceForUI (not _bakeSurface) also flags the canvas UI-bound, so canvas.update() later
  // refreshes this image node in place — that's what makes an animated UIImage(canvas) update.
  isCanvas(src) ? { canvasSurface: src._bakeSurfaceForUI() } : src

/** The stored/resolved image source (a Canvas is resolved to a surface marker). */
export type ResolvedImageSource = string | FetchResponse | File | SvgSource | CanvasImageSource

/** An image element. Sources: URL / `asset(...)`, fetch response / File, an SVG source
 *  (`assetIcon`), or a live Canvas (`canvas.update()` refreshes the node in place). */
export interface UIImage extends UIElementBase<UIImageStyle> {
  readonly type: "image",
  /** The image source — writable to swap the picture in place. */
  get src(): ResolvedImageSource
  set src(value: ImageSource)

  /** Render only a sub-rectangle of the source texture (texture pixels) — the atlas/spritesheet
   *  primitive: crop once, then swap the rect per frame for sprite animation. The rect's w/h
   *  become the node's intrinsic size and override `objectFit` — the cropped frame fills the box. */
  setSourceRect(x: number, y: number, w: number, h: number): this
}

class ImageElement extends Element<"image"> {
  private _src: ResolvedImageSource
  // Current source sub-rect; undefined until the first setSourceRect. Kept on the node (not
  // just sent as a command) so a crop set while detached (_id == 0) is reapplied at mount —
  // the viewer reads it when it creates the element.
  private _sourceRect?: [number, number, number, number]
  constructor(style: any, src: ImageSource) {
    super("image", style)
    this._src = toSrc(src)
  }

  get src(): ResolvedImageSource {
    return this._src
  }
  set src(value: ImageSource) {
    this._src = toSrc(value)
    if (this._id !== 0) {
      _creatorUI.updateNode(this._id, "src", this._src)
    }
  }

  setSourceRect(x: number, y: number, w: number, h: number): this {
    // Stash on the node so a crop set before mount is reapplied when the element is created.
    this._sourceRect = [x, y, w, h]
    // Live mutation: fast channel (not _style → no PX_VALUE density scaling: these are texture
    // pixels, a different coordinate space). On a detached node there is nothing to mutate yet —
    // the stash above carries it to mount. The viewer decides relayout vs draw-only by comparing
    // w/h against the rect it has stashed on the element.
    if (this._id !== 0) {
      _creatorUI.setSourceRect(this._id, x, y, w, h)
    }
    return this
  }
}

/** Create an image element from a source (see {@link ImageSource}). */
export function UIImage(src: ImageSource): UIImage
/** @deprecated Style as the first argument is legacy — use chained `.style({...})`. */
export function UIImage(style: UIImageStyle, src: ImageSource): UIImage
export function UIImage(...args: [ImageSource] | [UIImageStyle, ImageSource]): UIImage {
  if (args.length === 1) {
    return new ImageElement({}, args[0])
  } else {
    return new ImageElement(args[0], args[1])
  }
}