//! Overlay panel assembly.
//!
//! Owns the [`ltk::OverlayId`] assignments and the body of the
//! [`ltk::App::overlays`] trait method. Every panel above the
//! wallpaper (launcher, quick settings, calendar,
//! power menu, wifi / bt pickers, OSD, edit menu, app info) is
//! pushed from one place so input-region layering and dismiss
//! routing stay in a single file.

use crate::homescreen::DragOrigin;
use crate::message::Message;
use crate::powermenu::PowerAction;
use crate::state::Crustace;

pub( crate ) const OVERLAY_LAUNCHER:       ltk::OverlayId = ltk::OverlayId( 1 );
pub( crate ) const OVERLAY_QUICK_SETTINGS: ltk::OverlayId = ltk::OverlayId( 2 );
pub( crate ) const OVERLAY_CALENDAR:       ltk::OverlayId = ltk::OverlayId( 4 );
pub( crate ) const OVERLAY_POWER_MENU:     ltk::OverlayId = ltk::OverlayId( 5 );
pub( crate ) const OVERLAY_WIFI_PICKER:    ltk::OverlayId = ltk::OverlayId( 6 );
pub( crate ) const OVERLAY_BT_PICKER:      ltk::OverlayId = ltk::OverlayId( 7 );
pub( crate ) const OVERLAY_OSD:            ltk::OverlayId = ltk::OverlayId( 8 );
pub( crate ) const OVERLAY_EDIT_MENU:      ltk::OverlayId = ltk::OverlayId( 9 );
pub( crate ) const OVERLAY_APP_INFO:       ltk::OverlayId = ltk::OverlayId( 10 );
pub( crate ) const OVERLAY_DOCK:           ltk::OverlayId = ltk::OverlayId( 11 );
pub( crate ) const OVERLAY_DRAG_GHOST:     ltk::OverlayId = ltk::OverlayId( 12 );
pub( crate ) const OVERLAY_SPLASH:         ltk::OverlayId = ltk::OverlayId( 13 );

pub( crate ) const SUBSURFACE_LAUNCHER: ltk::SubsurfaceId = ltk::SubsurfaceId( 1 );

