using System;
using System.Collections.Generic;
using System.Linq;
using Cthangover.Core.Characters;
using Cthangover.Core.Settings;
using Cthangover.Core.Utils;
namespace Cthangover.Core.Relationship
{
///
/// Central registry and lifecycle dispatcher for recruit behaviours
/// and conditions. Behaviours () and
/// conditions () of every mod arrive
/// through the kernel's assembly consumers
/// (, )
/// as mods load, and leave again when their mod unloads — the kernel
/// stays the only place that enumerates assemblies.
///
/// Condition gating — evaluates every
/// registered condition conjunctively (logical AND), so a single
/// false blocks recruitment. This allows mods to compose
/// recruitment gates without coordination.
///
/// Id-based deduplication — a registration replaces any earlier
/// implementation with the same Id, so two mods shipping the same
/// id never double-dispatch notifications.
///
/// Tick registration — the first call to any lifecycle method
/// triggers ,
/// which registers for the scene's timer event. This avoids paying
/// the tick overhead unless at least one behaviour is active.
///
/// Singleton — accessed via the static Instance field
/// rather than a Lazy<T> wrapper because Godot
/// reflection-based discovery must tolerate partial-initialisation
/// scenarios where Lazy's thread-safety check could deadlock.
///
public class RecruitBehaviourRegistry
{
/// Eagerly-initialised singleton for reflection-safe access.
public static readonly RecruitBehaviourRegistry Instance = new();
private readonly List _behaviours = new();
private readonly List _conditions = new();
private readonly RecruitTickController _tickController = new();
private void EnsureTickRegistered()
{
_tickController.EnsureRegistered();
}
///
/// Registers a behaviour, replacing an earlier one with the same Id. Fed
/// by for every mod assembly.
///
public void RegisterBehaviour(IRecruitBehaviour behaviour)
{
if (behaviour == null)
return;
_behaviours.RemoveAll(existing => existing.Id == behaviour.Id);
_behaviours.Add(behaviour);
}
/// Removes exactly (its mod is being unloaded).
public void UnregisterBehaviour(IRecruitBehaviour behaviour)
{
_behaviours.Remove(behaviour);
}
///
/// Registers a condition, replacing an earlier one with the same Id. Fed
/// by for every mod assembly.
///
public void RegisterCondition(IRecruitCondition condition)
{
if (condition == null)
return;
_conditions.RemoveAll(existing => existing.Id == condition.Id);
_conditions.Add(condition);
}
/// Removes exactly (its mod is being unloaded).
public void UnregisterCondition(IRecruitCondition condition)
{
_conditions.Remove(condition);
}
///
/// Conjunctive gate: returns true only if every registered
/// returns true for the
/// given enemy and runtime state.
///
public bool CanRecruit(Character enemy, RuntimeData runtime)
{
return _conditions.All(c => c.CanRecruit(enemy, runtime));
}
///
/// Looks up a behaviour by its Id (linear scan —
/// behaviour lists are small enough that a dictionary is
/// unnecessary). Returns null if not found.
///
public IRecruitBehaviour Get(string id)
{
return _behaviours.Find(b => b.Id == id);
}
/// Returns all registered behaviours for external enumeration.
public IEnumerable All()
{
return _behaviours;
}
///
/// Dispatches to every
/// behaviour for every active recruit. Ensures tick registration
/// on first call via lazy init.
///
public void OnTick()
{
EnsureTickRegistered();
var runtime = GameData.Instance.Runtime;
if (runtime == null)
return;
var recruits = RecruitingData.Instance.Data;
foreach (var behaviour in _behaviours)
{
foreach (var recruit in recruits)
{
behaviour.OnTick(recruit, runtime, runtime.Time.Tick);
}
}
}
///
/// Dispatches to
/// every behaviour for the newly-added .
///
public void OnConfigure(Recruit recruit)
{
EnsureTickRegistered();
var runtime = GameData.Instance.Runtime;
if (runtime == null)
return;
foreach (var behaviour in _behaviours)
behaviour.ConfigureRecruit(recruit, runtime);
}
///
/// Dispatches to every
/// behaviour for the departing .
///
public void OnRemove(Recruit recruit)
{
EnsureTickRegistered();
var runtime = GameData.Instance.Runtime;
if (runtime == null)
return;
foreach (var behaviour in _behaviours)
behaviour.OnRemove(recruit, runtime);
}
}
}