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

use super::*;

fn st() -> ComboState { ComboState::new() }

#[ test ]
fn defaults_match_documented_values()
{
	let c: Combo<()> = combo( st(), vec![] );
	assert!( c.label.is_none() );
	assert!( !c.disabled );
	assert!( !c.multi_select );
	assert!( !c.searchable );
	assert_eq!( c.max_chips_visible, 4 );
	assert_eq!( c.popup_width,      320.0 );
	assert_eq!( c.popup_max_height, 280.0 );
}

#[ test ]
fn builders_round_trip_through_struct_fields()
{
	let c: Combo<()> = combo( st(), vec![] )
		.label( "Fruit" )
		.description( "Choose" )
		.placeholder( "Pick…" )
		.helper( "Type to search" )
		.disabled( true )
		.multi_select( true )
		.searchable( true )
		.popup_width( 400.0 )
		.popup_max_height( 320.0 )
		.max_chips_visible( 6 );

	assert_eq!( c.label.as_deref(),       Some( "Fruit"          ) );
	assert_eq!( c.description.as_deref(), Some( "Choose"         ) );
	assert_eq!( c.placeholder.as_deref(), Some( "Pick…"          ) );
	assert_eq!( c.helper.as_deref(),      Some( "Type to search" ) );
	assert!(   c.disabled );
	assert!(   c.multi_select );
	assert!(   c.searchable );
	assert_eq!( c.popup_width,       400.0 );
	assert_eq!( c.popup_max_height,  320.0 );
	assert_eq!( c.max_chips_visible, 6 );
}

#[ test ]
fn popup_width_is_clamped_to_eighty_minimum()
{
	let c: Combo<()> = combo( st(), vec![] ).popup_width( 10.0 );
	assert_eq!( c.popup_width, 80.0 );
}

#[ test ]
fn popup_max_height_is_clamped_to_eighty_minimum()
{
	let c: Combo<()> = combo( st(), vec![] ).popup_max_height( 10.0 );
	assert_eq!( c.popup_max_height, 80.0 );
}

#[ test ]
fn empty_query_returns_every_index()
{
	let items = vec![ "Apple".into(), "Banana".into(), "Cherry".into() ];
	let c: Combo<()> = combo( st(), items );
	assert_eq!( c.filtered_indices(), vec![ 0, 1, 2 ] );
}

#[ test ]
fn case_insensitive_contains_filter()
{
	let items = vec![ "Apple".into(), "Banana".into(), "Avocado".into(), "Cherry".into() ];
	let mut state = st();
	state.query = "a".into();
	let c: Combo<()> = combo( state, items );
	// "Apple", "Banana", "Avocado" all contain `a` in some case;
	// "Cherry" does not.
	assert_eq!( c.filtered_indices(), vec![ 0, 1, 2 ] );
}

#[ test ]
fn filter_match_is_case_insensitive_for_query_uppercase()
{
	let items = vec![ "apple".into(), "BANANA".into(), "Cherry".into() ];
	let mut state = st();
	state.query = "RY".into();
	let c: Combo<()> = combo( state, items );
	assert_eq!( c.filtered_indices(), vec![ 2 ] );
}

#[ test ]
fn filter_with_no_matches_returns_empty()
{
	let items = vec![ "Apple".into(), "Banana".into() ];
	let mut state = st();
	state.query = "zzz".into();
	let c: Combo<()> = combo( state, items );
	assert!( c.filtered_indices().is_empty() );
}

#[ test ]
fn closed_state_yields_no_popup()
{
	let c: Combo<()> = combo( st(), vec![ "x".into() ] );
	assert!( c.popup().is_none() );
}

#[ test ]
fn open_state_yields_popup_element()
{
	let mut state = st();
	state.is_open = true;
	let c: Combo<()> = combo( state, vec![ "x".into() ] );
	assert!( c.popup().is_some(), "open combo must produce a popup element" );
}

#[ test ]
fn combo_state_helpers_select_and_unselect()
{
	let mut s = ComboState::new();
	s.select( 1 );
	s.select( 3 );
	s.select( 1 );  // duplicate should be a no-op
	assert_eq!( s.selected, vec![ 1, 3 ] );

	s.unselect( 1 );
	assert_eq!( s.selected, vec![ 3 ] );

	s.unselect( 99 );  // missing index is a no-op
	assert_eq!( s.selected, vec![ 3 ] );
}

#[ test ]
fn combo_state_toggle_flips_open_flag()
{
	let mut s = ComboState::new();
	assert!( !s.is_open );
	s.toggle_open();
	assert!( s.is_open );
	s.toggle_open();
	assert!( !s.is_open );
}

#[ test ]
fn first_selected_label_returns_none_when_empty()
{
	let c: Combo<()> = combo( st(), vec![ "Apple".into() ] );
	assert!( c.first_selected_label().is_none() );
}

#[ test ]
fn first_selected_label_returns_first_selected_item()
{
	let mut state = st();
	state.selected = vec![ 1 ];
	let c: Combo<()> = combo( state, vec![ "Apple".into(), "Banana".into() ] );
	assert_eq!( c.first_selected_label(), Some( "Banana" ) );
}

#[ test ]
fn first_selected_label_handles_stale_index()
{
	let mut state = st();
	state.selected = vec![ 99 ];  // stale index — no panic
	let c: Combo<()> = combo( state, vec![ "Apple".into() ] );
	assert!( c.first_selected_label().is_none() );
}
