//! The `params!`-facing trait surface: typed value extraction, the per-param
//! marker, the spec a `Params` enum implements, and its `Snapshot` storage.

use std::fmt::Debug;
use std::hash::Hash;

use after_effects::Parameters;
use premiere as pr;

use super::value::{Color, ParamValue, Point2};

/// Geometry handed to the snapshot constructors. Host pixel-space conversions
/// (point normalization) divide by `layer_w`/`layer_h`.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct SnapshotGeom {
	pub layer_w: u32,
	pub layer_h: u32,
	pub output_w: u32,
	pub output_h: u32,
	pub ext_x: i32,
	pub ext_y: i32,
}

/// Conversion from a snapshot slot to the marker's typed value. Out-of-variant
/// slots yield `Default`; GPU popups arriving as `Float32` coerce to `Index`.
pub trait FromParamValue: Sized {
	fn from_value(v: ParamValue) -> Self;
}

impl FromParamValue for f32 {
	fn from_value(v: ParamValue) -> Self {
		match v {
			ParamValue::Float(x) => x,
			ParamValue::Index(i) => i as f32,
			_ => 0.0,
		}
	}
}

impl FromParamValue for bool {
	fn from_value(v: ParamValue) -> Self {
		matches!(v, ParamValue::Bool(true))
	}
}

impl FromParamValue for Color {
	fn from_value(v: ParamValue) -> Self {
		match v {
			ParamValue::Color(c) => c,
			_ => Color::default(),
		}
	}
}

impl FromParamValue for Point2 {
	fn from_value(v: ParamValue) -> Self {
		match v {
			ParamValue::Point(p) => p,
			_ => Point2::default(),
		}
	}
}

impl FromParamValue for u32 {
	fn from_value(v: ParamValue) -> Self {
		match v {
			ParamValue::Index(i) => i,
			// Premiere GPU returns popups as Float32; coerce instead of failing.
			ParamValue::Float(f) => f.max(0.0) as u32,
			_ => 0,
		}
	}
}

impl FromParamValue for () {
	fn from_value(_v: ParamValue) {}
}

/// A `#[repr(u32)]` enum of popup options. Generated by `#[derive(prgpu::Popup)]`.
pub trait PopupOptions: Copy + 'static {
	const LABELS: &'static [&'static str];
	/// Out-of-range indices clamp to the first variant.
	fn from_index(index: u32) -> Self;
	fn to_index(self) -> u32;
}

/// Zero-sized per-parameter marker. `ctx.get(Marker)` resolves through `ID`.
pub trait Param: Copy + 'static {
	type Spec: ParamsSpec;
	type Value: FromParamValue;
	const ID: Self::Spec;
}

/// Fixed-size, `Copy` snapshot of every parameter's normalized value, indexed
/// by the `Params` discriminant.
pub trait Snapshot<P: ParamsSpec>: Copy + Default {
	fn value(&self, id: P) -> ParamValue;
	fn set(&mut self, id: P, value: ParamValue);
}

/// Implemented by a `params!` enum: registration + per-frame snapshot layout.
pub trait ParamsSpec: Copy + Eq + Hash + Debug + Into<usize> + Send + Sync + 'static {
	const COUNT: usize;
	/// The `#[checkbox(debug_only)]` param (if any), so a later phase can wire
	/// `Ctx::debug_view` to it.
	const DEBUG_PARAM: Option<Self>;
	type Snapshot: Snapshot<Self> + Send + Sync + 'static;

	fn register(params: &mut Parameters<Self>) -> Result<(), after_effects::Error>;
	fn snapshot_cpu(params: &Parameters<Self>, geom: &SnapshotGeom) -> Result<Self::Snapshot, after_effects::Error>;
	fn snapshot_gpu(filter: &pr::GpuFilterData, render_params: &pr::RenderParams, geom: &SnapshotGeom) -> Self::Snapshot;
	fn buttons() -> &'static [(Self, fn())];
}
