//! Shared building blocks for the phone UI: icons rasterised from the
//! theme catalogue, avatars, list rows and pill controls. Pure view code.

use std::sync::Arc;

use ltk::
{
	column, container, img_widget, pressable, row, spacer, stack, text,
	Color, Element, HAlign, VAlign,
};

use crate::app::Message;
use crate::palette;
use crate::types::{ CallKind, ContactRef, HistoryEntry };

/// Icon/text colour that reads on the cyan accent fill in both modes.
pub const ON_ACCENT: Color = Color::hex( 0x0A, 0x03, 0x2E );

/// A large filled circular call action (accept / decline / hang up).
pub fn circle_action( name: &str, bg: Color, msg: Message ) -> Element<Message>
{
	pressable( container( icon( name, 30.0, Color::WHITE ) ).background( bg ).radius( 36.0 ).padding( palette::dp( 21.0 ) ) )
		.on_press( msg )
		.into()
}

/// A catalogue icon (filled, falling back to line) tinted to `color`.
pub fn icon( name: &str, size: f32, color: Color ) -> Element<Message>
{
	match ltk::theme_icon_rgba( name, size.ceil() as u32 )
	{
		Some( ( rgba, w, h ) ) =>
		{
			let tinted = Arc::new( ltk::tint_symbolic( &rgba, color ) );
			img_widget( tinted, w, h ).size( size, size ).into()
		}
		None => spacer().width( size ).height( size ).into(),
	}
}

/// A transparent circular icon button.
pub fn round_icon_btn( name: &str, icon_size: f32, color: Color, msg: Message ) -> Element<Message>
{
	pressable( container( icon( name, icon_size, color ) ).padding( palette::dp( 9.0 ) ) )
		.on_press( msg )
		.into()
}

/// Initials-in-a-coloured-circle avatar. `None` renders a neutral bubble.
pub fn avatar( contact: Option<&ContactRef>, size: f32 ) -> Element<Message>
{
	let ( bg, label ) = match contact
	{
		Some( c ) => ( palette::avatar_color( &c.display_name ), palette::initials( &c.display_name ) ),
		None      => ( palette::chip(), String::from( "?" ) ),
	};

	let circle = container::<Message>( spacer().width( size ).height( size ) )
		.background( bg )
		.radius( size / 2.0 );

	stack::<Message>()
		.fit_content()
		.push( circle )
		.push_aligned(
			text( label ).size( size * 0.38 ).weight( 600 ).color( Color::WHITE ),
			HAlign::Center, VAlign::Center,
		)
		.into()
}

/// A Calls/Contacts segmented pill.
pub fn pill_tab( label: &str, active: bool, msg: Message ) -> Element<Message>
{
	let palette = ltk::theme_palette();
	let ( bg, fg ) = if active
	{
		( palette::chip_active(), palette::chip_active_fg() )
	} else {
		( palette::chip(), palette.text_primary )
	};

	pressable(
		container( text( label ).size( palette::dp( 13.0 ) ).weight( 500 ).color( fg ) )
			.background( bg )
			.radius( 100.0 )
			.padding_h( palette::dp( 18.0 ) )
			.padding_v( palette::dp( 8.0 ) )
	)
		.on_press( msg )
		.into()
}

/// Uppercase tertiary section header ("FAVORITES", "TODAY"…).
pub fn section_label( txt: &str ) -> Element<Message>
{
	container( text( txt.to_uppercase() ).size( palette::dp( 12.0 ) ).weight( 500 ).color( palette::text_tertiary() ) )
		.padding_left( palette::dp( 8.0 ) )
		.padding_top( palette::dp( 8.0 ) )
		.padding_bottom( palette::dp( 2.0 ) )
		.into()
}

