Conveyance/BasculeBridge.cs
using Sandbox;
using System.Collections.Generic;

/// <summary>
/// Coordinates one or more BasculeLeaf components as a single drawbridge. Exposes the
/// public control surface — Open() / Close() / Toggle() — and fans each command out to
/// every leaf so they move together.
///
/// For a classic double-leaf bascule, you'll wire two BasculeLeaf components in here (one
/// per bank). The coordinator doesn't care how many leaves there are — one, two, or more
/// all work the same way.
///
/// SETUP:
///   - Put this on an empty parent GameObject (e.g. "BasculeBridge") that organizes the
///     whole structure.
///   - Build each leaf separately (leaf rigidbody + hinge + BasculeLeaf component), per
///     the BasculeLeaf docs and the dev tips page.
///   - Drag each leaf's BasculeLeaf component into the Leaves list here.
///   - Wire Open()/Close()/Toggle() to whatever trigger you want — a lever, a pressure
///     plate, a proximity trigger — or just call them from the editor buttons to test.
///
/// MEETING POINT NOTE:
/// For a double-leaf bridge, the two leaves should meet (or nearly meet) in the middle when
/// closed, so a box can roll across the seam. Get this with editor PLACEMENT: position each
/// leaf so that at its ClosedAngle the leaves touch in the middle. Each leaf's hinge
/// MinAngle hard stop then guarantees they return to exactly that meeting position every
/// time — the precision only has to be got right once.
///
/// If the leaves visibly fight or jostle each other at the seam when closing, that's the
/// cue to add a small close stagger (close one leaf a fraction of a second before the
/// other). Not built yet — it's a prototype, and fire-and-forget is the simplest thing
/// that might just work. Add the stagger only if testing shows it's needed.
/// </summary>
public sealed class BasculeBridge : Component
{
	/// <summary>
	/// All the leaves that make up this bridge. One for a single-leaf bridge, two for a
	/// classic double bascule. Every Open/Close/Toggle command is sent to all of them.
	/// </summary>
	[Property] public List<BasculeLeaf> Leaves { get; set; } = new();

	/// <summary>
	/// True if the bridge is currently commanded open. Reflects the COMMAND — use
	/// IsFullyOpen / IsFullyClosed for actual physical state.
	/// </summary>
	public bool IsOpenCommanded { get; private set; } = false;

	/// <summary>Command every leaf to open.</summary>
	[Button( "Open Bridge" )]
	public void Open()
	{
		IsOpenCommanded = true;
		foreach ( var leaf in Leaves )
		{
			if ( leaf.IsValid() )
				leaf.Open();
		}
	}

	/// <summary>Command every leaf to close.</summary>
	[Button( "Close Bridge" )]
	public void Close()
	{
		IsOpenCommanded = false;
		foreach ( var leaf in Leaves )
		{
			if ( leaf.IsValid() )
				leaf.Close();
		}
	}

	/// <summary>Flip the whole bridge between open and closed.</summary>
	[Button( "Toggle Bridge" )]
	public void Toggle()
	{
		if ( IsOpenCommanded )
			Close();
		else
			Open();
	}

	/// <summary>
	/// True only when EVERY leaf has physically reached its open angle. Useful for gating
	/// logic — e.g. "only let the tall load through once the bridge is fully open."
	/// </summary>
	public bool IsFullyOpen
	{
		get
		{
			if ( Leaves is null || Leaves.Count == 0 ) return false;
			foreach ( var leaf in Leaves )
			{
				if ( !leaf.IsValid() || !leaf.IsFullyOpen )
					return false;
			}
			return true;
		}
	}

	/// <summary>
	/// True only when EVERY leaf has physically reached its closed angle. Useful for gating
	/// logic — e.g. "only let players cross once the bridge is fully closed and seated."
	/// </summary>
	public bool IsFullyClosed
	{
		get
		{
			if ( Leaves is null || Leaves.Count == 0 ) return false;
			foreach ( var leaf in Leaves )
			{
				if ( !leaf.IsValid() || !leaf.IsFullyClosed )
					return false;
			}
			return true;
		}
	}
}