//! Top-level view assembly for the QS panel. Composes the three
//! horizontal stripes — chips, middle row (media card + sliders),
//! displaced toggle bank — into a single column inside a
//! `surface-panel` envelope (the panel-wide backdrop blur).
//!
//! Two outer-chrome branches share the same inner stripes:
//!
//! * Portrait ([`build_portrait`]) — full-strip lockscreen-card
//!   reveal. The opaque lockscreen wallpaper paints the panel
//!   rect; symmetric gutters absorb whatever's left of the screen
//!   width on tablet portrait. The slide is the iOS / Android
//!   pull-down convention.
//! * Landscape ([`build_desktop`]) — content-sized pill anchored
//!   to the top-right. No wallpaper layer; the `surface-panel`
//!   backdrop blur composes directly over whatever apps are below,
//!   so the entire surface outside the pill stays click-through
//!   and the apps remain interactive.
//!
//! This module is the only one in `quicksettings/view/*` that knows
//! about the panel's outer shape (gutters, viewport height,
//! wallpaper crop, right-anchor offset). Sub-modules render strictly
//! the contents of their stripe.

mod chips;
mod buttons;
mod desktop;
mod middle;
mod pickers;

use crate::message::Message;
use crate::mpris::MediaState;
use crate::system::SystemState;

use super::QuickSettings;
use super::{
	desktop_panel_width, desktop_right_margin,
	BUTTON_ICON_PX, GRID_GAP, PANEL_ASPECT, PANEL_OUTER_PAD, PANEL_ROW_SPACING,
	VSLIDER_H, VSLIDER_W,
	QS_OUTER_PAD_VW, QS_ROW_SPACING_VW,
};

/// Build the main QS panel view. Pure rendering: reads from
/// `qs.icons`, `qs.toggle_state`, `qs.progress`, `qs.wallpaper` and
/// the live state slices, returns an `Element<Message>` with no
/// side effects.
pub( super ) fn build(
	qs:            &QuickSettings,
	system_state:  &SystemState,
	media_state:   Option<&MediaState>,
	screen_width:  u32,
	screen_height: u32,
	display_scale: u32,
) -> ltk::Element<Message>
{
	let palette      = ltk::theme_palette();
	let is_landscape = screen_width > screen_height;

	// Canonical row height of the QS 4-column grid. Each top-row
	// card occupies one row, each round button occupies one row,
	// and each vslider occupies two rows + the inter-row gap —
	// i.e. the slider pill is exactly as tall as two stacked
	// button cells. Derived from `VSLIDER_H` so the slider stays
	// the visual reference point and everything else snaps to it.
	let grid_row_h = ( VSLIDER_H - GRID_GAP ) / 2.0;

	let fade = qs.progress().clamp( 0.0, 1.0 );

	if is_landscape
	{
		let displaced  = buttons::build_desktop( qs.icons(), &qs.toggle_state, &palette, VSLIDER_W, BUTTON_ICON_PX );
		let top_row    = desktop::top_row( qs.icons(), system_state, media_state, &palette, grid_row_h );
		let sliders    = desktop::sliders( qs.icons(), system_state, &palette );
		build_desktop( qs, top_row, sliders, displaced, fade, screen_width, screen_height, display_scale )
	}
	else
	{
		// Portrait: all sizes are Length::vw, resolved by ltk against the
		// physical layout viewport. The media card needs a concrete px row
		// height for its cover aspect ratio, so it gets the physical width.
		let displaced  = buttons::build( qs.icons(), &qs.toggle_state, &palette );
		let chips_row  = chips::build( qs.icons(), system_state, &qs.toggle_state, &palette );
		let middle_row = middle::build( qs.icons(), system_state, media_state, &palette, screen_width as f32 );
		build_portrait( qs, chips_row, middle_row, displaced, fade, screen_width, screen_height, display_scale )
	}
}

