// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>

//! `glScissor`-based clipping + whole-canvas fill / clear for
//! [`GlesCanvas`]. When [`GlesCanvas::set_clip_rects`] receives
//! multiple rects the bounding-box union is used as the scissor —
//! coarse, but the partial-redraw path normally clusters 1–3 rects
//! so the union is barely larger than the sum. Disjoint regions
//! would want a stencil-buffer path; not implemented today.

use glow::HasContext;

use crate::types::{ Color, Rect };

use super::GlesCanvas;

impl GlesCanvas
{
	pub fn set_clip_rects( &mut self, rects: &[Rect] )
	{
		// Scissor is global GL state; rebind our FBO first so the scissor
		// applies to this canvas and not whatever target was active before.
		self.activate_target();
		if rects.is_empty()
		{
			self.clear_clip();
			return;
		}
		let mut x0 =  f32::INFINITY;
		let mut y0 =  f32::INFINITY;
		let mut x1 = -f32::INFINITY;
		let mut y1 = -f32::INFINITY;
		for r in rects
		{
			x0 = x0.min( r.x );
			y0 = y0.min( r.y );
			x1 = x1.max( r.x + r.width  );
			y1 = y1.max( r.y + r.height );
		}
		x0 = x0.max( 0.0 );
		y0 = y0.max( 0.0 );
		x1 = x1.min( self.width  as f32 );
		y1 = y1.min( self.height as f32 );
		if x1 <= x0 || y1 <= y0
		{
			// Empty union — install a zero-area scissor so subsequent draws
			// are no-ops without disabling the test.
			self.set_scissor( Rect { x: 0.0, y: 0.0, width: 0.0, height: 0.0 } );
			return;
		}
		self.set_scissor( Rect { x: x0, y: y0, width: x1 - x0, height: y1 - y0 } );
	}

	pub fn clear_clip( &mut self )
	{
		// SAFETY: see `primitives.rs` module doc. `disable( SCISSOR_TEST )` is
		// pure global-state mutation; the cached `clip_scissor` is updated
		// to match below.
		unsafe { self.gl.disable( glow::SCISSOR_TEST ); }
		self.clip_scissor = None;
	}

	/// Snapshot of the active scissor as a `Vec<Rect>` (empty when no
	/// scissor is set).
	pub fn clip_bounds_snapshot( &self ) -> Vec<Rect>
	{
		self.clip_scissor.map_or_else( Vec::new, |r| vec![ r ] )
	}

	/// Apply `rect` as the current scissor (top-left coords, GL bottom-left).
	fn set_scissor( &mut self, rect: Rect )
	{
		let ( x, y, w, h ) = self.scissor_pixels( rect );
		// SAFETY: `scissor_pixels` clamps to non-negative integers; GL accepts
		// arbitrary scissor rects (regions outside the framebuffer simply
		// cull all fragments). State change is mirrored in `clip_scissor`.
		unsafe
		{
			self.gl.enable( glow::SCISSOR_TEST );
			self.gl.scissor( x, y, w, h );
		}
		self.clip_scissor = Some( rect );
	}

	/// Convert a top-left rect to the bottom-left integer pixel scissor that
	/// GL expects.
	pub( super ) fn scissor_pixels( &self, rect: Rect ) -> ( i32, i32, i32, i32 )
	{
		let x = rect.x.floor() as i32;
		let w = rect.width.ceil()  as i32;
		let h = rect.height.ceil() as i32;
		// GL origin is bottom-left, our coords are top-left.
		let y_top    = rect.y.floor() as i32;
		let y_bottom = self.height as i32 - y_top - h;
		( x, y_bottom.max( 0 ), w.max( 0 ), h.max( 0 ) )
	}

	/// Clear to a solid color. Honours the active scissor — if a clip is set,
	/// only the clipped region is filled.
	pub fn fill( &mut self, color: Color )
	{
		self.activate_target();
		// SAFETY: `clear` writes the configured `clear_color` into every
		// fragment that survives the scissor test (which `activate_target`
		// has already configured to match `clip_scissor`).
		unsafe
		{
			self.gl.clear_color( color.r, color.g, color.b, color.a );
			self.gl.clear( glow::COLOR_BUFFER_BIT );
		}
	}

	/// Clear to fully transparent. Honours the active scissor.
	pub fn clear( &mut self )
	{
		self.activate_target();
		// SAFETY: same as `fill`, with a transparent clear colour.
		unsafe
		{
			self.gl.clear_color( 0.0, 0.0, 0.0, 0.0 );
			self.gl.clear( glow::COLOR_BUFFER_BIT );
		}
	}

	/// Zero the pixels inside each rect (alpha+RGB → 0).
	pub fn clear_rects_transparent( &mut self, rects: &[Rect] )
	{
		self.activate_target();
		let saved = self.clip_scissor;
		// SAFETY: enable scissor + set transparent clear colour once for
		// the whole loop; per-rect we rewrite `scissor` and clear. After
		// the loop we restore `saved` via `set_scissor` / `clear_clip`
		// so the cached `clip_scissor` matches the actual GL state again.
		unsafe
		{
			self.gl.enable( glow::SCISSOR_TEST );
			self.gl.clear_color( 0.0, 0.0, 0.0, 0.0 );
		}
		for r in rects
		{
			let ( x, y, w, h ) = self.scissor_pixels( *r );
			if w <= 0 || h <= 0 { continue; }
			// SAFETY: per-rect scissor + clear; same invariants as above.
			unsafe
			{
				self.gl.scissor( x, y, w, h );
				self.gl.clear( glow::COLOR_BUFFER_BIT );
			}
		}
		// Restore the previous scissor (or disable if none was active).
		match saved
		{
			Some( r ) => self.set_scissor( r ),
			None      => self.clear_clip(),
		}
	}

}
