/* 
 * Copyright (c)2009-2025 DemiVision, LLC. All Rights Reserved. The information 
 * herein is the CONFIDENTIAL and PROPRIETARY information of DemiVision, LLC. 
 */

using DXLib.UI.Layout;

using DXLib.Data;
using DXLib.Data.Model;

namespace DXLib.UI.Control.Checkbox;

/*
 * Implements one node in a treeview control with a root node and a variable number of child nodes. Each node has a
 * checkbox. The root node has three checked states indicating whether the children are all selected, partially
 * selected, or none selected.
 */
public class DXCheckboxNode : DXGridLayout
{
	/* Constants */
	private const double RootHt = DXCheckbox.DefaultSize;
	private const double IndentWd = 20;

	/* Events */
	public Action SelectionChanged { get; set; }

	/* Properties */
	public int SelectedCount => children.SelectedCount();
	public int ChildCount => children.Count;

	public bool AllSelected => children.AllSelected;
	public bool NoneSelected => children.NoneSelected;

	// Is root selection with no children valid?
	public bool AllowEmpty { get => allowEmpty; set { allowEmpty = value; root.IsTriState = value; } }

	/* Inherited */
	
	// Count
	
	/* Fields */
	private readonly DXCheckbox root;
	private readonly DXCheckboxList children;

	// Config
	private bool allowEmpty;

	/* Methods */
	public DXCheckboxNode()
	{
		BackgroundColor = DXColors.Light4;

		Padding = 0;
		RowSpacing = 0;
		ColumnSpacing = 0;

		Horizontal = LayoutOptions.Fill;
		Vertical = LayoutOptions.Fill;
		
		// Root node
		root = new DXCheckbox
		{
			IsBold = true,
			DisabledColor = DXCheckbox.DefaultDisabledColor,
			ValueChanged = OnRootChanged
		};
		
		// Child nodes
		children = new DXCheckboxList
		{
			ValueChanged = OnChildChanged
		};
		
		// 2 rows
		AddFixedRow( RootHt );			// 0: root
		AddStarRow();					// 1: children

		// 2 columns
		AddFixedColumn( IndentWd );		// 0: indent
		AddStarColumn();				// 1: children
		
		Add( root, 0, 0, 2, 1 );
		Add( children, 1, 1 );
	}

	// Returns total height required to display root and child nodes
	public double GetViewHt()
	{
		return (RootHt + children.GetViewHt());
	}

	/* Load */

	// Sets root checkbox value
	public void SetRoot( string key, string text )
	{
		root.Key = key;
		root.Text = text;
	}

	// Sets child checkboxes from LUT item list
	public void SetChildItems( string key )
	{
		children.LoadItems( key );
	}

	// Sets child checkboxes from list of key,value items
	public void SetChildItems( List<DXItem> items )
	{
		children.LoadItems( items );
	}

	// Removes specified checkbox item from list
	public void RemoveItem( string key )
	{
		children.RemoveItem( key );
	}

	// Sets child checkboxes from object list (objects must have 'ObjectName' field)
	public void SetChildObjects( IList<object> objects )
	{
		children.LoadObjects( objects );
	}

	// Sets child checkboxes from data model list
	public void SetChildModels( List<DXModel> models )
	{
		children.LoadModels( models );
	}

	/* Get Selected */

	// Determines if root node currently selected (or tri-state)
	public bool IsRootSelected()
	{
		bool? selected = root.IsChecked;

		return selected is true;
	}

	// Returns keys corresponding to currently selected checkboxes 
	public List<string> GetSelectedKeys()
	{
		return children.GetSelectedKeys();
	}

	// Returns list of item values corresponding to selected checkboxes
	public List<string> GetSelectedItems()
	{
		return children.GetSelectedItems();
	}

	// Returns objects corresponding to currently selected checkboxes
	public List<object> GetSelectedObjects()
	{
		return children.GetSelectedObjects();
	}

	/* Set Selected */

	// (De)selects root node (NOT cascading)
	public void SelectRoot( bool selected )
	{
		root.IsChecked = selected;
	}

	// Selects root and all child nodes
	public void SelectAll( bool selected )
	{
		children.SelectAll( selected );
	}
	 
	// (De)selects item for specified key
	public void SelectItem( string key, bool selected )
	{
		children.SelectItem( key, selected );
	}

	// Selects checkboxes given their unique item keys
	public void SelectItems( IList<string> keys )
	{
		children.SelectItems( keys );
	}

	// Selects checkboxes given their unique object keys
	public void SelectObjects( IList<string> keys )
	{
		children.SelectObjects( keys );
	}

	/* Event Callbacks */

	// User tapped root node checkbox
	private void OnRootChanged( DXCheckbox checkbox )
	{
		children.SuppressEvent = true;

		// Tri-state tap
		if ( allowEmpty )
		{
			HandleEmptyTap();
		}
		// Normal on/off tap
		else
		{
			HandleTap();
		}

		// Notify listener
		SelectionChanged?.Invoke();

		children.SuppressEvent = false;
		root.SuppressEvent = false;
	}

	// Handles a normal two state on/off tap
	private void HandleTap()
	{
		// Visually tri-state, but functionally only check/uncheck
		bool selected = (bool)root.IsChecked! || Equals( root.SelectedColor, DXCheckbox.IndeterminateColor );

		// All children select/unselect
		children.SelectAll( selected );

		// Update root state
		Color color = selected ? DXCheckbox.DefaultSelectedColor : DXCheckbox.IndeterminateColor;
		root.SetColor( color, false );

		root.SuppressEvent = true;
		root.IsChecked = selected;
	}

	// Handles a tri-state on/off/indeterminate tap
	private void HandleEmptyTap()
	{
		bool? selected = root.IsChecked;

		// Normal on/off
		if ( selected.HasValue )
		{
			HandleTap();
		}
		// Indeterminate
		else
		{
			children.SelectAll( false );

			root.SetColor( DXCheckbox.IndeterminateColor, false );

			root.SuppressEvent = true;
			root.IsChecked = null;
		}
	}

	// User tapped child node checkbox
	private void OnChildChanged( DXCheckbox checkbox )
	{
		root.SuppressEvent = true;

		int count = children.SelectedCount();

		// None selected (either deselect root or indeterminate)
		if ( count == 0 )
		{
			root.IsChecked = allowEmpty ? null : false;
		}
		// All selected, select root (green)
		else if ( count == ChildCount )
		{
			root.SetColor( DXCheckbox.DefaultSelectedColor, false );
			root.IsChecked = true;
		}
		// Some selected, select root (orange)
		else
		{
			root.SetColor( DXCheckbox.IndeterminateColor, false );
			root.IsChecked = true;
		}

		// Notify listener
		SelectionChanged?.Invoke();

		root.SuppressEvent = false;
	}
}

//