/// Portrait branch — lockscreen-card reveal, full-strip chrome.
///
/// The opaque lockscreen wallpaper paints the panel rect; the
/// `surface-panel` envelope adds the single panel-wide backdrop
/// blur on top. Inner surfaces (chips, media card, sliders, toggle
/// pills) all use the `*-flat` variants without their own backdrop
/// so the renderer doesn't compound ~19 per-surface FBO snapshots
/// per frame.
fn build_portrait(
	qs:            &QuickSettings,
	chips_row:     ltk::Element<Message>,
	middle_row:    ltk::Element<Message>,
	displaced:     ltk::Element<Message>,
	fade:          f32,
	screen_width:  u32,
	screen_height: u32,
	display_scale: u32,
) -> ltk::Element<Message>
{
	// Layout runs in physical pixels (ltk resolves Length against the
	// physical viewport), and all these dimensions are physical too, so
	// no scale arithmetic here.
	let panel_h_px = qs.max_height( screen_height, display_scale );
	let panel_w    = ( ( panel_h_px as f32 ) * PANEL_ASPECT ) as u32;
	let panel_w    = panel_w.min( screen_width ).max( 1 );

	let panel_col = ltk::column::<Message>()
		.padding( ltk::Length::vw( QS_OUTER_PAD_VW ) )
		.spacing( ltk::Length::vw( QS_ROW_SPACING_VW ) )
		.max_width( panel_w as f32 )
		.push( chips_row )
		.push( middle_row )
		.push( displaced );

	let ( bg_rgba, bg_w, bg_h ) = qs.wallpaper().for_size( panel_w, screen_height );
	let bg = ltk::img_widget( bg_rgba, bg_w, bg_h ).cover();

	let viewport_h = qs.current_height( screen_width, screen_height, display_scale ) as f32;

	// During the slide the chrome splits into two layers over the
	// wallpaper: a panel-wide `surface-panel` tint covering the full stack
	// rect (so the leading 8 px band reads as panel bg) and the inner
	// widgets clipped 8 px earlier. `Fill / Top` is essential — the default
	// `push` uses `VAlign::Fill` which would override `.height( content_h )`.
	let leading_band = if qs.progress() < 1.0 { 8.0 } else { 0.0 };

	let panel: ltk::Element<Message> = if leading_band > 0.0
	{
		let content_h    = ( viewport_h - leading_band ).max( 0.0 );
		let tint         = ltk::container::<Message>( ltk::spacer() )
			.surface( "surface-panel" )
			.opacity( fade );
		let widgets      = ltk::container::<Message>( panel_col ).opacity( fade );
		let widgets_clip = ltk::viewport( widgets ).height( content_h );
		ltk::stack::<Message>()
			.push( bg )
			.push( tint )
			.push_aligned( widgets_clip, ltk::HAlign::Fill, ltk::VAlign::Top )
			.into()
	}
	else
	{
		let content = ltk::container::<Message>( panel_col )
			.surface( "surface-panel" )
			.opacity( fade );
		ltk::stack::<Message>()
			.push( bg )
			.push( content )
			.into()
	};

	let total_gutter = screen_width.saturating_sub( panel_w );
	let half         = total_gutter / 2;
	let left_gutter  = half as f32;
	let right_gutter = ( total_gutter - half ) as f32;

	ltk::column::<Message>()
		.padding( 0.0 )
		.spacing( 0.0 )
		.push(
			ltk::row::<Message>()
				.padding( 0.0 )
				.spacing( 0.0 )
				.push( ltk::spacer().width( left_gutter ) )
				.push( ltk::viewport( panel ).height( viewport_h ) )
				.push( ltk::spacer().width( right_gutter ) ),
		)
		.push( ltk::spacer() )
		.into()
}

/// Landscape (desktop) branch — content-sized pill anchored to the
/// top-right with a transparent backdrop that lets the apps below
/// show through the `surface-panel` blur.
///
/// Width and height are not hard-coded: `desktop_panel_width()` and
/// `desktop_panel_height()` (in `super`) sum the chrome primitives
/// (`VSLIDER_W`, `VSLIDER_H`, `GRID_GAP`, `PANEL_OUTER_PAD`,
/// `PANEL_ROW_SPACING`) so any tweak to those propagates to the
/// pill envelope automatically. The right margin scales with
/// `screen_width` via [`desktop_right_margin`] so the gap from the
/// bezel reads "the same" relative size on a 1366-wide laptop and a
/// 3840-wide desktop.
///
/// No wallpaper is painted — only the `surface-panel` blur over
/// whatever the apps below are rendering, matching the user-facing
/// "transparent background" requirement.
fn build_desktop(
	qs:            &QuickSettings,
	top_row:       ltk::Element<Message>,
	sliders:       ltk::Element<Message>,
	round_buttons: ltk::Element<Message>,
	fade:          f32,
	screen_width:  u32,
	screen_height: u32,
	display_scale: u32,
) -> ltk::Element<Message>
{
	let panel_w      = desktop_panel_width();
	let right_margin = desktop_right_margin( screen_width );

	let panel_col = ltk::column::<Message>()
		.padding( PANEL_OUTER_PAD )
		.spacing( PANEL_ROW_SPACING )
		.max_width( panel_w )
		.push( top_row )
		.push( sliders )
		.push( round_buttons );

	// No panel chrome: backdrop-filter on this overlay surface
	// samples its own (empty) framebuffer rather than the apps
	// below, so any `surface-panel*` slot paints black instead of
	// translucent. Inner stripes carry their own `surface-card-flat`
	// glass.
	let content = ltk::container::<Message>( panel_col )
		.opacity( fade );

	// Clip content to `current_height` so progress=0 hides the panel and
	// progress=1 reveals the full intrinsic content height. `target_height(
	// landscape ) = desktop_panel_height` so the viewport never leaks past
	// the chrome's natural footprint.
	let viewport_h = qs.current_height( screen_width, screen_height, display_scale ) as f32;

	ltk::column::<Message>()
		.padding( 0.0 )
		.spacing( 0.0 )
		.push(
			ltk::row::<Message>()
				.padding( 0.0 )
				.spacing( 0.0 )
				// Flex spacer expands to claim every pixel left of
				// the panel — pushes the panel against the right
				// edge regardless of `panel_w` or screen width.
				// `Viewport::width(panel_w)` is essential: without
				// the explicit width, the viewport's preferred-size
				// reports `max_width` (its default fallback) and
				// swallows every horizontal pixel offered to the
				// row, leaving the flex spacer with zero space to
				// claim — the panel would visibly stretch to the
				// full screen width, sit left-aligned inside the
				// stretched viewport, and look "centered" because
				// the surface chrome paints across the whole
				// viewport rect.
				.push( ltk::flex( ltk::spacer() ) )
				.push(
					ltk::viewport( content )
						.width( panel_w )
						.height( viewport_h ),
				)
				.push( ltk::spacer().width( right_margin ) ),
		)
		.push( ltk::spacer() )
		.into()
}

pub( super ) fn wifi_picker( qs: &QuickSettings ) -> ltk::Element<Message>
{
	pickers::wifi( &qs.wifi_networks )
}

pub( super ) fn bluetooth_picker( qs: &QuickSettings ) -> ltk::Element<Message>
{
	pickers::bluetooth( &qs.bt_devices )
}

