using System;
using System.Collections.Generic;
using UnityEngine;
namespace Heroes.Battle
{
/// Display/behaviour state of a stack, driven as a finite state machine.
public enum UnitStackState
{
/// Waiting for its turn.
Idle,
/// It is this stack's turn: the player is choosing an action.
Active,
/// Gliding along towards a hex.
Moving,
/// Playing an attack against .
Attacking,
/// Stack was wiped out; no longer participates.
Dead,
}
///
/// Runtime state of one deployed stack of units, modelled as a small finite
/// state machine so the view can react to state transitions instead of being
/// driven imperatively. Pure C#: holds grid position, live health and current
/// action payloads (move path, attack target); the controller advances the
/// machine and the view animates each state.
///
/// State flow for a turn:
/// Idle --Activate--> Active
/// Active --StartMove--> Moving --CompleteMove--> Active
/// Active --StartAttack--> Attacking --CompleteAttack--> Active
/// Active --Deactivate--> Idle
/// (any) --Kill--> Dead
///
public sealed class UnitStack
{
/// Raised after changes. Argument is this stack.
public event Action StateChanged;
/// Raised after the stack's health/count changes (e.g. took damage).
public event Action HealthChanged;
private readonly List _movePath = new List();
public UnitStack(UnitDefinition definition, int count, BattleSide side, int slotIndex, int x, int y)
{
Definition = definition;
Side = side;
SlotIndex = slotIndex;
X = x;
Y = y;
MaxUnitHealth = definition != null ? Mathf.Max(1, definition.health) : 1;
TotalHealth = Mathf.Max(0, count) * MaxUnitHealth;
State = UnitStackState.Idle;
}
public UnitDefinition Definition { get; }
public BattleSide Side { get; }
/// Index of the army slot (0..6) this stack came from.
public int SlotIndex { get; }
/// Anchor column. Wide units extend towards the battlefield center.
public int X { get; private set; }
/// Row on the battlefield grid.
public int Y { get; private set; }
public UnitStackState State { get; private set; }
/// Health of a single unit in the stack.
public int MaxUnitHealth { get; }
/// Current total health of the whole stack.
public int TotalHealth { get; private set; }
/// Live unit count, derived from total health (a partially wounded top unit still counts).
public int Count => TotalHealth <= 0 ? 0 : Mathf.CeilToInt((float)TotalHealth / MaxUnitHealth);
public bool IsAlive => State != UnitStackState.Dead && TotalHealth > 0;
/// Movement range in hexes (equal to the unit's speed).
public int Speed => Definition != null ? Mathf.Max(1, Definition.speed) : 1;
public int Attack => Definition != null ? Mathf.Max(0, Definition.attack) : 0;
public int Defense => Definition != null ? Mathf.Max(0, Definition.defense) : 0;
public int HexWidth => Definition != null && Definition.hexWidth > 1 ? 2 : 1;
/// Hex anchors to walk through during the current state.
public IReadOnlyList MovePath => _movePath;
/// Target of the current state.
public UnitStack AttackTarget { get; private set; }
///
/// Columns occupied on row . The anchor sits at the army
/// edge; the second hex of a wide unit extends towards the center.
///
public IEnumerable OccupiedColumns() => ColumnsFor(Side, HexWidth, X);
/// Columns a stack of the given side/width would occupy at an arbitrary anchor.
public static IEnumerable ColumnsFor(BattleSide side, int width, int anchorX)
{
int w = Mathf.Max(1, width);
for (int i = 0; i < w; i++)
{
yield return side == BattleSide.Attacker ? anchorX + i : anchorX - i;
}
}
// --- State transitions -------------------------------------------------
public void Activate()
{
if (State == UnitStackState.Dead) return;
SetState(UnitStackState.Active);
}
public void Deactivate()
{
if (State == UnitStackState.Dead) return;
SetState(UnitStackState.Idle);
}
/// Enters the Moving state with the given path of hex anchors (excluding the start hex).
public void StartMove(IReadOnlyList path)
{
if (State == UnitStackState.Dead) return;
_movePath.Clear();
if (path != null) _movePath.AddRange(path);
SetState(UnitStackState.Moving);
}
/// Called by the view once the move animation finishes; commits the final position.
public void CompleteMove()
{
if (_movePath.Count > 0)
{
var end = _movePath[_movePath.Count - 1];
X = end.x;
Y = end.y;
}
_movePath.Clear();
if (State == UnitStackState.Dead) return;
SetState(UnitStackState.Active);
}
public void StartAttack(UnitStack target)
{
if (State == UnitStackState.Dead) return;
AttackTarget = target;
SetState(UnitStackState.Attacking);
}
/// Called by the view once the attack animation finishes.
public void CompleteAttack()
{
AttackTarget = null;
if (State == UnitStackState.Dead) return;
SetState(UnitStackState.Active);
}
/// Applies damage to the stack's total health; kills it if it drops to zero.
public void ApplyDamage(int amount)
{
if (amount <= 0 || State == UnitStackState.Dead) return;
TotalHealth = Mathf.Max(0, TotalHealth - amount);
HealthChanged?.Invoke(this);
if (TotalHealth <= 0) SetState(UnitStackState.Dead);
}
private void SetState(UnitStackState next)
{
if (State == next) return;
State = next;
StateChanged?.Invoke(this);
}
}
}