impl Crustace
{
	/// Assemble every overlay panel whose visible state says it should
	/// be on screen this frame. Called from [`ltk::App::overlays`].
	pub( crate ) fn build_overlays( &self ) -> Vec<ltk::OverlaySpec<Message>>
	{
		let mut out: Vec<ltk::OverlaySpec<Message>> = Vec::new();

		if self.launcher.is_shown()
		{
			// Bottom-anchored host spanning the strip below the topbar. Open:
			// it paints the real interactive panel. Sliding: the panel rides a
			// compositor-repositioned subsurface (see `Crustace::subsurfaces`)
			// and the host stays transparent and input-transparent so the
			// gesture engine keeps driving the slide.
			let surface_h = self.launcher.max_height( self.screen_height, self.display_scale );
			let revealed  = self.launcher.revealed();

			let view: ltk::Element<Message> = if revealed
			{
				let mut cache = self.icon_cache.borrow_mut();
				let v = self.launcher.view(
					&self.config,
					&mut cache,
					self.screen_width,
					self.screen_height,
					self.display_scale,
					Message::LaunchApp,
					|app_id| Message::StartDrag { origin: DragOrigin::Launcher, app_id },
					Message::LauncherSearch,
				);
				drop( cache );
				v
			}
			else
			{
				ltk::spacer().into()
			};

			let input_region = if revealed
			{
				Some( vec![ ltk::Rect {
					x:      0.0,
					y:      0.0,
					width:  self.screen_width.max( 1 ) as f32,
					height: surface_h as f32,
				} ] )
			}
			else { Some( Vec::new() ) };

			out.push( ltk::OverlaySpec {
				id:                 OVERLAY_LAUNCHER,
				layer:              ltk::Layer::Overlay,
				anchor:             ltk::Anchor::BOTTOM,
				size:               ( 0, surface_h ),
				exclusive_zone:     -1,
				keyboard_exclusive: true,
				input_region,
				view,
				on_dismiss:         if revealed { Some( Message::ToggleLauncher ) } else { None },
				anchor_widget_id:   None,
			} );
		}

		if self.quick_settings.is_shown()
		{
			// QS renders in this overlay every frame (not a subsurface): the
			// panel's `surface-panel` backdrop blur is a GLES pass the software
			// subsurface raster can't do, so a subsurface would drop the glass
			// while sliding. The per-frame cost is bounded by `invalidate_after`
			// scoping the wipe to this overlay (no homescreen re-raster).
			let view = self.quick_settings.build(
				&self.system_state,
				self.media_state.as_ref(),
				self.screen_width,
				self.screen_height,
				self.display_scale,
			);

			let surface_h = self.quick_settings.max_height( self.screen_height, self.display_scale );

			out.push( ltk::OverlaySpec {
				id:                 OVERLAY_QUICK_SETTINGS,
				layer:              ltk::Layer::Overlay,
				anchor:             ltk::Anchor::BOTTOM,
				size:               ( 0, surface_h ),
				exclusive_zone:     0,
				keyboard_exclusive: false,
				input_region:       Some( vec![ ltk::Rect {
					x:      0.0,
					y:      0.0,
					width:  self.screen_width.max( 1 ) as f32,
					height: surface_h as f32,
				} ] ),
				view,
				on_dismiss:         Some( Message::CloseQuickSettings ),
				anchor_widget_id:   None,
			} );
		}

		if self.calendar_visible
		{
			use chrono::Datelike as _;
			let today = chrono::Local::now().date_naive();
			let today_date = ltk::Date::new(
				today.year(), today.month() as u8, today.day() as u8,
			);
			let portrait = self.screen_height > self.screen_width;
			let cal_w = if portrait
			{
				self.screen_width as f32
			} else {
				self.screen_width as f32 * 0.30
			};
			let picker = ltk::date_picker::<Message>( today_date )
				.today( today_date )
				.view( self.calendar_view_year, self.calendar_view_month )
				.width( cal_w )
				.on_navigate( |y, m| Message::CalendarNavigate { year: y, month: m } );
			let cell: ltk::Element<Message> = ltk::container( picker )
				.max_width( cal_w )
				.into();
			let view = ltk::stack::<Message>()
				.push_translated( cell, ltk::HAlign::Center, ltk::VAlign::Top, 0.0, crate::launcher::TOPBAR_PX as f32 )
				.into();

			out.push( ltk::OverlaySpec {
				id:                 OVERLAY_CALENDAR,
				layer:              ltk::Layer::Overlay,
				anchor:             ltk::Anchor::ALL,
				size:               ( 0, 0 ),
				exclusive_zone:     0,
				keyboard_exclusive: false,
				input_region:       Some( vec![ ltk::Rect {
					x:      0.0,
					y:      crate::launcher::TOPBAR_PX as f32,
					width:  self.screen_width.max( 1 ) as f32,
					height: self.screen_height.saturating_sub( crate::launcher::TOPBAR_PX ).max( 1 ) as f32,
				} ] ),
				view,
				on_dismiss:         Some( Message::ToggleCalendar ),
				anchor_widget_id:   None,
			} );
		}

		if self.power_menu.visible
		{
			out.push( ltk::OverlaySpec {
				id:                 OVERLAY_POWER_MENU,
				layer:              ltk::Layer::Overlay,
				anchor:             ltk::Anchor::ALL,
				size:               ( 0, 0 ),
				exclusive_zone:     0,
				keyboard_exclusive: false,
				input_region:       None,
				view:               self.power_menu.view(),
				on_dismiss:         Some( Message::PowerAction( PowerAction::Cancel ) ),
				anchor_widget_id:   None,
			} );
		}

		if self.quick_settings.wifi_picker_visible
		{
			out.push( ltk::OverlaySpec {
				id:                 OVERLAY_WIFI_PICKER,
				layer:              ltk::Layer::Overlay,
				anchor:             ltk::Anchor::ALL,
				size:               ( 0, 0 ),
				exclusive_zone:     0,
				keyboard_exclusive: false,
				input_region:       None,
				view:               self.quick_settings.build_wifi_picker(),
				on_dismiss:         Some( Message::CloseQuickSettings ),
				anchor_widget_id:   None,
			} );
		}

		if self.quick_settings.bt_picker_visible
		{
			out.push( ltk::OverlaySpec {
				id:                 OVERLAY_BT_PICKER,
				layer:              ltk::Layer::Overlay,
				anchor:             ltk::Anchor::ALL,
				size:               ( 0, 0 ),
				exclusive_zone:     0,
				keyboard_exclusive: false,
				input_region:       None,
				view:               self.quick_settings.build_bluetooth_picker(),
				on_dismiss:         Some( Message::CloseQuickSettings ),
				anchor_widget_id:   None,
			} );
		}

		if let Some( view ) = self.osd.view()
		{
			out.push( ltk::OverlaySpec {
				id:                 OVERLAY_OSD,
				layer:              ltk::Layer::Overlay,
				anchor:             ltk::Anchor::ALL,
				size:               ( 0, 0 ),
				exclusive_zone:     0,
				keyboard_exclusive: false,
				// OSD is purely informational — don't steal input
				// from apps below.
				input_region:       Some( Vec::new() ),
				view,
				on_dismiss:         None,
				anchor_widget_id:   None,
			} );
		}

		if let Some( ref drag ) = self.dragging_item
		{
			let mut cache = self.icon_cache.borrow_mut();
			let entry = self.launcher.apps.iter().find( |a| a.app_id == drag.app_id );
			let ( rgba, iw, ih ) = entry
				.and_then( |a| a.icon.as_ref() )
				.and_then( |p| cache.get_or_load_sized( p, 48 ) )
				.unwrap_or_else( || self.launcher.default_icon().clone() );
			drop( cache );

			const GHOST_SIZE: f32 = 56.0;
			let ( cx, cy ) = drag.cursor;
			let ghost_opacity = if self.drop_would_succeed( cx, cy ) { 1.0 } else { 0.4 };
			let ghost: ltk::Element<Message> = ltk::img_widget( rgba, iw, ih )
				.size( GHOST_SIZE, GHOST_SIZE )
				.opacity( ghost_opacity )
				.into();

			let offset_x = cx - GHOST_SIZE / 2.0;
			let offset_y = cy - GHOST_SIZE / 2.0;
			let view: ltk::Element<Message> = ltk::stack::<Message>()
				.push_translated(
					ghost,
					ltk::HAlign::Start,
					ltk::VAlign::Top,
					offset_x,
					offset_y,
				)
				.into();

			out.push( ltk::OverlaySpec {
				id:                 OVERLAY_DRAG_GHOST,
				layer:              ltk::Layer::Overlay,
				anchor:             ltk::Anchor::ALL,
				size:               ( 0, 0 ),
				exclusive_zone:     -1,
				keyboard_exclusive: false,
				input_region:       Some( Vec::new() ),
				view,
				on_dismiss:         None,
				anchor_widget_id:   None,
			} );
		}

		// Edit-mode context menu. Tapping the bare area (outside the
		// panel itself) routes through `on_dismiss` which exits edit
		// mode.
		if let Some( ref menu ) = self.edit_menu
		{
			out.push( ltk::OverlaySpec {
				id:                 OVERLAY_EDIT_MENU,
				layer:              ltk::Layer::Overlay,
				anchor:             ltk::Anchor::ALL,
				size:               ( 0, 0 ),
				exclusive_zone:     0,
				keyboard_exclusive: false,
				input_region:       None,
				view:               menu.view( self.screen_width, self.screen_height ),
				on_dismiss:         Some( Message::EditMenuCancel ),
				anchor_widget_id:   None,
			} );
		}

		// App info card. Modal over everything else; dismiss on tap
		// outside the card.
		if let Some( ref info ) = self.app_info
		{
			let mut cache = self.icon_cache.borrow_mut();
			let view      = info.view( &self.launcher.apps, &mut cache );
			drop( cache );
			out.push( ltk::OverlaySpec {
				id:                 OVERLAY_APP_INFO,
				layer:              ltk::Layer::Overlay,
				anchor:             ltk::Anchor::ALL,
				size:               ( 0, 0 ),
				exclusive_zone:     0,
				keyboard_exclusive: false,
				input_region:       None,
				view,
				on_dismiss:         Some( Message::CloseAppInfo ),
				anchor_widget_id:   None,
			} );
		}

		let always_visible = self.config.dock.always_visible;
		let dock_mounted   = !self.use_mobile_layout()
			&& ( always_visible || self.dock_reveal.is_shown() );
		if dock_mounted
		{
			let is_landscape = self.screen_width > self.screen_height;
			let dims         = crate::homescreen::geometry::grid_dims(
				self.screen_width, self.screen_height, is_landscape,
			);
			let geom         = crate::homescreen::geometry::dock_geom_desktop(
				self.screen_width, self.screen_height, &dims,
			);
			let pill_h       = crate::dock::desktop_pill_height( geom.icon as u32 );
			let surface_h    = pill_h.ceil() as u32;

			let on_launch:   fn( String ) -> Message = Message::LaunchApp;
			let on_activate: fn( String ) -> Message = Message::ActivateApp;
			let long_press = |index: usize, app_id: String| Message::ShowEditMenu {
				origin: DragOrigin::Dock { index }, app_id,
			};
			let drag_start = |index: usize, app_id: String| Message::StartDrag {
				origin: DragOrigin::Dock { index }, app_id,
			};

			let dock_preview = self.dragging_item.as_ref().and_then( | drag |
			{
				let adds_item = !matches!( drag.origin, crate::homescreen::DragOrigin::Dock { .. } );
				crate::homescreen::dock_preview_index(
					drag.cursor.0, drag.cursor.1,
					self.screen_width, self.screen_height,
					self.config.dock.items.len(),
					adds_item,
					is_landscape,
				)
			} );

			let mut cache = self.icon_cache.borrow_mut();
			let long_press_temp = |app_id: String| Message::ShowEditMenu {
				origin: DragOrigin::Running { app_id: app_id.clone() }, app_id,
			};
			let pill = crate::dock::build_dock(
				&self.config.dock.items,
				&self.launcher.apps,
				&mut cache,
				self.homescreen.logo(),
				self.launcher.default_icon(),
				Message::ToggleLauncher,
				&on_launch,
				&on_activate,
				long_press,
				long_press_temp,
				drag_start,
				geom.icon as u32,
				geom.spacing,
				true,
				dock_preview,
				&self.running_apps,
			);
			drop( cache );

			let pill_row: ltk::Element<Message> = ltk::row::<Message>()
				.padding( 0.0 )
				.spacing( 0.0 )
				.push( ltk::flex( ltk::spacer() ) )
				.push( pill )
				.push( ltk::flex( ltk::spacer() ) )
				.into();

			let progress = if always_visible { 1.0 } else { self.dock_reveal.progress() };
			let view: ltk::Element<Message> = ltk::stack::<Message>()
				.push_translated(
					pill_row,
					ltk::HAlign::Fill,
					ltk::VAlign::Fill,
					0.0,
					( 1.0 - progress ) * surface_h as f32,
				)
				.into();

			// The surface spans the full width to centre the pill, but the
			// flex spacers either side are transparent and must not eat
			// clicks meant for the window below; restrict input to the pill.
			let pill_w = crate::dock::desktop_pill_width(
				&self.config.dock.items, &self.launcher.apps, &self.running_apps,
				geom.icon as u32, geom.spacing,
			);
			let pill_left = ( ( self.screen_width as f32 - pill_w ) * 0.5 ).max( 0.0 );

			out.push( ltk::OverlaySpec {
				id:                 OVERLAY_DOCK,
				layer:              ltk::Layer::Top,
				anchor:             ltk::Anchor::BOTTOM,
				size:               ( 0, surface_h ),
				exclusive_zone:     if always_visible { surface_h as i32 } else { 0 },
				keyboard_exclusive: false,
				input_region:       Some( vec![ ltk::Rect {
					x:      pill_left,
					y:      0.0,
					width:  pill_w,
					height: surface_h as f32,
				} ] ),
				view,
				on_dismiss:         None,
				anchor_widget_id:   None,
			} );
		}

		if let Some( entry ) = self.splash.current()
		{
			let portrait    = self.screen_height > self.screen_width;
			let mut cache   = self.icon_cache.borrow_mut();
			let view        = crate::splash::view::build(
				entry,
				&mut cache,
				self.launcher.default_icon(),
				self.screen_width,
				self.screen_height,
			);
			drop( cache );

			// Portrait fills the screen and absorbs every click; landscape
			// only blocks input over the centred card so apps around it
			// stay interactive.
			let input_region = if portrait
			{
				None
			} else {
				let card_w = ( self.screen_width  as f32 ) * crate::splash::LANDSCAPE_CARD_FRAC;
				let card_h = ( self.screen_height as f32 ) * crate::splash::LANDSCAPE_CARD_FRAC;
				let x      = ( self.screen_width  as f32 - card_w ) / 2.0;
				let y      = ( self.screen_height as f32 - card_h ) / 2.0;
				Some( vec![ ltk::Rect { x, y, width: card_w, height: card_h } ] )
			};

			out.push( ltk::OverlaySpec {
				id:                 OVERLAY_SPLASH,
				layer:              ltk::Layer::Overlay,
				anchor:             ltk::Anchor::ALL,
				size:               ( 0, 0 ),
				exclusive_zone:     -1,
				keyboard_exclusive: false,
				input_region,
				view,
				on_dismiss:         None,
				anchor_widget_id:   None,
			} );
		}

		out
	}

