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

use super::*;

// ── Time arithmetic ───────────────────────────────────────────────────────

#[ test ]
fn time_round_trips_through_seconds()
{
	let t = Time::with_seconds( 13, 24, 56 );
	assert_eq!( Time::from_seconds( t.as_seconds() ), t );
}

#[ test ]
fn time_from_seconds_wraps_modulo_24h()
{
	// One second past midnight → 00:00:01.
	assert_eq!( Time::from_seconds( 24 * 3600 + 1 ), Time::with_seconds( 0, 0, 1 ) );
	// One second before midnight (negative) → 23:59:59.
	assert_eq!( Time::from_seconds( -1 ), Time::with_seconds( 23, 59, 59 ) );
}

#[ test ]
fn time_validity()
{
	assert!( Time::with_seconds( 23, 59, 59 ).is_valid() );
	assert!( !Time::with_seconds( 24, 0, 0 ).is_valid() );
	assert!( !Time::with_seconds( 0, 60, 0 ).is_valid() );
}

// ── Builders ──────────────────────────────────────────────────────────────

#[ derive( Clone, Debug, PartialEq, Eq ) ]
enum Msg { Pick( Time ) }

#[ test ]
fn defaults()
{
	let p: TimePicker<Msg> = time_picker( Time::new( 9, 30 ) );
	assert_eq!( p.value, Time::new( 9, 30 ) );
	assert_eq!( p.minute_step, 1 );
	assert_eq!( p.second_step, 1 );
	assert!( !p.seconds );
	assert!( !p.twelve_hour );
	assert!( p.on_change.is_none() );
}

#[ test ]
fn minute_step_clamps_to_thirty()
{
	let p: TimePicker<Msg> = time_picker( Time::new( 0, 0 ) ).minute_step( 99 );
	assert_eq!( p.minute_step, 30 );
	let p: TimePicker<Msg> = time_picker( Time::new( 0, 0 ) ).minute_step( 0 );
	assert_eq!( p.minute_step, 1 );
}

#[ test ]
fn on_change_callback_invokes_with_time()
{
	let p: TimePicker<Msg> = time_picker( Time::new( 9, 30 ) ).on_change( Msg::Pick );
	let cb = p.on_change.as_ref().expect( "set" );
	assert_eq!( cb( Time::new( 10, 0 ) ), Msg::Pick( Time::new( 10, 0 ) ) );
}

#[ test ]
fn build_does_not_panic_minimal()
{
	let _: Element<Msg> = time_picker::<Msg>( Time::new( 9, 30 ) ).build();
}

#[ test ]
fn build_does_not_panic_with_twelve_hour_and_seconds()
{
	let _: Element<Msg> = time_picker::<Msg>( Time::with_seconds( 13, 24, 56 ) )
		.twelve_hour( true )
		.seconds( true )
		.minute_step( 15 )
		.on_change( Msg::Pick )
		.build();
}

// ── Step snapping ────────────────────────────────────────────────────────

#[ test ]
fn snap_step_delta_advances_to_next_multiple_when_off_step()
{
	// Starting at :32 with step 5: up should land on :35 (+3),
	// down should land on :30 (−2).
	assert_eq!( snap_step_delta( 32, 5,  1 ),  3 );
	assert_eq!( snap_step_delta( 32, 5, -1 ), -2 );
}

#[ test ]
fn snap_step_delta_full_step_when_already_aligned()
{
	// Starting on a multiple of step takes a full step in the
	// requested direction.
	assert_eq!( snap_step_delta( 30, 5,  1 ),  5 );
	assert_eq!( snap_step_delta( 30, 5, -1 ), -5 );
}

#[ test ]
fn snap_step_delta_clamps_step_to_at_least_one()
{
	// A step of 0 is meaningless — clamp to 1 so the picker
	// always makes some progress on a click.
	assert_eq!( snap_step_delta( 0, 0, 1 ), 1 );
}

// ── Typed-input parser ────────────────────────────────────────────────────

#[ test ]
fn parse_clamped_digits_strips_non_numeric()
{
	assert_eq!( parse_clamped_digits( "0a3", 99 ), Some( 3 ) );
	assert_eq!( parse_clamped_digits( "  12  ", 59 ), Some( 12 ) );
}

#[ test ]
fn parse_clamped_digits_clamps_to_max()
{
	// A 12-hour field caps at 12; typing "23" reads as 23 then
	// clamps to 12.
	assert_eq!( parse_clamped_digits( "23", 12 ), Some( 12 ) );
	// A 24-hour field caps at 23.
	assert_eq!( parse_clamped_digits( "099", 23 ), Some( 23 ) );
}

#[ test ]
fn parse_clamped_digits_empty_returns_none()
{
	// `None` lets the caller distinguish "user erased the
	// field" from "user committed a zero", so the picker can
	// hold the previous value instead of jumping to midnight.
	assert!( parse_clamped_digits( "", 59 ).is_none() );
	assert!( parse_clamped_digits( "abc", 59 ).is_none() );
}