/// One recents row: avatar · (name + direction/time) · info button.
pub fn recent_row( e: &HistoryEntry, now_ms: i64 ) -> Element<Message>
{
	let palette = ltk::theme_palette();
	let kind    = e.kind();
	let missed  = matches!( kind, CallKind::Missed );

	let name_color = if missed { palette::danger() } else { palette.text_primary };
	let sub_color  = if missed { palette::danger() } else { palette.text_secondary };

	let dir = match kind
	{
		CallKind::Incoming => "general/down-simple",
		CallKind::Outgoing => "general/up-simple",
		CallKind::Missed   => "general/down-simple",
	};

	let name = e.contact.as_ref()
		.map( |c| c.display_name.clone() )
		.unwrap_or_else( || e.number.as_str().to_string() );

	let label = e.contact.as_ref().and_then( |c| c.label.clone() ).unwrap_or_default();
	let when  = rel_time( now_ms, e.started_at_ms );
	let sub   = if label.is_empty() { when } else { format!( "{label} · {when}" ) };

	let text_block = column::<Message>()
		.fit_content()
		.spacing( palette::dp( 2.0 ) )
		.push( text( name ).size( palette::dp( 16.0 ) ).weight( 500 ).color( name_color ) )
		.push(
			row::<Message>()
				.spacing( palette::dp( 6.0 ) )
				.push( icon( dir, 14.0, sub_color ) )
				.push( text( sub ).size( palette::dp( 13.0 ) ).color( sub_color ) )
		);

	let mut r = row::<Message>()
		.spacing( palette::dp( 14.0 ) )
		.push( avatar( e.contact.as_ref(), 44.0 ) )
		.push( pressable( text_block ).on_press( Message::RedialFromHistory( e.number.clone() ) ) )
		.push( spacer() );

	if let Some( id ) = e.contact.as_ref().map( |c| c.id )
	{
		r = r.push( round_icon_btn( "feedback/information", 20.0, palette.text_secondary, Message::OpenContact( id ) ) );
	}

	container( r ).padding_h( palette::dp( 8.0 ) ).padding_v( palette::dp( 6.0 ) ).into()
}

/// One contacts row: avatar · (name + label/number) · optional star.
pub fn contact_row( c: &ContactRef ) -> Element<Message>
{
	let palette = ltk::theme_palette();

	let sub = match ( c.label.as_deref(), c.number.as_ref() )
	{
		( Some( l ), Some( n ) ) => format!( "{l} · {}", n.as_str() ),
		( _,         Some( n ) ) => n.as_str().to_string(),
		_                        => String::new(),
	};

	let text_block = column::<Message>()
		.fit_content()
		.spacing( palette::dp( 2.0 ) )
		.push( text( c.display_name.clone() ).size( palette::dp( 16.0 ) ).weight( 500 ).color( palette.text_primary ) )
		.push( text( sub ).size( palette::dp( 13.0 ) ).color( palette.text_secondary ) );

	let tap = pressable(
		row::<Message>()
			.spacing( palette::dp( 14.0 ) )
			.push( avatar( Some( c ), 44.0 ) )
			.push( text_block )
	)
		.on_press( call_msg( c ) );

	let mut r = row::<Message>()
		.spacing( palette::dp( 14.0 ) )
		.push( tap )
		.push( spacer() );

	if c.starred
	{
		r = r.push( icon( "actions/favourite", 16.0, palette.accent ) );
	}

	container( r ).padding_h( palette::dp( 8.0 ) ).padding_v( palette::dp( 6.0 ) ).into()
}

/// A favorites bubble: large avatar with the first name beneath it.
pub fn fav_bubble( c: &ContactRef ) -> Element<Message>
{
	let palette = ltk::theme_palette();
	let first   = c.display_name.split_whitespace().next().unwrap_or( "" ).to_string();

	let body = column::<Message>()
		.fit_content()
		.spacing( palette::dp( 6.0 ) )
		.push( avatar( Some( c ), 64.0 ) )
		.push( text( first ).size( palette::dp( 12.0 ) ).weight( 500 ).color( palette.text_primary ) );

	pressable( body ).on_press( call_msg( c ) ).into()
}

fn call_msg( c: &ContactRef ) -> Message
{
	match c.number.clone()
	{
		Some( n ) => Message::CallNumber( n ),
		None      => Message::OpenContact( c.id ),
	}
}

fn rel_time( now_ms: i64, then_ms: i64 ) -> String
{
	let mins = ( now_ms - then_ms ).max( 0 ) / 60_000;
	if mins < 1        { "now".to_string() }
	else if mins < 60  { format!( "{mins} min" ) }
	else if mins < 1440 { format!( "{} h", mins / 60 ) }
	else               { format!( "{} d", mins / 1440 ) }
}

/// Coarse day bucket for grouping recents without a calendar/timezone.
pub fn day_bucket( now_ms: i64, then_ms: i64 ) -> &'static str
{
	match ( now_ms - then_ms ).max( 0 ) / 86_400_000
	{
		0 => "Today",
		1 => "Yesterday",
		_ => "Earlier",
	}
}
