//! Pre-decoded asset bundle for the QS panel chrome plus a per-frame
//! tint cache. Kept in its own type so the rendering side
//! ([`crate::quicksettings::view`]) borrows a `&Icons` without
//! reaching into the rest of `QuickSettings::*` state, and the state
//! side never has to think about RGBA buffers / `tint_symbolic` /
//! GPU texture caching.
//!
//! Every glyph is `include_bytes!`-embedded via [`crate::assets`] and
//! rasterised once in [`Icons::new`]; per-frame rendering then reuses
//! the same `Arc<Vec<u8>>` allocation through [`Icons::tint_cached`],
//! which keys on `Arc::as_ptr` so ltk's GLES `image_cache` (also
//! pointer-keyed) hits and avoids re-uploading the same texture every
//! redraw.

use std::cell::RefCell;
use std::collections::HashMap;
use std::sync::Arc;

use crate::assets;

type RgbaIcon = ( Arc<Vec<u8>>, u32, u32 );

/// Bundle of pre-rasterised symbolic icons used by the QS view, plus
/// the tint-result cache shared across all of them. One instance is
/// owned by [`crate::quicksettings::QuickSettings`] for the lifetime
/// of the panel.
pub( crate ) struct Icons
{
	// ── connectivity card glyphs (top row) ──────────────────────
	pub( crate ) wifi_card:   Option<RgbaIcon>,
	pub( crate ) mobile_card: Option<RgbaIcon>,

	// ── round-button glyphs (the displaced 4×2 toggle bank) ─────
	pub( crate ) bluetooth:     Option<RgbaIcon>,
	pub( crate ) settings:      Option<RgbaIcon>,
	pub( crate ) airplane:      Option<RgbaIcon>,
	pub( crate ) notifications: Option<RgbaIcon>,
	pub( crate ) dark_mode:     Option<RgbaIcon>,
	pub( crate ) rotation:      Option<RgbaIcon>,
	pub( crate ) vpn:           Option<RgbaIcon>,
	pub( crate ) flashlight:    Option<RgbaIcon>,

	// ── slider glyphs ───────────────────────────────────────────
	// Portrait pills overlay a single max-state glyph at the bottom
	// of each vslider; desktop pills flank the horizontal track with
	// the min/mute glyph on the left and the max glyph on the right.
	pub( crate ) brightness_slider:     Option<RgbaIcon>,
	pub( crate ) speaker_slider:        Option<RgbaIcon>,
	pub( crate ) brightness_slider_low: Option<RgbaIcon>,
	pub( crate ) speaker_slider_mute:   Option<RgbaIcon>,

	// ── media-card transport glyphs ─────────────────────────────
	pub( crate ) play:     Option<RgbaIcon>,
	pub( crate ) pause:    Option<RgbaIcon>,
	pub( crate ) previous: Option<RgbaIcon>,
	pub( crate ) next:     Option<RgbaIcon>,

	// ── tint cache ──────────────────────────────────────────────
	/// Per-icon cache of tinted RGBA buffers, keyed on the input
	/// `Arc<Vec<u8>>`'s allocation pointer. Without this, every
	/// frame of every QS render calls `ltk::tint_symbolic` for each
	/// of ~16 icons, returning a fresh `Vec<u8>` wrapped in a fresh
	/// `Arc`. The GLES canvas keys its texture cache on
	/// `rgba_data.as_ptr()` (`ltk/src/gles_render/image.rs`), so a
	/// fresh pointer means a fresh `glTexImage2D` per icon per
	/// frame plus a permanent leak of the previous-frame texture
	/// into the cache. With this cache the input pointer drives
	/// the lookup and the same tinted `Arc<Vec<u8>>` is returned
	/// every frame the tint colour stays the same — the GPU
	/// texture is uploaded once per icon per theme change instead
	/// of once per frame.
	tint_cache: RefCell<HashMap<usize, ( ltk::Color, Arc<Vec<u8>> )>>,
}

impl Icons
{
	/// Decode every QS-chrome SVG up front so the first pull-down
	/// pays no rasterisation cost on the hot path. Returns `None`
	/// for any icon whose source SVG is malformed or absent
	/// (handled by the call site as "render a `spacer` in place" so
	/// the layout still composes).
	pub( crate ) fn new() -> Self
	{
		// 64 px keeps a comfortable margin for the card glyph at
		// 24 px on HiDPI scales without paying for the full 256 px
		// the source SVG could rasterise to. Transport glyphs at
		// 48 px because they paint at 32 px (BUTTON_ICON_PX) and a
		// closer multiple keeps resvg's antialiasing crisp.
		Self
		{
			wifi_card:         assets::decode_svg_bytes( assets::WIFI_FULL_FILLED,    64 ),
			mobile_card:       assets::decode_svg_bytes( assets::MOBILE_DATA_FILLED,  64 ),
			bluetooth:         assets::decode_svg_bytes( assets::BLUETOOTH_FILLED,    64 ),
			settings:          assets::decode_svg_bytes( assets::SETTINGS_FILLED,     64 ),
			airplane:          assets::decode_svg_bytes( assets::AIRPLANE_FILLED,     64 ),
			notifications:     assets::decode_svg_bytes( assets::NOTIFICATIONS_FILLED, 64 ),
			dark_mode:         assets::decode_svg_bytes( assets::DARK_MODE_FILLED,    64 ),
			rotation:          assets::decode_svg_bytes( assets::ROTATION_FILLED,     64 ),
			vpn:               assets::decode_svg_bytes( assets::VPN_FILLED,          64 ),
			flashlight:        assets::decode_svg_bytes( assets::FLASHLIGHT_FILLED,   64 ),
			brightness_slider:     assets::decode_svg_bytes( assets::BRIGHTNESS_FILLED,     64 ),
			speaker_slider:        assets::decode_svg_bytes( assets::SPEAKER_FILLED,        64 ),
			brightness_slider_low: assets::decode_svg_bytes( assets::BRIGHTNESS_LOW_FILLED, 64 ),
			speaker_slider_mute:   assets::decode_svg_bytes( assets::SPEAKER_MUTE_FILLED,   64 ),
			play:              assets::decode_svg_bytes( assets::PLAY_FILLED,         48 ),
			pause:             assets::decode_svg_bytes( assets::PAUSE_FILLED,        48 ),
			previous:          assets::decode_svg_bytes( assets::PREVIOUS_FILLED,     48 ),
			next:              assets::decode_svg_bytes( assets::NEXT_FILLED,         48 ),
			tint_cache:        RefCell::new( HashMap::new() ),
		}
	}

	/// Tint `raw` with `color`, reusing the previous result if the
	/// same `(input pointer, colour)` pair is already cached. See
	/// the comment on [`Self::tint_cache`] for why this matters at
	/// the per-frame redraw rate of the QS panel.
	pub( crate ) fn tint_cached( &self, raw: &Arc<Vec<u8>>, color: ltk::Color ) -> Arc<Vec<u8>>
	{
		let key = Arc::as_ptr( raw ) as usize;
		let mut guard = self.tint_cache.borrow_mut();
		if let Some( ( cached_color, cached_buf ) ) = guard.get( &key )
		{
			if *cached_color == color
			{
				return Arc::clone( cached_buf );
			}
		}
		let fresh = Arc::new( ltk::tint_symbolic( &raw[..], color ) );
		guard.insert( key, ( color, Arc::clone( &fresh ) ) );
		fresh
	}
}
