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

//! Notebook — paginated tabs with a content area.
//!
//! Different from `tab_bar` (which is just a segmented selector — the
//! application is responsible for rendering whatever content
//! corresponds to the active tab). A `Notebook` owns the
//! pages: each page bundles a label *and* its content, and only the
//! active page's content is laid out / drawn each frame.
//!
//! The widget itself is stateless: the application owns
//! `self.tab: usize` and updates it from the `on_select` callback. The
//! pages can be built fresh every frame (typical) or memoised on the
//! application side.
//!
//! ```rust,no_run
//! # use ltk::{ notebook, text, Element, Notebook };
//! # #[ derive( Clone ) ] enum Msg { SelectTab( usize ) }
//! # struct App { tab: usize }
//! # impl App {
//! #     fn general_view( &self ) -> Element<Msg> { text( "g" ).into() }
//! #     fn network_view( &self ) -> Element<Msg> { text( "n" ).into() }
//! #     fn audio_view( &self )   -> Element<Msg> { text( "a" ).into() }
//! #     fn _ex( &self ) -> Notebook<Msg> {
//! notebook()
//!     .page( "General",  self.general_view() )
//!     .page( "Network",  self.network_view() )
//!     .page( "Audio",    self.audio_view() )
//!     .selected( self.tab )
//!     .on_select( Msg::SelectTab )
//! #     }
//! # }
//! ```

use std::sync::Arc;

use crate::layout::column::column;
use crate::layout::spacer::spacer;
use crate::types::Length;

use super::Element;

mod theme;

#[ cfg( test ) ]
mod tests;

/// One page of a [`Notebook`] — a label for the tab strip and the
/// element to show when this page is active.
pub struct NotebookPage<Msg: Clone>
{
	pub( crate ) label: String,
	pub( crate ) view:  Element<Msg>,
}

/// Paginated tab container.
///
/// Renders a `tab_bar` strip at the top followed by the
/// active page's content. Pages whose index is not [`Self::selected`]
/// are dropped before draw so they do not consume layout time — a
/// notebook with 50 pages costs the same to draw as one with 5.
pub struct Notebook<Msg: Clone>
{
	pub( crate ) pages:     Vec<NotebookPage<Msg>>,
	pub( crate ) selected:  usize,
	pub( crate ) on_select: Option<Arc<dyn Fn( usize ) -> Msg>>,
}

impl<Msg: Clone + 'static> Notebook<Msg>
{
	/// Create an empty notebook with no pages and no selection.
	pub fn new() -> Self
	{
		Self
		{
			pages:     Vec::new(),
			selected:  0,
			on_select: None,
		}
	}

	/// Append a page. Returns `Self` for chaining.
	pub fn page(
		mut self,
		label: impl Into<String>,
		view:  impl Into<Element<Msg>>,
	) -> Self
	{
		self.pages.push( NotebookPage
		{
			label: label.into(),
			view:  view.into(),
		} );
		self
	}

	/// Index of the currently active page. Out-of-range values fall
	/// back to page 0; a notebook with no pages renders an empty
	/// container.
	pub fn selected( mut self, idx: usize ) -> Self
	{
		self.selected = idx;
		self
	}

	/// Callback invoked with the index of the tab the user tapped.
	pub fn on_select( mut self, f: impl Fn( usize ) -> Msg + 'static ) -> Self
	{
		self.on_select = Some( Arc::new( f ) );
		self
	}

	/// Build the `Element` tree representing this notebook.
	pub fn build( self ) -> Element<Msg>
	{
		use super::tab_bar::tabs;

		let labels: Vec<String> = self.pages.iter().map( |p| p.label.clone() ).collect();
		let selected = if self.selected < self.pages.len() { self.selected } else { 0 };
		let n_pages  = self.pages.len();

		// Build the tab strip; wire on_select if the caller provided one.
		let mut strip = tabs::<Msg, _, _>( labels ).selected( selected );
		if let Some( cb ) = self.on_select.clone()
		{
			strip = strip.on_select( move |i| cb( i ) );
		}

		// Drain to extract the active page's view by index.
		let mut pages = self.pages;
		let active_view: Element<Msg> = if pages.is_empty()
		{
			spacer().into()
		} else {
			let _ = n_pages;
			pages.swap_remove( selected )
				.view
		};

		column()
			.spacing( Length::widget( theme::SPACING ) )
			.push( strip )
			.push( active_view )
			.into()
	}
}

impl<Msg: Clone + 'static> Default for Notebook<Msg>
{
	fn default() -> Self { Self::new() }
}

impl<Msg: Clone + 'static> From<Notebook<Msg>> for Element<Msg>
{
	fn from( n: Notebook<Msg> ) -> Self { n.build() }
}

/// Create an empty [`Notebook`].
///
/// ```rust,no_run
/// # use ltk::{ notebook, text, Element, Notebook };
/// # #[ derive( Clone ) ] enum Msg { SelectTab( usize ) }
/// # struct App { tab: usize }
/// # impl App {
/// #     fn inbox_view( &self ) -> Element<Msg> { text( "i" ).into() }
/// #     fn sent_view( &self )  -> Element<Msg> { text( "s" ).into() }
/// #     fn _ex( &self ) -> Notebook<Msg> {
/// notebook()
///     .page( "Inbox",  self.inbox_view() )
///     .page( "Sent",   self.sent_view() )
///     .selected( self.tab )
///     .on_select( Msg::SelectTab )
/// #     }
/// # }
/// ```
pub fn notebook<Msg: Clone + 'static>() -> Notebook<Msg>
{
	Notebook::new()
}