	/// Sliding panels rendered as compositor-repositioned subsurfaces over
	/// their host overlays. Called from [`ltk::App::subsurfaces`]. Each is
	/// rastered once and slid by `y` alone, so the reveal tracks the finger
	/// at the compositor's compositing cost, not a per-frame re-raster.
	pub( crate ) fn build_subsurfaces( &self ) -> Vec<ltk::SubsurfaceSpec<Message>>
	{
		let mut out: Vec<ltk::SubsurfaceSpec<Message>> = Vec::new();

		if self.launcher.subsurface_shown()
		{
			let surface_h = self.launcher.max_height( self.screen_height, self.display_scale );
			let mut cache = self.icon_cache.borrow_mut();
			let view = self.launcher.view(
				&self.config,
				&mut cache,
				self.screen_width,
				self.screen_height,
				self.display_scale,
				Message::LaunchApp,
				|app_id| Message::StartDrag { origin: DragOrigin::Launcher, app_id },
				Message::LauncherSearch,
			);
			drop( cache );

			out.push( ltk::SubsurfaceSpec {
				id:              SUBSURFACE_LAUNCHER,
				parent:          ltk::SubsurfaceParent::Overlay( OVERLAY_LAUNCHER ),
				view,
				x:               0,
				y:               self.launcher.subsurface_offset_y( surface_h ),
				content_version: self.launcher.content_version(),
				// No glass — software raster is cheaper to spin up per swipe
				// (no shader compile, no per-surface EGL surface).
				gpu:             false,
			} );
		}

		out
	}
}
