using static SpaceGame.Api.Shared.Runtime.SimulationRuntimeSupport;
using static SpaceGame.Api.Shared.Runtime.ShipBehaviorKinds;

namespace SpaceGame.Api.PlayerFaction.Simulation;

internal sealed class PlayerFactionService
{
    private const int MaxDecisionEntries = 64;
    private const int MaxAlerts = 32;
    private const string PlayerFactionDomainId = "player-faction";

    internal static bool IsPlayerFaction(IPlayerStateStore playerStateStore, string factionId) =>
      playerStateStore.GetPlayerFactions().Any(player =>
        string.Equals(player.SovereignFactionId, factionId, StringComparison.Ordinal));

    internal PlayerFactionRuntime? TryGetDomain(IPlayerStateStore playerStateStore, string playerId)
    {
        return playerStateStore.TryGetPlayerFaction(playerId, out var player) ? player : null;
    }

    internal PlayerFactionRuntime EnsureDomain(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId)
    {
        var player = playerStateStore.GetOrAddPlayerFaction(playerId, () => new PlayerFactionRuntime
        {
            Id = PlayerFactionDomainId,
            Label = "Pending Pilot",
            SovereignFactionId = string.Empty,
            RequiresOnboarding = true,
            CreatedAtUtc = world.GeneratedAtUtc,
            UpdatedAtUtc = world.GeneratedAtUtc,
        });

        EnsureBaseStructures(world, player);
        SyncRegistry(world, player);
        return player;
    }

    internal PlayerFactionRuntime CompleteOnboarding(
      SimulationWorld world,
      IPlayerStateStore playerStateStore,
      string playerId,
      CompletePlayerOnboardingRequest request)
    {
        var player = EnsureDomain(world, playerStateStore, playerId);
        if (!player.RequiresOnboarding)
        {
            throw new InvalidOperationException("Player onboarding has already been completed.");
        }

        var personaName = request.Name.Trim();
        if (personaName.Length < 2)
        {
            throw new InvalidOperationException("Player name must contain at least 2 characters.");
        }

        if (personaName.Length > 48)
        {
            throw new InvalidOperationException("Player name must contain at most 48 characters.");
        }

        var ownedFactionId = BuildOwnedFactionId(playerId);
        if (world.Factions.Any(faction => string.Equals(faction.Id, ownedFactionId, StringComparison.Ordinal)))
        {
            throw new InvalidOperationException($"Player faction '{ownedFactionId}' already exists in the current world.");
        }

        player.Label = personaName;
        player.PersonaName = personaName;
        player.RaceId = request.RaceId.Trim();
        player.SovereignFactionId = ownedFactionId;
        player.RequiresOnboarding = false;
        player.UpdatedAtUtc = DateTimeOffset.UtcNow;
        return player;
    }

    internal PlayerFactionRuntime EnsureInitializedDomain(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId)
    {
        var player = EnsureDomain(world, playerStateStore, playerId);
        if (player.RequiresOnboarding || string.IsNullOrWhiteSpace(player.SovereignFactionId))
        {
            throw new InvalidOperationException("Player onboarding must be completed before issuing gameplay commands.");
        }

        return player;
    }

    internal static string BuildOwnedFactionId(string playerId) =>
      $"player-{playerId.Replace("-", string.Empty, StringComparison.Ordinal).ToLowerInvariant()}";

    internal void Update(SimulationWorld world, IPlayerStateStore playerStateStore, float _deltaSeconds, ICollection<SimulationEventRecord> events)
    {
        if (playerStateStore.GetPlayerFactions().Count == 0)
        {
            return;
        }

        foreach (var player in playerStateStore.GetPlayerFactions())
        {
            EnsureBaseStructures(world, player);
            SyncRegistry(world, player);
            PrunePlayerState(world, player);
            RefreshGeopoliticalOrganizationContext(world, player);
            ReconcileOrganizationAssignments(world, player);
            ReconcileDirectiveScopes(player);
            RefreshProductionPrograms(world, player);
            ApplyStrategicIntegration(world, player);
            ApplyPolicies(world, player);
            ApplyAssignmentsAndDirectives(world, player, events);
            RefreshAlerts(world, player);
            player.UpdatedAtUtc = DateTimeOffset.UtcNow;
        }
    }

    internal PlayerFactionRuntime CreateOrganization(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, PlayerOrganizationCommandRequest request)
    {
        var player = EnsureInitializedDomain(world, playerStateStore, playerId);
        var id = CreateDomainId(request.Kind, request.Label, ExistingOrganizationIds(player));
        var nowUtc = DateTimeOffset.UtcNow;

        switch (NormalizeKind(request.Kind))
        {
            case "fleet":
                player.Fleets.Add(new PlayerFleetRuntime
                {
                    Id = id,
                    Label = request.Label,
                    Role = request.Role ?? "general-purpose",
                    FrontId = request.FrontId,
                    HomeSystemId = request.HomeSystemId,
                    HomeStationId = request.HomeStationId,
                    PolicyId = request.PolicyId,
                    AutomationPolicyId = request.AutomationPolicyId,
                    ReinforcementPolicyId = request.ReinforcementPolicyId,
                    UpdatedAtUtc = nowUtc,
                });
                player.AssetRegistry.FleetIds.Add(id);
                break;
            case "task-force":
                player.TaskForces.Add(new PlayerTaskForceRuntime
                {
                    Id = id,
                    Label = request.Label,
                    Role = request.Role ?? "task-force",
                    FleetId = request.ParentOrganizationId,
                    FrontId = request.FrontId,
                    PolicyId = request.PolicyId,
                    AutomationPolicyId = request.AutomationPolicyId,
                    UpdatedAtUtc = nowUtc,
                });
                player.AssetRegistry.TaskForceIds.Add(id);
                break;
            case "station-group":
                var stationGroup = new PlayerStationGroupRuntime
                {
                    Id = id,
                    Label = request.Label,
                    Role = request.Role ?? "industrial-group",
                    EconomicRegionId = request.ParentOrganizationId,
                    PolicyId = request.PolicyId,
                    AutomationPolicyId = request.AutomationPolicyId,
                    UpdatedAtUtc = nowUtc,
                };
                foreach (var itemId in request.FocusItemIds ?? [])
                {
                    stationGroup.FocusItemIds.Add(itemId);
                }
                player.StationGroups.Add(stationGroup);
                player.AssetRegistry.StationGroupIds.Add(id);
                break;
            case "economic-region":
                var region = new PlayerEconomicRegionRuntime
                {
                    Id = id,
                    Label = request.Label,
                    Role = request.Role ?? "balanced-region",
                    PolicyId = request.PolicyId,
                    AutomationPolicyId = request.AutomationPolicyId,
                    UpdatedAtUtc = nowUtc,
                };
                foreach (var systemId in request.SystemIds ?? [])
                {
                    if (world.Systems.Any(system => string.Equals(system.Definition.Id, systemId, StringComparison.Ordinal)))
                    {
                        region.SystemIds.Add(systemId);
                    }
                }
                player.EconomicRegions.Add(region);
                player.AssetRegistry.EconomicRegionIds.Add(id);
                break;
            case "front":
                var front = new PlayerFrontRuntime
                {
                    Id = id,
                    Label = request.Label,
                    Priority = request.Priority ?? 50f,
                    Posture = request.Role ?? "hold",
                    TargetFactionId = request.TargetFactionId,
                    UpdatedAtUtc = nowUtc,
                };
                foreach (var systemId in request.SystemIds ?? [])
                {
                    if (world.Systems.Any(system => string.Equals(system.Definition.Id, systemId, StringComparison.Ordinal)))
                    {
                        front.SystemIds.Add(systemId);
                    }
                }
                player.Fronts.Add(front);
                player.AssetRegistry.FrontIds.Add(id);
                break;
            case "reserve":
                player.Reserves.Add(new PlayerReserveGroupRuntime
                {
                    Id = id,
                    Label = request.Label,
                    ReserveKind = request.ReserveKind ?? "military",
                    HomeSystemId = request.HomeSystemId,
                    PolicyId = request.PolicyId,
                    UpdatedAtUtc = nowUtc,
                });
                player.AssetRegistry.ReserveIds.Add(id);
                break;
            default:
                throw new InvalidOperationException($"Unsupported organization kind '{request.Kind}'.");
        }

        AddDecision(player, "organization-created", $"Created {request.Kind} {request.Label}.", request.Kind, id);
        player.UpdatedAtUtc = nowUtc;
        return player;
    }

    internal PlayerFactionRuntime DeleteOrganization(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string organizationId)
    {
        var player = EnsureInitializedDomain(world, playerStateStore, playerId);
        RemoveOrganization(player, organizationId);
        player.Assignments.RemoveAll(assignment =>
          assignment.FleetId == organizationId ||
          assignment.TaskForceId == organizationId ||
          assignment.StationGroupId == organizationId ||
          assignment.EconomicRegionId == organizationId ||
          assignment.FrontId == organizationId ||
          assignment.ReserveId == organizationId);
        ReconcileOrganizationAssignments(world, player);
        ReconcileDirectiveScopes(player);
        AddDecision(player, "organization-deleted", $"Removed organization {organizationId}.", "organization", organizationId);
        player.UpdatedAtUtc = DateTimeOffset.UtcNow;
        return player;
    }

    internal PlayerFactionRuntime UpdateOrganizationMembership(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string organizationId, PlayerOrganizationMembershipCommandRequest request)
    {
        var player = EnsureInitializedDomain(world, playerStateStore, playerId);
        var kind = ResolveOrganizationKind(player, organizationId);
        switch (kind)
        {
            case "fleet":
                var fleet = player.Fleets.First(entity => entity.Id == organizationId);
                UpdateStringList(fleet.AssetIds, request.AssetIds, request.Replace, player.AssetRegistry.ShipIds);
                UpdateStringList(fleet.TaskForceIds, request.ChildOrganizationIds, request.Replace, player.AssetRegistry.TaskForceIds);
                fleet.UpdatedAtUtc = DateTimeOffset.UtcNow;
                break;
            case "task-force":
                var taskForce = player.TaskForces.First(entity => entity.Id == organizationId);
                UpdateStringList(taskForce.AssetIds, request.AssetIds, request.Replace, player.AssetRegistry.ShipIds);
                taskForce.UpdatedAtUtc = DateTimeOffset.UtcNow;
                break;
            case "station-group":
                var stationGroup = player.StationGroups.First(entity => entity.Id == organizationId);
                UpdateStringList(stationGroup.StationIds, request.AssetIds, request.Replace, player.AssetRegistry.StationIds);
                stationGroup.UpdatedAtUtc = DateTimeOffset.UtcNow;
                break;
            case "economic-region":
                var region = player.EconomicRegions.First(entity => entity.Id == organizationId);
                UpdateStringList(region.SystemIds, request.SystemIds, request.Replace, world.Systems.Select(system => system.Definition.Id));
                UpdateStringList(region.StationGroupIds, request.ChildOrganizationIds, request.Replace, player.AssetRegistry.StationGroupIds);
                region.UpdatedAtUtc = DateTimeOffset.UtcNow;
                break;
            case "front":
                var front = player.Fronts.First(entity => entity.Id == organizationId);
                UpdateStringList(front.SystemIds, request.SystemIds, request.Replace, world.Systems.Select(system => system.Definition.Id));
                UpdateStringList(front.FleetIds, request.ChildOrganizationIds, request.Replace, player.AssetRegistry.FleetIds);
                front.UpdatedAtUtc = DateTimeOffset.UtcNow;
                break;
            case "reserve":
                var reserve = player.Reserves.First(entity => entity.Id == organizationId);
                UpdateStringList(reserve.AssetIds, request.AssetIds, request.Replace, player.AssetRegistry.ShipIds);
                UpdateStringList(reserve.FrontIds, request.FrontIds, request.Replace, player.AssetRegistry.FrontIds);
                reserve.UpdatedAtUtc = DateTimeOffset.UtcNow;
                break;
            default:
                throw new InvalidOperationException($"Unknown organization '{organizationId}'.");
        }

        ReconcileOrganizationAssignments(world, player);
        ReconcileDirectiveScopes(player);
        AddDecision(player, "membership-updated", $"Updated membership for {organizationId}.", "organization", organizationId);
        player.UpdatedAtUtc = DateTimeOffset.UtcNow;
        return player;
    }

    internal PlayerFactionRuntime UpsertDirective(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string? directiveId, PlayerDirectiveCommandRequest request)
    {
        var player = EnsureInitializedDomain(world, playerStateStore, playerId);
        var directive = directiveId is null
          ? null
          : player.Directives.FirstOrDefault(candidate => string.Equals(candidate.Id, directiveId, StringComparison.Ordinal));
        if (directive is null)
        {
            directive = new PlayerDirectiveRuntime
            {
                Id = directiveId ?? CreateDomainId("directive", request.Label, player.Directives.Select(candidate => candidate.Id)),
                Label = request.Label,
                CreatedAtUtc = DateTimeOffset.UtcNow,
            };
            player.Directives.Add(directive);
        }

        directive.Label = request.Label;
        directive.Kind = request.Kind;
        directive.ScopeKind = request.ScopeKind;
        directive.ScopeId = request.ScopeId;
        directive.BehaviorKind = request.BehaviorKind;
        directive.UseOrders = request.UseOrders;
        directive.StagingOrderKind = request.StagingOrderKind;
        directive.TargetEntityId = request.TargetEntityId;
        directive.TargetSystemId = request.TargetSystemId;
        directive.TargetPosition = request.TargetPosition is null ? null : new Vector3(request.TargetPosition.X, request.TargetPosition.Y, request.TargetPosition.Z);
        directive.HomeSystemId = request.HomeSystemId;
        directive.HomeStationId = request.HomeStationId;
        directive.SourceStationId = request.SourceStationId;
        directive.DestinationStationId = request.DestinationStationId;
        directive.ItemId = request.ItemId;
        directive.PreferredNodeId = request.PreferredNodeId;
        directive.PreferredConstructionSiteId = request.PreferredConstructionSiteId;
        directive.PreferredModuleId = request.PreferredModuleId;
        directive.Priority = request.Priority;
        directive.Radius = MathF.Max(0f, request.Radius ?? directive.Radius);
        directive.WaitSeconds = MathF.Max(0f, request.WaitSeconds ?? directive.WaitSeconds);
        directive.MaxSystemRange = Math.Max(0, request.MaxSystemRange ?? directive.MaxSystemRange);
        directive.KnownStationsOnly = request.KnownStationsOnly ?? directive.KnownStationsOnly;
        directive.PatrolPoints.Clear();
        foreach (var point in request.PatrolPoints ?? [])
        {
            directive.PatrolPoints.Add(new Vector3(point.X, point.Y, point.Z));
        }
        directive.RepeatOrders.Clear();
        foreach (var template in request.RepeatOrders ?? [])
        {
            directive.RepeatOrders.Add(new ShipOrderTemplateRuntime
            {
                Kind = template.Kind,
                Label = template.Label,
                TargetEntityId = template.TargetEntityId,
                TargetSystemId = template.TargetSystemId,
                TargetPosition = template.TargetPosition is null ? null : new Vector3(template.TargetPosition.X, template.TargetPosition.Y, template.TargetPosition.Z),
                SourceStationId = template.SourceStationId,
                DestinationStationId = template.DestinationStationId,
                ItemId = template.ItemId,
                NodeId = template.NodeId,
                ConstructionSiteId = template.ConstructionSiteId,
                ModuleId = template.ModuleId,
                WaitSeconds = MathF.Max(0f, template.WaitSeconds ?? 0f),
                Radius = MathF.Max(0f, template.Radius ?? 0f),
                MaxSystemRange = template.MaxSystemRange,
                KnownStationsOnly = template.KnownStationsOnly ?? false,
            });
        }
        directive.PolicyId = request.PolicyId;
        directive.AutomationPolicyId = request.AutomationPolicyId;
        directive.Notes = request.Notes;
        directive.UpdatedAtUtc = DateTimeOffset.UtcNow;

        AddDecision(player, "directive-upserted", $"Updated directive {directive.Label}.", "directive", directive.Id);
        player.UpdatedAtUtc = directive.UpdatedAtUtc;
        return player;
    }

    internal PlayerFactionRuntime DeleteDirective(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string directiveId)
    {
        var player = EnsureInitializedDomain(world, playerStateStore, playerId);
        player.Directives.RemoveAll(directive => directive.Id == directiveId);
        foreach (var assignment in player.Assignments.Where(assignment => assignment.DirectiveId == directiveId))
        {
            assignment.DirectiveId = null;
        }
        ReconcileDirectiveScopes(player);
        AddDecision(player, "directive-deleted", $"Removed directive {directiveId}.", "directive", directiveId);
        player.UpdatedAtUtc = DateTimeOffset.UtcNow;
        return player;
    }

    internal PlayerFactionRuntime UpsertPolicy(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string? policyId, PlayerPolicyCommandRequest request)
    {
        var player = EnsureInitializedDomain(world, playerStateStore, playerId);
        var policy = policyId is null
          ? null
          : player.Policies.FirstOrDefault(candidate => string.Equals(candidate.Id, policyId, StringComparison.Ordinal));
        if (policy is null)
        {
            policy = new PlayerFactionPolicyRuntime
            {
                Id = policyId ?? CreateDomainId("policy", request.Label, player.Policies.Select(candidate => candidate.Id)),
                Label = request.Label,
            };
            player.Policies.Add(policy);
        }

        policy.Label = request.Label;
        policy.ScopeKind = request.ScopeKind;
        policy.ScopeId = request.ScopeId;
        policy.AllowDelegatedCombat = request.AllowDelegatedCombat;
        policy.AllowDelegatedTrade = request.AllowDelegatedTrade;
        policy.ReserveCreditsRatio = Math.Clamp(request.ReserveCreditsRatio, 0f, 1f);
        policy.ReserveMilitaryRatio = Math.Clamp(request.ReserveMilitaryRatio, 0f, 1f);
        if (request.TradeAccessPolicy is not null)
        {
            policy.TradeAccessPolicy = request.TradeAccessPolicy;
        }
        if (request.DockingAccessPolicy is not null)
        {
            policy.DockingAccessPolicy = request.DockingAccessPolicy;
        }
        if (request.ConstructionAccessPolicy is not null)
        {
            policy.ConstructionAccessPolicy = request.ConstructionAccessPolicy;
        }
        if (request.OperationalRangePolicy is not null)
        {
            policy.OperationalRangePolicy = request.OperationalRangePolicy;
        }
        if (request.CombatEngagementPolicy is not null)
        {
            policy.CombatEngagementPolicy = request.CombatEngagementPolicy;
        }
        if (request.AvoidHostileSystems.HasValue)
        {
            policy.AvoidHostileSystems = request.AvoidHostileSystems.Value;
        }
        if (request.FleeHullRatio.HasValue)
        {
            policy.FleeHullRatio = Math.Clamp(request.FleeHullRatio.Value, 0f, 1f);
        }
        if (request.BlacklistedSystemIds is not null)
        {
            policy.BlacklistedSystemIds.Clear();
            foreach (var systemId in request.BlacklistedSystemIds)
            {
                policy.BlacklistedSystemIds.Add(systemId);
            }
        }
        policy.Notes = request.Notes;
        policy.UpdatedAtUtc = DateTimeOffset.UtcNow;

        var policySet = EnsurePolicySet(world, player, policy, request.PolicySetId);
        ApplyPolicySetRequest(policySet, request);
        policy.PolicySetId = policySet.Id;

        AddDecision(player, "policy-upserted", $"Updated policy {policy.Label}.", "policy", policy.Id);
        player.UpdatedAtUtc = policy.UpdatedAtUtc;
        return player;
    }

    internal PlayerFactionRuntime UpsertAutomationPolicy(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string? automationPolicyId, PlayerAutomationPolicyCommandRequest request)
    {
        var player = EnsureInitializedDomain(world, playerStateStore, playerId);
        var policy = automationPolicyId is null
          ? null
          : player.AutomationPolicies.FirstOrDefault(candidate => string.Equals(candidate.Id, automationPolicyId, StringComparison.Ordinal));
        if (policy is null)
        {
            policy = new PlayerAutomationPolicyRuntime
            {
                Id = automationPolicyId ?? CreateDomainId("automation", request.Label, player.AutomationPolicies.Select(candidate => candidate.Id)),
                Label = request.Label,
            };
            player.AutomationPolicies.Add(policy);
        }

        policy.Label = request.Label;
        policy.ScopeKind = request.ScopeKind;
        policy.ScopeId = request.ScopeId;
        policy.Enabled = request.Enabled;
        policy.BehaviorKind = request.BehaviorKind;
        policy.UseOrders = request.UseOrders;
        policy.StagingOrderKind = request.StagingOrderKind;
        policy.MaxSystemRange = Math.Max(0, request.MaxSystemRange);
        policy.KnownStationsOnly = request.KnownStationsOnly;
        policy.Radius = MathF.Max(0f, request.Radius);
        policy.WaitSeconds = MathF.Max(0f, request.WaitSeconds);
        policy.PreferredItemId = request.PreferredItemId;
        policy.Notes = request.Notes;
        policy.RepeatOrders.Clear();
        foreach (var template in request.RepeatOrders ?? [])
        {
            policy.RepeatOrders.Add(new ShipOrderTemplateRuntime
            {
                Kind = template.Kind,
                Label = template.Label,
                TargetEntityId = template.TargetEntityId,
                TargetSystemId = template.TargetSystemId,
                TargetPosition = template.TargetPosition is null ? null : new Vector3(template.TargetPosition.X, template.TargetPosition.Y, template.TargetPosition.Z),
                SourceStationId = template.SourceStationId,
                DestinationStationId = template.DestinationStationId,
                ItemId = template.ItemId,
                NodeId = template.NodeId,
                ConstructionSiteId = template.ConstructionSiteId,
                ModuleId = template.ModuleId,
                WaitSeconds = MathF.Max(0f, template.WaitSeconds ?? 0f),
                Radius = MathF.Max(0f, template.Radius ?? 0f),
                MaxSystemRange = template.MaxSystemRange,
                KnownStationsOnly = template.KnownStationsOnly ?? false,
            });
        }
        policy.UpdatedAtUtc = DateTimeOffset.UtcNow;

        AddDecision(player, "automation-upserted", $"Updated automation policy {policy.Label}.", "automation-policy", policy.Id);
        player.UpdatedAtUtc = policy.UpdatedAtUtc;
        return player;
    }

    internal PlayerFactionRuntime UpsertReinforcementPolicy(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string? reinforcementPolicyId, PlayerReinforcementPolicyCommandRequest request)
    {
        var player = EnsureInitializedDomain(world, playerStateStore, playerId);
        var policy = reinforcementPolicyId is null
          ? null
          : player.ReinforcementPolicies.FirstOrDefault(candidate => string.Equals(candidate.Id, reinforcementPolicyId, StringComparison.Ordinal));
        if (policy is null)
        {
            policy = new PlayerReinforcementPolicyRuntime
            {
                Id = reinforcementPolicyId ?? CreateDomainId("reinforcement", request.Label, player.ReinforcementPolicies.Select(candidate => candidate.Id)),
                Label = request.Label,
            };
            player.ReinforcementPolicies.Add(policy);
        }

        policy.Label = request.Label;
        policy.ScopeKind = request.ScopeKind;
        policy.ScopeId = request.ScopeId;
        policy.ShipKind = request.ShipKind;
        policy.DesiredAssetCount = Math.Max(0, request.DesiredAssetCount);
        policy.MinimumReserveCount = Math.Max(0, request.MinimumReserveCount);
        policy.AutoTransferReserves = request.AutoTransferReserves;
        policy.AutoQueueProduction = request.AutoQueueProduction;
        policy.SourceReserveId = request.SourceReserveId;
        policy.TargetFrontId = request.TargetFrontId;
        policy.Notes = request.Notes;
        policy.UpdatedAtUtc = DateTimeOffset.UtcNow;

        AddDecision(player, "reinforcement-upserted", $"Updated reinforcement policy {policy.Label}.", "reinforcement-policy", policy.Id);
        player.UpdatedAtUtc = policy.UpdatedAtUtc;
        return player;
    }

    internal PlayerFactionRuntime UpsertProductionProgram(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string? productionProgramId, PlayerProductionProgramCommandRequest request)
    {
        var player = EnsureInitializedDomain(world, playerStateStore, playerId);
        var program = productionProgramId is null
          ? null
          : player.ProductionPrograms.FirstOrDefault(candidate => string.Equals(candidate.Id, productionProgramId, StringComparison.Ordinal));
        if (program is null)
        {
            program = new PlayerProductionProgramRuntime
            {
                Id = productionProgramId ?? CreateDomainId("production", request.Label, player.ProductionPrograms.Select(candidate => candidate.Id)),
                Label = request.Label,
            };
            player.ProductionPrograms.Add(program);
        }

        program.Label = request.Label;
        program.Kind = request.Kind;
        program.TargetShipKind = request.TargetShipKind;
        program.TargetModuleId = request.TargetModuleId;
        program.TargetItemId = request.TargetItemId;
        program.TargetCount = Math.Max(0, request.TargetCount);
        program.StationGroupId = request.StationGroupId;
        program.ReinforcementPolicyId = request.ReinforcementPolicyId;
        program.Notes = request.Notes;
        program.UpdatedAtUtc = DateTimeOffset.UtcNow;

        AddDecision(player, "production-upserted", $"Updated production program {program.Label}.", "production-program", program.Id);
        player.UpdatedAtUtc = program.UpdatedAtUtc;
        return player;
    }

    internal PlayerFactionRuntime UpsertAssignment(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string assetId, PlayerAssetAssignmentCommandRequest request)
    {
        var player = EnsureInitializedDomain(world, playerStateStore, playerId);
        var assignment = player.Assignments.FirstOrDefault(candidate =>
          string.Equals(candidate.AssetId, assetId, StringComparison.Ordinal) &&
          string.Equals(candidate.AssetKind, request.AssetKind, StringComparison.Ordinal));
        if (assignment is null)
        {
            assignment = new PlayerAssignmentRuntime
            {
                Id = $"assignment-{request.AssetKind}-{assetId}",
                AssetKind = request.AssetKind,
                AssetId = assetId,
            };
            player.Assignments.Add(assignment);
        }

        if (request.ClearConflicts)
        {
            RemoveAssetFromOrganizations(player, request.AssetKind, assetId);
        }

        if (request.FleetId is not null)
        {
            AddAssetToFleet(player, request.FleetId, assetId);
        }
        if (request.TaskForceId is not null)
        {
            AddAssetToTaskForce(player, request.TaskForceId, assetId);
        }
        if (request.StationGroupId is not null)
        {
            AddAssetToStationGroup(player, request.StationGroupId, assetId);
        }
        if (request.ReserveId is not null)
        {
            AddAssetToReserve(player, request.ReserveId, assetId);
        }

        assignment.FleetId = request.FleetId;
        assignment.TaskForceId = request.TaskForceId;
        assignment.StationGroupId = request.StationGroupId;
        assignment.EconomicRegionId = request.EconomicRegionId ?? assignment.EconomicRegionId;
        assignment.FrontId = request.FrontId ?? assignment.FrontId;
        assignment.ReserveId = request.ReserveId;
        assignment.DirectiveId = request.DirectiveId;
        assignment.PolicyId = request.PolicyId;
        assignment.AutomationPolicyId = request.AutomationPolicyId;
        assignment.Role = request.Role;
        assignment.Status = "active";
        assignment.UpdatedAtUtc = DateTimeOffset.UtcNow;

        ReconcileOrganizationAssignments(world, player);
        ReconcileDirectiveScopes(player);
        AddDecision(player, "assignment-upserted", $"Assigned {request.AssetKind} {assetId}.", request.AssetKind, assetId);
        player.UpdatedAtUtc = assignment.UpdatedAtUtc;
        return player;
    }

    internal PlayerFactionRuntime UpdateStrategicIntent(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, PlayerStrategicIntentCommandRequest request)
    {
        var player = EnsureInitializedDomain(world, playerStateStore, playerId);
        player.StrategicIntent.StrategicPosture = request.StrategicPosture;
        player.StrategicIntent.EconomicPosture = request.EconomicPosture;
        player.StrategicIntent.MilitaryPosture = request.MilitaryPosture;
        player.StrategicIntent.LogisticsPosture = request.LogisticsPosture;
        player.StrategicIntent.DesiredReserveRatio = Math.Clamp(request.DesiredReserveRatio, 0f, 1f);
        player.StrategicIntent.AllowDelegatedCombatAutomation = request.AllowDelegatedCombatAutomation;
        player.StrategicIntent.AllowDelegatedEconomicAutomation = request.AllowDelegatedEconomicAutomation;
        player.StrategicIntent.Notes = request.Notes;
        player.UpdatedAtUtc = DateTimeOffset.UtcNow;
        AddDecision(player, "strategic-intent-updated", "Updated player strategic intent.", "player-faction", player.Id);
        return player;
    }

    internal ShipRuntime? EnqueueDirectShipOrder(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string shipId, ShipOrderCommandRequest request)
    {
        var player = EnsureInitializedDomain(world, playerStateStore, playerId);
        if (!player.AssetRegistry.ShipIds.Contains(shipId))
        {
            return null;
        }

        var ship = world.Ships.FirstOrDefault(candidate => candidate.Id == shipId);
        if (ship is null)
        {
            return null;
        }

        if (ship.OrderQueue.Count >= 8)
        {
            throw new InvalidOperationException("Order queue is full.");
        }

        ship.OrderQueue.Add(new ShipOrderRuntime
        {
            Id = $"order-{ship.Id}-{Guid.NewGuid():N}",
            Kind = request.Kind,
            SourceKind = ShipOrderSourceKind.Player,
            SourceId = playerId,
            Priority = request.Priority,
            InterruptCurrentPlan = request.InterruptCurrentPlan,
            Label = request.Label,
            TargetEntityId = request.TargetEntityId,
            TargetSystemId = request.TargetSystemId,
            TargetPosition = request.TargetPosition is null ? null : new Vector3(request.TargetPosition.X, request.TargetPosition.Y, request.TargetPosition.Z),
            SourceStationId = request.SourceStationId,
            DestinationStationId = request.DestinationStationId,
            ItemId = request.ItemId,
            NodeId = request.NodeId,
            ConstructionSiteId = request.ConstructionSiteId,
            ModuleId = request.ModuleId,
            WaitSeconds = MathF.Max(0f, request.WaitSeconds ?? 0f),
            Radius = MathF.Max(0f, request.Radius ?? 0f),
            MaxSystemRange = request.MaxSystemRange,
            KnownStationsOnly = request.KnownStationsOnly ?? false,
        });

        AddDecision(player, "ship-order-enqueued", $"Queued {request.Kind} for {ship.Definition.Name}.", "ship", shipId);
        player.UpdatedAtUtc = DateTimeOffset.UtcNow;
        ship.ControlSourceKind = "player-order";
        ship.ControlSourceId = ship.OrderQueue
          .Where(order => order.SourceKind == ShipOrderSourceKind.Player)
          .OrderByDescending(order => order.Priority)
          .ThenBy(order => order.CreatedAtUtc)
          .Select(order => order.Id)
          .FirstOrDefault();
        ship.ControlReason = request.Label ?? request.Kind;
        ship.NeedsReplan = true;
        ship.LastReplanReason = "player-order-enqueued";
        ship.LastDeltaSignature = string.Empty;
        return ship;
    }

    internal ShipRuntime? RemoveDirectShipOrder(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string shipId, string orderId)
    {
        var player = EnsureInitializedDomain(world, playerStateStore, playerId);
        if (!player.AssetRegistry.ShipIds.Contains(shipId))
        {
            return null;
        }

        var ship = world.Ships.FirstOrDefault(candidate => candidate.Id == shipId);
        if (ship is null)
        {
            return null;
        }

        var removed = ship.OrderQueue.RemoveAll(order => order.Id == orderId);
        if (removed > 0)
        {
            AddDecision(player, "ship-order-removed", $"Removed order {orderId} from {ship.Definition.Name}.", "ship", shipId);
            player.UpdatedAtUtc = DateTimeOffset.UtcNow;
        }

        ship.ControlSourceKind = ship.OrderQueue.Any(order => order.SourceKind == ShipOrderSourceKind.Player)
          ? "player-order"
          : "player-manual";
        ship.ControlSourceId = ship.OrderQueue
          .Where(order => order.SourceKind == ShipOrderSourceKind.Player)
          .OrderByDescending(order => order.Priority)
          .ThenBy(order => order.CreatedAtUtc)
          .Select(order => order.Id)
          .FirstOrDefault();
        ship.ControlReason = ship.OrderQueue
          .Where(order => order.SourceKind == ShipOrderSourceKind.Player)
          .OrderByDescending(order => order.Priority)
          .ThenBy(order => order.CreatedAtUtc)
          .Select(order => order.Label ?? order.Kind)
          .FirstOrDefault()
          ?? "manual-player-control";
        ship.NeedsReplan = true;
        ship.LastReplanReason = "player-order-removed";
        ship.LastDeltaSignature = string.Empty;
        return ship;
    }

    internal ShipRuntime? ConfigureDirectShipBehavior(SimulationWorld world, IPlayerStateStore playerStateStore, string playerId, string shipId, ShipDefaultBehaviorCommandRequest request)
    {
        var player = EnsureInitializedDomain(world, playerStateStore, playerId);
        if (!player.AssetRegistry.ShipIds.Contains(shipId))
        {
            return null;
        }

        var ship = world.Ships.FirstOrDefault(candidate => candidate.Id == shipId);
        if (ship is null)
        {
            return null;
        }

        var directiveId = $"player-directive-ship-{shipId}";
        var directive = player.Directives.FirstOrDefault(candidate => candidate.Id == directiveId);
        if (directive is null)
        {
            directive = new PlayerDirectiveRuntime
            {
                Id = directiveId,
                Label = $"Direct control {ship.Definition.Name}",
                ScopeKind = "ship",
                ScopeId = shipId,
                Kind = "direct-control",
                CreatedAtUtc = DateTimeOffset.UtcNow,
            };
            player.Directives.Add(directive);
        }

        directive.Label = $"Direct control {ship.Definition.Name}";
        directive.Kind = "direct-control";
        directive.ScopeKind = "ship";
        directive.ScopeId = shipId;
        directive.BehaviorKind = request.Kind;
        directive.UseOrders = false;
        directive.StagingOrderKind = null;
        directive.TargetEntityId = request.TargetEntityId;
        directive.TargetSystemId = request.AreaSystemId;
        directive.TargetPosition = request.TargetPosition is null ? null : new Vector3(request.TargetPosition.X, request.TargetPosition.Y, request.TargetPosition.Z);
        directive.HomeSystemId = request.HomeSystemId ?? ship.SystemId;
        directive.HomeStationId = request.HomeStationId;
        directive.SourceStationId = request.HomeStationId;
        directive.DestinationStationId = null;
        directive.ItemId = request.ItemId;
        directive.PreferredNodeId = request.PreferredNodeId;
        directive.PreferredConstructionSiteId = request.PreferredConstructionSiteId;
        directive.PreferredModuleId = request.PreferredModuleId;
        directive.Priority = 100;
        directive.Radius = MathF.Max(0f, request.Radius ?? ship.DefaultBehavior.Radius);
        directive.WaitSeconds = MathF.Max(0f, request.WaitSeconds ?? ship.DefaultBehavior.WaitSeconds);
        directive.MaxSystemRange = Math.Max(0, request.MaxSystemRange ?? ship.DefaultBehavior.MaxSystemRange);
        directive.KnownStationsOnly = request.KnownStationsOnly ?? ship.DefaultBehavior.KnownStationsOnly;
        directive.PatrolPoints.Clear();
        foreach (var point in request.PatrolPoints ?? [])
        {
            directive.PatrolPoints.Add(new Vector3(point.X, point.Y, point.Z));
        }
        directive.RepeatOrders.Clear();
        foreach (var template in request.RepeatOrders ?? [])
        {
            directive.RepeatOrders.Add(new ShipOrderTemplateRuntime
            {
                Kind = template.Kind,
                Label = template.Label,
                TargetEntityId = template.TargetEntityId,
                TargetSystemId = template.TargetSystemId,
                TargetPosition = template.TargetPosition is null ? null : new Vector3(template.TargetPosition.X, template.TargetPosition.Y, template.TargetPosition.Z),
                SourceStationId = template.SourceStationId,
                DestinationStationId = template.DestinationStationId,
                ItemId = template.ItemId,
                NodeId = template.NodeId,
                ConstructionSiteId = template.ConstructionSiteId,
                ModuleId = template.ModuleId,
                WaitSeconds = MathF.Max(0f, template.WaitSeconds ?? 0f),
                Radius = MathF.Max(0f, template.Radius ?? 0f),
                MaxSystemRange = template.MaxSystemRange,
                KnownStationsOnly = template.KnownStationsOnly ?? false,
            });
        }
        directive.UpdatedAtUtc = DateTimeOffset.UtcNow;

        var assignment = GetOrCreateAssignment(player, "ship", shipId);
        assignment.DirectiveId = directive.Id;
        assignment.Status = "active";
        assignment.UpdatedAtUtc = directive.UpdatedAtUtc;

        ApplyBehavior(ship.DefaultBehavior, BuildDirectiveBehavior(ship, directive, null));
        ship.ControlSourceKind = "player-directive";
        ship.ControlSourceId = directive.Id;
        ship.ControlReason = directive.Label;
        AddDecision(player, "ship-behavior-configured", $"Configured {request.Kind} for {ship.Definition.Name}.", "ship", shipId);
        player.UpdatedAtUtc = directive.UpdatedAtUtc;
        ship.NeedsReplan = true;
        ship.LastReplanReason = "player-behavior-configured";
        ship.LastDeltaSignature = string.Empty;
        return ship;
    }

    private static void EnsureBaseStructures(SimulationWorld world, PlayerFactionRuntime player)
    {
        if (player.Policies.Count == 0)
        {
            var sovereign = world.Factions.FirstOrDefault(faction => string.Equals(faction.Id, player.SovereignFactionId, StringComparison.Ordinal));
            player.Policies.Add(new PlayerFactionPolicyRuntime
            {
                Id = "player-core-policy",
                Label = "Core Empire Policy",
                PolicySetId = sovereign?.DefaultPolicySetId,
            });

            if (sovereign?.DefaultPolicySetId is { } defaultPolicySetId
              && world.Policies.FirstOrDefault(policy => policy.Id == defaultPolicySetId) is { } defaultPolicySet)
            {
                CopyPolicySetToPlayerPolicy(defaultPolicySet, player.Policies[0]);
            }
        }

        if (player.AutomationPolicies.Count == 0)
        {
            player.AutomationPolicies.Add(new PlayerAutomationPolicyRuntime
            {
                Id = "player-core-automation",
                Label = "Core Automation",
                BehaviorKind = Idle,
            });
        }

        if (player.Reserves.Count == 0)
        {
            player.Reserves.Add(new PlayerReserveGroupRuntime
            {
                Id = "player-core-reserve",
                Label = "Strategic Reserve",
                ReserveKind = "military",
            });
            player.AssetRegistry.ReserveIds.Add("player-core-reserve");
        }
    }

    private static void SyncRegistry(SimulationWorld world, PlayerFactionRuntime player)
    {
        if (string.IsNullOrWhiteSpace(player.SovereignFactionId))
        {
            SyncSet(player.AssetRegistry.ShipIds, []);
            SyncSet(player.AssetRegistry.StationIds, []);
            SyncSet(player.AssetRegistry.CommanderIds, []);
            SyncSet(player.AssetRegistry.ClaimIds, []);
            SyncSet(player.AssetRegistry.ConstructionSiteIds, []);
            SyncSet(player.AssetRegistry.PolicySetIds, player.Policies.Where(entry => entry.PolicySetId is not null).Select(entry => entry.PolicySetId!));
            SyncSet(player.AssetRegistry.MarketOrderIds, []);
            SyncSet(player.AssetRegistry.FleetIds, player.Fleets.Select(fleet => fleet.Id));
            SyncSet(player.AssetRegistry.TaskForceIds, player.TaskForces.Select(taskForce => taskForce.Id));
            SyncSet(player.AssetRegistry.StationGroupIds, player.StationGroups.Select(group => group.Id));
            SyncSet(player.AssetRegistry.EconomicRegionIds, player.EconomicRegions.Select(region => region.Id));
            SyncSet(player.AssetRegistry.FrontIds, player.Fronts.Select(front => front.Id));
            SyncSet(player.AssetRegistry.ReserveIds, player.Reserves.Select(reserve => reserve.Id));
            return;
        }

        SyncSet(player.AssetRegistry.ShipIds, world.Ships.Where(ship => ship.FactionId == player.SovereignFactionId).Select(ship => ship.Id));
        SyncSet(player.AssetRegistry.StationIds, world.Stations.Where(station => station.FactionId == player.SovereignFactionId).Select(station => station.Id));
        SyncSet(player.AssetRegistry.CommanderIds, world.Commanders.Where(commander => commander.FactionId == player.SovereignFactionId).Select(commander => commander.Id));
        SyncSet(player.AssetRegistry.ClaimIds, world.Claims.Where(claim => claim.FactionId == player.SovereignFactionId).Select(claim => claim.Id));
        SyncSet(player.AssetRegistry.ConstructionSiteIds, world.ConstructionSites.Where(site => site.FactionId == player.SovereignFactionId).Select(site => site.Id));
        SyncSet(player.AssetRegistry.PolicySetIds, world.Policies.Where(policy => policy.OwnerId == player.SovereignFactionId || player.Policies.Any(entry => entry.PolicySetId == policy.Id)).Select(policy => policy.Id));
        SyncSet(player.AssetRegistry.MarketOrderIds, world.MarketOrders.Where(order => order.FactionId == player.SovereignFactionId).Select(order => order.Id));
        SyncSet(player.AssetRegistry.FleetIds, player.Fleets.Select(fleet => fleet.Id));
        SyncSet(player.AssetRegistry.TaskForceIds, player.TaskForces.Select(taskForce => taskForce.Id));
        SyncSet(player.AssetRegistry.StationGroupIds, player.StationGroups.Select(group => group.Id));
        SyncSet(player.AssetRegistry.EconomicRegionIds, player.EconomicRegions.Select(region => region.Id));
        SyncSet(player.AssetRegistry.FrontIds, player.Fronts.Select(front => front.Id));
        SyncSet(player.AssetRegistry.ReserveIds, player.Reserves.Select(reserve => reserve.Id));
    }

    private static void PrunePlayerState(SimulationWorld world, PlayerFactionRuntime player)
    {
        var shipIds = player.AssetRegistry.ShipIds;
        var stationIds = player.AssetRegistry.StationIds;
        var frontIds = player.AssetRegistry.FrontIds;
        var fleetIds = player.AssetRegistry.FleetIds;
        var reserveIds = player.AssetRegistry.ReserveIds;
        var taskForceIds = player.AssetRegistry.TaskForceIds;
        var stationGroupIds = player.AssetRegistry.StationGroupIds;
        var regionIds = player.AssetRegistry.EconomicRegionIds;
        var directiveIds = player.Directives.Select(directive => directive.Id).ToHashSet(StringComparer.Ordinal);
        var policyIds = player.Policies.Select(policy => policy.Id).ToHashSet(StringComparer.Ordinal);
        var automationIds = player.AutomationPolicies.Select(policy => policy.Id).ToHashSet(StringComparer.Ordinal);

        foreach (var fleet in player.Fleets)
        {
            fleet.AssetIds.RemoveAll(assetId => !shipIds.Contains(assetId));
            fleet.TaskForceIds.RemoveAll(taskForceId => !taskForceIds.Contains(taskForceId));
            fleet.DirectiveIds.RemoveAll(directiveId => !directiveIds.Contains(directiveId));
        }

        foreach (var taskForce in player.TaskForces)
        {
            taskForce.AssetIds.RemoveAll(assetId => !shipIds.Contains(assetId));
            taskForce.DirectiveIds.RemoveAll(directiveId => !directiveIds.Contains(directiveId));
            if (taskForce.FleetId is not null && !fleetIds.Contains(taskForce.FleetId))
            {
                taskForce.FleetId = null;
            }
        }

        foreach (var group in player.StationGroups)
        {
            group.StationIds.RemoveAll(stationId => !stationIds.Contains(stationId));
            group.DirectiveIds.RemoveAll(directiveId => !directiveIds.Contains(directiveId));
            if (group.EconomicRegionId is not null && !regionIds.Contains(group.EconomicRegionId))
            {
                group.EconomicRegionId = null;
            }
        }

        foreach (var region in player.EconomicRegions)
        {
            region.SystemIds.RemoveAll(systemId => !world.Systems.Any(system => string.Equals(system.Definition.Id, systemId, StringComparison.Ordinal)));
            region.StationGroupIds.RemoveAll(groupId => !stationGroupIds.Contains(groupId));
            region.DirectiveIds.RemoveAll(directiveId => !directiveIds.Contains(directiveId));
        }

        foreach (var front in player.Fronts)
        {
            front.SystemIds.RemoveAll(systemId => !world.Systems.Any(system => string.Equals(system.Definition.Id, systemId, StringComparison.Ordinal)));
            front.FleetIds.RemoveAll(fleetId => !fleetIds.Contains(fleetId));
            front.ReserveIds.RemoveAll(reserveId => !reserveIds.Contains(reserveId));
            front.DirectiveIds.RemoveAll(directiveId => !directiveIds.Contains(directiveId));
        }

        foreach (var reserve in player.Reserves)
        {
            reserve.AssetIds.RemoveAll(assetId => !shipIds.Contains(assetId));
            reserve.FrontIds.RemoveAll(frontId => !frontIds.Contains(frontId));
        }

        player.Assignments.RemoveAll(assignment =>
          (assignment.AssetKind == "ship" && !shipIds.Contains(assignment.AssetId)) ||
          (assignment.AssetKind == "station" && !stationIds.Contains(assignment.AssetId)));

        foreach (var assignment in player.Assignments)
        {
            if (assignment.FleetId is not null && !fleetIds.Contains(assignment.FleetId))
            {
                assignment.FleetId = null;
            }
            if (assignment.TaskForceId is not null && !taskForceIds.Contains(assignment.TaskForceId))
            {
                assignment.TaskForceId = null;
            }
            if (assignment.StationGroupId is not null && !stationGroupIds.Contains(assignment.StationGroupId))
            {
                assignment.StationGroupId = null;
            }
            if (assignment.EconomicRegionId is not null && !regionIds.Contains(assignment.EconomicRegionId))
            {
                assignment.EconomicRegionId = null;
            }
            if (assignment.FrontId is not null && !frontIds.Contains(assignment.FrontId))
            {
                assignment.FrontId = null;
            }
            if (assignment.ReserveId is not null && !reserveIds.Contains(assignment.ReserveId))
            {
                assignment.ReserveId = null;
            }
            if (assignment.DirectiveId is not null && !directiveIds.Contains(assignment.DirectiveId))
            {
                assignment.DirectiveId = null;
            }
            if (assignment.PolicyId is not null && !policyIds.Contains(assignment.PolicyId))
            {
                assignment.PolicyId = null;
            }
            if (assignment.AutomationPolicyId is not null && !automationIds.Contains(assignment.AutomationPolicyId))
            {
                assignment.AutomationPolicyId = null;
            }
        }
    }

    private static void ApplyPolicies(SimulationWorld world, PlayerFactionRuntime player)
    {
        foreach (var policy in player.Policies)
        {
            if (policy.PolicySetId is null)
            {
                continue;
            }

            if (world.Policies.FirstOrDefault(candidate => candidate.Id == policy.PolicySetId) is { } policySet)
            {
                policySet.TradeAccessPolicy = policy.TradeAccessPolicy;
                policySet.DockingAccessPolicy = policy.DockingAccessPolicy;
                policySet.ConstructionAccessPolicy = policy.ConstructionAccessPolicy;
                policySet.OperationalRangePolicy = policy.OperationalRangePolicy;
                policySet.CombatEngagementPolicy = policy.CombatEngagementPolicy;
                policySet.FleeHullRatio = Math.Clamp(policy.FleeHullRatio, 0.05f, 0.95f);
                policySet.AvoidHostileSystems = policy.AvoidHostileSystems;

                policySet.BlacklistedSystemIds.Clear();
                foreach (var systemId in policy.BlacklistedSystemIds)
                {
                    policySet.BlacklistedSystemIds.Add(systemId);
                }
            }
        }
    }

    private static void ApplyAssignmentsAndDirectives(SimulationWorld world, PlayerFactionRuntime player, ICollection<SimulationEventRecord> events)
    {
        var factionCommander = world.Commanders.FirstOrDefault(commander =>
          commander.Kind == CommanderKind.Faction &&
          string.Equals(commander.FactionId, player.SovereignFactionId, StringComparison.Ordinal));
        if (factionCommander is null)
        {
            return;
        }

        var fleetCommanders = EnsureFleetCommanders(world, player, factionCommander);
        var taskForceCommanders = EnsureTaskForceCommanders(world, player, factionCommander, fleetCommanders);
        var assignmentsByAsset = player.Assignments
          .Where(assignment => assignment.Status == "active")
          .GroupBy(assignment => $"{assignment.AssetKind}:{assignment.AssetId}", StringComparer.Ordinal)
          .ToDictionary(group => group.Key, group => group.OrderByDescending(item => item.UpdatedAtUtc).First(), StringComparer.Ordinal);

        foreach (var ship in world.Ships.Where(candidate => candidate.FactionId == player.SovereignFactionId))
        {
            if (ship.CommanderId is null)
            {
                continue;
            }

            var commander = world.Commanders.FirstOrDefault(candidate => candidate.Id == ship.CommanderId);
            if (commander is null)
            {
                continue;
            }

            var assignment = ResolveAssignment(assignmentsByAsset, "ship", ship.Id);
            var directive = ResolveDirective(player, assignment, "ship", ship.Id);
            var automation = ResolveAutomation(player, assignment, directive, "ship", ship.Id);
            var policy = ResolvePolicy(player, assignment, directive, "ship", ship.Id);

            commander.ParentCommanderId = ResolveParentCommanderId(factionCommander, assignment, fleetCommanders, taskForceCommanders);
            commander.PolicySetId = policy?.PolicySetId ?? factionCommander.PolicySetId;
            ship.PolicySetId = commander.PolicySetId;
            var changed = ApplyDirectiveToShip(commander, ship, directive, automation, assignment);
            if (changed && directive is not null)
            {
                events.Add(new SimulationEventRecord("ship", ship.Id, "player-directive", $"{ship.Definition.Name} aligned to player directive {directive.Label}.", DateTimeOffset.UtcNow, "player", "universe", ship.Id));
            }
        }

        foreach (var station in world.Stations.Where(candidate => candidate.FactionId == player.SovereignFactionId))
        {
            if (station.CommanderId is null)
            {
                continue;
            }

            var commander = world.Commanders.FirstOrDefault(candidate => candidate.Id == station.CommanderId);
            if (commander is null)
            {
                continue;
            }

            var assignment = ResolveAssignment(assignmentsByAsset, "station", station.Id);
            var directive = ResolveDirective(player, assignment, "station", station.Id);
            var policy = ResolvePolicy(player, assignment, directive, "station", station.Id);
            commander.PolicySetId = policy?.PolicySetId ?? factionCommander.PolicySetId;
            station.PolicySetId = commander.PolicySetId;
            commander.Assignment = directive is null && assignment is null
              ? null
              : new CommanderAssignmentRuntime
              {
                  ObjectiveId = directive?.Id ?? assignment?.StationGroupId ?? $"player-station-{station.Id}",
                  Kind = directive?.Kind ?? "player-station-control",
                  BehaviorKind = directive?.BehaviorKind ?? assignment?.Role ?? "station-control",
                  Status = directive?.Status ?? assignment?.Status ?? "active",
                  Priority = directive?.Priority ?? 40f,
                  HomeSystemId = directive?.HomeSystemId ?? station.SystemId,
                  HomeStationId = directive?.HomeStationId ?? station.Id,
                  TargetSystemId = directive?.TargetSystemId,
                  TargetEntityId = directive?.TargetEntityId,
                  TargetPosition = directive?.TargetPosition,
                  ItemId = directive?.ItemId,
                  Notes = directive?.Notes ?? assignment?.Role,
                  UpdatedAtUtc = directive?.UpdatedAtUtc ?? assignment?.UpdatedAtUtc ?? DateTimeOffset.UtcNow,
              };
        }
    }

    private static Dictionary<string, CommanderRuntime> EnsureFleetCommanders(SimulationWorld world, PlayerFactionRuntime player, CommanderRuntime factionCommander)
    {
        var map = new Dictionary<string, CommanderRuntime>(StringComparer.Ordinal);
        foreach (var fleet in player.Fleets)
        {
            var commander = world.Commanders.FirstOrDefault(candidate =>
              candidate.Kind == CommanderKind.Fleet &&
              candidate.FactionId == player.SovereignFactionId &&
              string.Equals(candidate.ControlledEntityId, fleet.Id, StringComparison.Ordinal));
            if (commander is null)
            {
                commander = new CommanderRuntime
                {
                    Id = $"commander-player-fleet-{fleet.Id}",
                    Kind = CommanderKind.Fleet,
                    FactionId = player.SovereignFactionId,
                    ControlledEntityId = fleet.Id,
                    Doctrine = "player-fleet-control",
                    Skills = new CommanderSkillProfileRuntime { Leadership = 5, Coordination = 4, Strategy = 4 },
                };
                world.Commanders.Add(commander);
            }

            commander.ParentCommanderId = factionCommander.Id;
            commander.PolicySetId = ResolvePolicySetId(world, player, fleet.PolicyId) ?? factionCommander.PolicySetId;
            commander.Assignment = new CommanderAssignmentRuntime
            {
                ObjectiveId = fleet.Id,
                Kind = "player-fleet",
                BehaviorKind = fleet.Role,
                Status = fleet.Status,
                Priority = 80f,
                HomeSystemId = fleet.HomeSystemId,
                HomeStationId = fleet.HomeStationId,
                Notes = fleet.Label,
                UpdatedAtUtc = fleet.UpdatedAtUtc,
            };
            fleet.CommanderId = commander.Id;
            map[fleet.Id] = commander;
        }
        return map;
    }

    private static Dictionary<string, CommanderRuntime> EnsureTaskForceCommanders(
      SimulationWorld world,
      PlayerFactionRuntime player,
      CommanderRuntime factionCommander,
      IReadOnlyDictionary<string, CommanderRuntime> fleetCommanders)
    {
        var map = new Dictionary<string, CommanderRuntime>(StringComparer.Ordinal);
        foreach (var taskForce in player.TaskForces)
        {
            var commander = world.Commanders.FirstOrDefault(candidate =>
              candidate.Kind == CommanderKind.TaskGroup &&
              candidate.FactionId == player.SovereignFactionId &&
              string.Equals(candidate.ControlledEntityId, taskForce.Id, StringComparison.Ordinal));
            if (commander is null)
            {
                commander = new CommanderRuntime
                {
                    Id = $"commander-player-task-force-{taskForce.Id}",
                    Kind = CommanderKind.TaskGroup,
                    FactionId = player.SovereignFactionId,
                    ControlledEntityId = taskForce.Id,
                    Doctrine = "player-task-force-control",
                    Skills = new CommanderSkillProfileRuntime { Leadership = 4, Coordination = 4, Strategy = 4 },
                };
                world.Commanders.Add(commander);
            }

            commander.ParentCommanderId = taskForce.FleetId is not null && fleetCommanders.TryGetValue(taskForce.FleetId, out var fleetCommander)
              ? fleetCommander.Id
              : factionCommander.Id;
            commander.PolicySetId = ResolvePolicySetId(world, player, taskForce.PolicyId) ?? factionCommander.PolicySetId;
            commander.Assignment = new CommanderAssignmentRuntime
            {
                ObjectiveId = taskForce.Id,
                Kind = "player-task-force",
                BehaviorKind = taskForce.Role,
                Status = taskForce.Status,
                Priority = 75f,
                Notes = taskForce.Label,
                UpdatedAtUtc = taskForce.UpdatedAtUtc,
            };
            taskForce.CommanderId = commander.Id;
            map[taskForce.Id] = commander;
        }
        return map;
    }

    private static string ResolveParentCommanderId(
      CommanderRuntime factionCommander,
      PlayerAssignmentRuntime? assignment,
      IReadOnlyDictionary<string, CommanderRuntime> fleetCommanders,
      IReadOnlyDictionary<string, CommanderRuntime> taskForceCommanders)
    {
        if (assignment?.TaskForceId is not null && taskForceCommanders.TryGetValue(assignment.TaskForceId, out var taskForceCommander))
        {
            return taskForceCommander.Id;
        }

        if (assignment?.FleetId is not null && fleetCommanders.TryGetValue(assignment.FleetId, out var fleetCommander))
        {
            return fleetCommander.Id;
        }

        return factionCommander.Id;
    }

    private static PlayerAssignmentRuntime? ResolveAssignment(
      IReadOnlyDictionary<string, PlayerAssignmentRuntime> assignmentsByAsset,
      string assetKind,
      string assetId) =>
      assignmentsByAsset.GetValueOrDefault($"{assetKind}:{assetId}");

    private static PlayerDirectiveRuntime? ResolveDirective(PlayerFactionRuntime player, PlayerAssignmentRuntime? assignment, string assetKind, string assetId)
    {
        if (assignment?.DirectiveId is not null)
        {
            return player.Directives.FirstOrDefault(directive => directive.Id == assignment.DirectiveId);
        }

        return SelectScopedDirective(
          player.Directives.Where(directive => directive.Status == "active"),
          player,
          assignment,
          assetKind,
          assetId);
    }

    private static PlayerAutomationPolicyRuntime? ResolveAutomation(PlayerFactionRuntime player, PlayerAssignmentRuntime? assignment, PlayerDirectiveRuntime? directive, string assetKind, string assetId)
    {
        var automationId = assignment?.AutomationPolicyId ?? directive?.AutomationPolicyId;
        if (automationId is not null)
        {
            return player.AutomationPolicies.FirstOrDefault(policy => policy.Id == automationId);
        }

        return SelectScopedAutomationPolicy(player, assignment, assetKind, assetId);
    }

    private static PlayerFactionPolicyRuntime? ResolvePolicy(PlayerFactionRuntime player, PlayerAssignmentRuntime? assignment, PlayerDirectiveRuntime? directive, string assetKind, string assetId)
    {
        var policyId = assignment?.PolicyId ?? directive?.PolicyId;
        if (policyId is not null)
        {
            return player.Policies.FirstOrDefault(policy => policy.Id == policyId);
        }

        return SelectScopedFactionPolicy(player, assignment, assetKind, assetId)
          ?? player.Policies.FirstOrDefault(policy => policy.Id == "player-core-policy");
    }

    private static bool ApplyDirectiveToShip(
      CommanderRuntime commander,
      ShipRuntime ship,
      PlayerDirectiveRuntime? directive,
      PlayerAutomationPolicyRuntime? automation,
      PlayerAssignmentRuntime? assignment)
    {
        var desiredAssignment = BuildDirectiveAssignment(ship, directive, automation, assignment);
        var desiredBehavior = BuildDirectiveBehavior(ship, directive, automation);
        var hasBehaviorSource = directive is not null || automation is not null;
        var desiredControlSourceKind = directive is not null
          ? "player-directive"
          : automation is not null
            ? "player-automation"
            : ship.OrderQueue.Any(order => order.SourceKind == ShipOrderSourceKind.Player)
              ? "player-order"
              : "player-manual";
        var desiredControlSourceId = directive?.Id
          ?? automation?.Id
          ?? ship.OrderQueue
            .Where(order => order.SourceKind == ShipOrderSourceKind.Player)
            .OrderByDescending(order => order.Priority)
            .ThenBy(order => order.CreatedAtUtc)
            .Select(order => order.Id)
            .FirstOrDefault();
        var desiredControlReason = directive?.Label
          ?? automation?.Label
          ?? ship.OrderQueue
            .Where(order => order.SourceKind == ShipOrderSourceKind.Player)
            .OrderByDescending(order => order.Priority)
            .ThenBy(order => order.CreatedAtUtc)
            .Select(order => order.Label ?? order.Kind)
            .FirstOrDefault()
          ?? (hasBehaviorSource ? "delegated-player-control" : "manual-player-control");

        var assignmentChanged = !AssignmentsEqual(commander.Assignment, desiredAssignment);
        var behaviorChanged = hasBehaviorSource && !DefaultBehaviorsEqual(ship.DefaultBehavior, desiredBehavior!);
        var ordersChanged = ReconcileDirectiveOrders(ship, directive, automation);
        var controlChanged =
          !string.Equals(ship.ControlSourceKind, desiredControlSourceKind, StringComparison.Ordinal)
          || !string.Equals(ship.ControlSourceId, desiredControlSourceId, StringComparison.Ordinal)
          || !string.Equals(ship.ControlReason, desiredControlReason, StringComparison.Ordinal);

        if (assignmentChanged)
        {
            commander.Assignment = desiredAssignment;
        }

        if (behaviorChanged && desiredBehavior is not null)
        {
            ApplyBehavior(ship.DefaultBehavior, desiredBehavior);
        }

        if (directive is null && automation is null)
        {
            ship.ControlSourceKind = desiredControlSourceKind;
            ship.ControlSourceId = desiredControlSourceId;
            ship.ControlReason = desiredControlReason;
            var surfaceChanged = assignmentChanged || ordersChanged || controlChanged;
            if (surfaceChanged)
            {
                ship.LastDeltaSignature = string.Empty;
            }

            if (assignmentChanged || ordersChanged)
            {
                ship.NeedsReplan = true;
                ship.LastReplanReason = assignmentChanged
                  ? "player-assignment-updated"
                  : ordersChanged
                    ? "player-order-updated"
                    : "player-control-updated";
            }

            return surfaceChanged;
        }

        ship.ControlSourceKind = desiredControlSourceKind;
        ship.ControlSourceId = desiredControlSourceId;
        ship.ControlReason = desiredControlReason;
        var changed = assignmentChanged || behaviorChanged || ordersChanged || controlChanged;
        if (changed)
        {
            ship.LastDeltaSignature = string.Empty;
        }

        if (assignmentChanged || behaviorChanged || ordersChanged)
        {
            ship.NeedsReplan = true;
            ship.LastReplanReason = assignmentChanged
              ? "player-assignment-updated"
              : behaviorChanged
                ? "player-behavior-updated"
                : ordersChanged
                  ? "player-order-updated"
                  : "player-control-updated";
        }

        return changed;
    }

    private static DefaultBehaviorRuntime BuildDirectiveBehavior(ShipRuntime ship, PlayerDirectiveRuntime? directive, PlayerAutomationPolicyRuntime? automation)
    {
        return new DefaultBehaviorRuntime
        {
            Kind = directive?.BehaviorKind ?? automation?.BehaviorKind ?? ship.DefaultBehavior.Kind,
            HomeSystemId = directive?.HomeSystemId ?? ship.DefaultBehavior.HomeSystemId ?? ship.SystemId,
            HomeStationId = directive?.HomeStationId ?? ship.DefaultBehavior.HomeStationId,
            AreaSystemId = directive?.TargetSystemId ?? directive?.HomeSystemId ?? ship.DefaultBehavior.AreaSystemId ?? ship.SystemId,
            TargetEntityId = directive?.TargetEntityId,
            ItemId = directive?.ItemId ?? automation?.PreferredItemId ?? ship.DefaultBehavior.ItemId,
            PreferredNodeId = directive?.PreferredNodeId ?? ship.DefaultBehavior.PreferredNodeId,
            PreferredConstructionSiteId = directive?.PreferredConstructionSiteId ?? ship.DefaultBehavior.PreferredConstructionSiteId,
            PreferredModuleId = directive?.PreferredModuleId ?? ship.DefaultBehavior.PreferredModuleId,
            TargetPosition = directive?.TargetPosition,
            WaitSeconds = directive?.WaitSeconds ?? automation?.WaitSeconds ?? ship.DefaultBehavior.WaitSeconds,
            Radius = directive?.Radius ?? automation?.Radius ?? ship.DefaultBehavior.Radius,
            MaxSystemRange = directive?.MaxSystemRange ?? automation?.MaxSystemRange ?? ship.DefaultBehavior.MaxSystemRange,
            KnownStationsOnly = directive?.KnownStationsOnly ?? automation?.KnownStationsOnly ?? ship.DefaultBehavior.KnownStationsOnly,
            PatrolPoints = directive?.PatrolPoints.Select(point => point).ToList() ?? ship.DefaultBehavior.PatrolPoints.Select(point => point).ToList(),
            PatrolIndex = ship.DefaultBehavior.PatrolIndex,
            RepeatOrders = directive?.RepeatOrders.Select(CloneTemplate).ToList()
            ?? automation?.RepeatOrders.Select(CloneTemplate).ToList()
            ?? ship.DefaultBehavior.RepeatOrders.Select(CloneTemplate).ToList(),
            RepeatIndex = ship.DefaultBehavior.RepeatIndex,
        };
    }

    private static bool ReconcileDirectiveOrders(ShipRuntime ship, PlayerDirectiveRuntime? directive, PlayerAutomationPolicyRuntime? automation)
    {
        var aiOrderId = directive is null ? null : $"player-order-{directive.Id}";
        var changed = ship.OrderQueue.RemoveAll(order => order.Id.StartsWith("player-order-", StringComparison.Ordinal) && order.Id != aiOrderId) > 0;

        var useOrders = directive?.UseOrders ?? automation?.UseOrders ?? false;
        if (!useOrders || directive is null || string.IsNullOrWhiteSpace(directive.StagingOrderKind))
        {
            return changed;
        }

        var desiredOrder = new ShipOrderRuntime
        {
            Id = aiOrderId!,
            Kind = directive.StagingOrderKind!,
            SourceKind = ShipOrderSourceKind.Player,
            SourceId = directive.Id,
            Priority = Math.Max(0, directive.Priority),
            InterruptCurrentPlan = true,
            Label = directive.Label,
            TargetEntityId = directive.TargetEntityId,
            TargetSystemId = directive.TargetSystemId,
            TargetPosition = directive.TargetPosition,
            SourceStationId = directive.SourceStationId ?? directive.HomeStationId,
            DestinationStationId = directive.DestinationStationId,
            ItemId = directive.ItemId,
            NodeId = directive.PreferredNodeId,
            ConstructionSiteId = directive.PreferredConstructionSiteId,
            ModuleId = directive.PreferredModuleId,
            WaitSeconds = directive.WaitSeconds,
            Radius = directive.Radius,
            MaxSystemRange = directive.MaxSystemRange,
            KnownStationsOnly = directive.KnownStationsOnly,
        };

        var existing = ship.OrderQueue.FirstOrDefault(order => order.Id == aiOrderId);
        if (existing is null)
        {
            ship.OrderQueue.Add(desiredOrder);
            return true;
        }

        if (!ShipOrdersEqual(existing, desiredOrder))
        {
            ship.OrderQueue.Remove(existing);
            ship.OrderQueue.Add(desiredOrder);
            return true;
        }

        return changed;
    }

    private static CommanderAssignmentRuntime? BuildDirectiveAssignment(
      ShipRuntime ship,
      PlayerDirectiveRuntime? directive,
      PlayerAutomationPolicyRuntime? automation,
      PlayerAssignmentRuntime? assignment)
    {
        if (directive is null && automation is null)
        {
            return null;
        }

        var behavior = directive?.BehaviorKind ?? automation?.BehaviorKind ?? ship.DefaultBehavior.Kind;
        return new CommanderAssignmentRuntime
        {
            ObjectiveId = directive?.Id ?? assignment?.DirectiveId ?? $"automation-{ship.Id}",
            Kind = directive?.Kind ?? "player-automation",
            BehaviorKind = behavior,
            Status = directive?.Status ?? "active",
            Priority = directive?.Priority ?? 50f,
            HomeSystemId = directive?.HomeSystemId ?? ship.DefaultBehavior.HomeSystemId,
            HomeStationId = directive?.HomeStationId ?? ship.DefaultBehavior.HomeStationId,
            TargetSystemId = directive?.TargetSystemId,
            TargetEntityId = directive?.TargetEntityId,
            TargetPosition = directive?.TargetPosition,
            ItemId = directive?.ItemId,
            Notes = directive?.Notes ?? automation?.Notes,
            UpdatedAtUtc = directive?.UpdatedAtUtc ?? automation?.UpdatedAtUtc ?? DateTimeOffset.UtcNow,
        };
    }

    private static void ApplyBehavior(DefaultBehaviorRuntime target, DefaultBehaviorRuntime source)
    {
        target.Kind = source.Kind;
        target.HomeSystemId = source.HomeSystemId;
        target.HomeStationId = source.HomeStationId;
        target.AreaSystemId = source.AreaSystemId;
        target.TargetEntityId = source.TargetEntityId;
        target.ItemId = source.ItemId;
        target.PreferredNodeId = source.PreferredNodeId;
        target.PreferredConstructionSiteId = source.PreferredConstructionSiteId;
        target.PreferredModuleId = source.PreferredModuleId;
        target.TargetPosition = source.TargetPosition;
        target.WaitSeconds = source.WaitSeconds;
        target.Radius = source.Radius;
        target.MaxSystemRange = source.MaxSystemRange;
        target.KnownStationsOnly = source.KnownStationsOnly;
        target.PatrolPoints = source.PatrolPoints.Select(point => point).ToList();
        target.PatrolIndex = source.PatrolIndex;
        target.RepeatOrders = source.RepeatOrders.Select(CloneTemplate).ToList();
        target.RepeatIndex = source.RepeatIndex;
    }

    private static bool DefaultBehaviorsEqual(DefaultBehaviorRuntime left, DefaultBehaviorRuntime right) =>
      string.Equals(left.Kind, right.Kind, StringComparison.Ordinal)
      && string.Equals(left.HomeSystemId, right.HomeSystemId, StringComparison.Ordinal)
      && string.Equals(left.HomeStationId, right.HomeStationId, StringComparison.Ordinal)
      && string.Equals(left.AreaSystemId, right.AreaSystemId, StringComparison.Ordinal)
      && string.Equals(left.TargetEntityId, right.TargetEntityId, StringComparison.Ordinal)
      && string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal)
      && string.Equals(left.PreferredNodeId, right.PreferredNodeId, StringComparison.Ordinal)
      && string.Equals(left.PreferredConstructionSiteId, right.PreferredConstructionSiteId, StringComparison.Ordinal)
      && string.Equals(left.PreferredModuleId, right.PreferredModuleId, StringComparison.Ordinal)
      && Nullable.Equals(left.TargetPosition, right.TargetPosition)
      && left.WaitSeconds.Equals(right.WaitSeconds)
      && left.Radius.Equals(right.Radius)
      && left.MaxSystemRange == right.MaxSystemRange
      && left.KnownStationsOnly == right.KnownStationsOnly
      && left.PatrolPoints.SequenceEqual(right.PatrolPoints)
      && left.RepeatOrders.Count == right.RepeatOrders.Count
      && left.RepeatOrders.Zip(right.RepeatOrders, ShipOrderTemplatesEqual).All(equal => equal);

    private static bool ShipOrderTemplatesEqual(ShipOrderTemplateRuntime left, ShipOrderTemplateRuntime right) =>
      string.Equals(left.Kind, right.Kind, StringComparison.Ordinal)
      && string.Equals(left.Label, right.Label, StringComparison.Ordinal)
      && string.Equals(left.TargetEntityId, right.TargetEntityId, StringComparison.Ordinal)
      && string.Equals(left.TargetSystemId, right.TargetSystemId, StringComparison.Ordinal)
      && Nullable.Equals(left.TargetPosition, right.TargetPosition)
      && string.Equals(left.SourceStationId, right.SourceStationId, StringComparison.Ordinal)
      && string.Equals(left.DestinationStationId, right.DestinationStationId, StringComparison.Ordinal)
      && string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal)
      && string.Equals(left.NodeId, right.NodeId, StringComparison.Ordinal)
      && string.Equals(left.ConstructionSiteId, right.ConstructionSiteId, StringComparison.Ordinal)
      && string.Equals(left.ModuleId, right.ModuleId, StringComparison.Ordinal)
      && left.WaitSeconds.Equals(right.WaitSeconds)
      && left.Radius.Equals(right.Radius)
      && left.MaxSystemRange == right.MaxSystemRange
      && left.KnownStationsOnly == right.KnownStationsOnly;

    private static bool ShipOrdersEqual(ShipOrderRuntime left, ShipOrderRuntime right) =>
      string.Equals(left.Id, right.Id, StringComparison.Ordinal)
      && string.Equals(left.Kind, right.Kind, StringComparison.Ordinal)
      && left.SourceKind == right.SourceKind
      && string.Equals(left.SourceId, right.SourceId, StringComparison.Ordinal)
      && left.Priority == right.Priority
      && left.InterruptCurrentPlan == right.InterruptCurrentPlan
      && string.Equals(left.Label, right.Label, StringComparison.Ordinal)
      && string.Equals(left.TargetEntityId, right.TargetEntityId, StringComparison.Ordinal)
      && string.Equals(left.TargetSystemId, right.TargetSystemId, StringComparison.Ordinal)
      && Nullable.Equals(left.TargetPosition, right.TargetPosition)
      && string.Equals(left.SourceStationId, right.SourceStationId, StringComparison.Ordinal)
      && string.Equals(left.DestinationStationId, right.DestinationStationId, StringComparison.Ordinal)
      && string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal)
      && string.Equals(left.NodeId, right.NodeId, StringComparison.Ordinal)
      && string.Equals(left.ConstructionSiteId, right.ConstructionSiteId, StringComparison.Ordinal)
      && string.Equals(left.ModuleId, right.ModuleId, StringComparison.Ordinal)
      && left.WaitSeconds.Equals(right.WaitSeconds)
      && left.Radius.Equals(right.Radius)
      && left.MaxSystemRange == right.MaxSystemRange
      && left.KnownStationsOnly == right.KnownStationsOnly;

    private static bool AssignmentsEqual(CommanderAssignmentRuntime? left, CommanderAssignmentRuntime? right)
    {
        if (ReferenceEquals(left, right))
        {
            return true;
        }

        if (left is null || right is null)
        {
            return false;
        }

        return string.Equals(left.ObjectiveId, right.ObjectiveId, StringComparison.Ordinal)
          && string.Equals(left.CampaignId, right.CampaignId, StringComparison.Ordinal)
          && string.Equals(left.TheaterId, right.TheaterId, StringComparison.Ordinal)
          && string.Equals(left.Kind, right.Kind, StringComparison.Ordinal)
          && string.Equals(left.BehaviorKind, right.BehaviorKind, StringComparison.Ordinal)
          && string.Equals(left.Status, right.Status, StringComparison.Ordinal)
          && left.Priority.Equals(right.Priority)
          && string.Equals(left.HomeSystemId, right.HomeSystemId, StringComparison.Ordinal)
          && string.Equals(left.HomeStationId, right.HomeStationId, StringComparison.Ordinal)
          && string.Equals(left.TargetSystemId, right.TargetSystemId, StringComparison.Ordinal)
          && string.Equals(left.TargetEntityId, right.TargetEntityId, StringComparison.Ordinal)
          && Nullable.Equals(left.TargetPosition, right.TargetPosition)
          && string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal);
    }

    private static ShipOrderTemplateRuntime CloneTemplate(ShipOrderTemplateRuntime template) => new()
    {
        Kind = template.Kind,
        Label = template.Label,
        TargetEntityId = template.TargetEntityId,
        TargetSystemId = template.TargetSystemId,
        TargetPosition = template.TargetPosition,
        SourceStationId = template.SourceStationId,
        DestinationStationId = template.DestinationStationId,
        ItemId = template.ItemId,
        NodeId = template.NodeId,
        ConstructionSiteId = template.ConstructionSiteId,
        ModuleId = template.ModuleId,
        WaitSeconds = template.WaitSeconds,
        Radius = template.Radius,
        MaxSystemRange = template.MaxSystemRange,
        KnownStationsOnly = template.KnownStationsOnly,
    };

    private static void ReconcileOrganizationAssignments(SimulationWorld world, PlayerFactionRuntime player)
    {
        var fleetMemberships = new Dictionary<string, List<string>>(StringComparer.Ordinal);
        var taskForceMemberships = new Dictionary<string, List<string>>(StringComparer.Ordinal);
        var stationGroupMemberships = new Dictionary<string, List<string>>(StringComparer.Ordinal);
        var reserveMemberships = new Dictionary<string, List<string>>(StringComparer.Ordinal);

        foreach (var fleet in player.Fleets)
        {
            foreach (var assetId in fleet.AssetIds.Where(player.AssetRegistry.ShipIds.Contains))
            {
                AddMembership(fleetMemberships, assetId, fleet.Id);
                GetOrCreateAssignment(player, "ship", assetId);
            }
        }

        foreach (var taskForce in player.TaskForces)
        {
            foreach (var assetId in taskForce.AssetIds.Where(player.AssetRegistry.ShipIds.Contains))
            {
                AddMembership(taskForceMemberships, assetId, taskForce.Id);
                GetOrCreateAssignment(player, "ship", assetId);
            }
        }

        foreach (var group in player.StationGroups)
        {
            foreach (var stationId in group.StationIds.Where(player.AssetRegistry.StationIds.Contains))
            {
                AddMembership(stationGroupMemberships, stationId, group.Id);
                GetOrCreateAssignment(player, "station", stationId);
            }
        }

        foreach (var reserve in player.Reserves)
        {
            foreach (var assetId in reserve.AssetIds.Where(player.AssetRegistry.ShipIds.Contains))
            {
                AddMembership(reserveMemberships, assetId, reserve.Id);
                GetOrCreateAssignment(player, "ship", assetId);
            }
        }

        foreach (var assignment in player.Assignments)
        {
            if (assignment.AssetKind == "ship")
            {
                assignment.FleetId = SelectSingleMembership(fleetMemberships, assignment.AssetId);
                assignment.TaskForceId = SelectSingleMembership(taskForceMemberships, assignment.AssetId);
                assignment.ReserveId = SelectSingleMembership(reserveMemberships, assignment.AssetId);

                if (assignment.TaskForceId is not null
                  && player.TaskForces.FirstOrDefault(taskForce => taskForce.Id == assignment.TaskForceId) is { FleetId: not null } taskForce)
                {
                    assignment.FleetId ??= taskForce.FleetId;
                }

                if (assignment.FleetId is not null)
                {
                    assignment.FrontId = player.Fronts
                      .Where(front => front.FleetIds.Contains(assignment.FleetId, StringComparer.Ordinal))
                      .OrderByDescending(front => front.Priority)
                      .ThenBy(front => front.Id, StringComparer.Ordinal)
                      .Select(front => front.Id)
                      .FirstOrDefault()
                      ?? assignment.FrontId;
                }
                else if (assignment.ReserveId is not null)
                {
                    assignment.FrontId = player.Fronts
                      .Where(front => front.ReserveIds.Contains(assignment.ReserveId, StringComparer.Ordinal))
                      .OrderByDescending(front => front.Priority)
                      .ThenBy(front => front.Id, StringComparer.Ordinal)
                      .Select(front => front.Id)
                      .FirstOrDefault()
                      ?? player.Reserves.FirstOrDefault(reserve => reserve.Id == assignment.ReserveId)?.FrontIds
                        .OrderBy(id => id, StringComparer.Ordinal)
                        .FirstOrDefault()
                      ?? assignment.FrontId;
                }
            }
            else if (assignment.AssetKind == "station")
            {
                assignment.StationGroupId = SelectSingleMembership(stationGroupMemberships, assignment.AssetId);
                if (assignment.StationGroupId is not null
                  && player.StationGroups.FirstOrDefault(group => group.Id == assignment.StationGroupId) is { EconomicRegionId: not null } stationGroup)
                {
                    assignment.EconomicRegionId = stationGroup.EconomicRegionId;
                }
            }
        }

        foreach (var assignment in player.Assignments)
        {
            assignment.UpdatedAtUtc = DateTimeOffset.UtcNow;
        }
    }

    private static void ReconcileDirectiveScopes(PlayerFactionRuntime player)
    {
        foreach (var fleet in player.Fleets)
        {
            fleet.DirectiveIds.Clear();
        }
        foreach (var taskForce in player.TaskForces)
        {
            taskForce.DirectiveIds.Clear();
        }
        foreach (var group in player.StationGroups)
        {
            group.DirectiveIds.Clear();
        }
        foreach (var region in player.EconomicRegions)
        {
            region.DirectiveIds.Clear();
        }
        foreach (var front in player.Fronts)
        {
            front.DirectiveIds.Clear();
        }

        foreach (var directive in player.Directives.Where(directive => directive.Status == "active"))
        {
            switch (directive.ScopeKind)
            {
                case "fleet":
                    player.Fleets.FirstOrDefault(entity => entity.Id == directive.ScopeId)?.DirectiveIds.Add(directive.Id);
                    break;
                case "task-force":
                    player.TaskForces.FirstOrDefault(entity => entity.Id == directive.ScopeId)?.DirectiveIds.Add(directive.Id);
                    break;
                case "station-group":
                    player.StationGroups.FirstOrDefault(entity => entity.Id == directive.ScopeId)?.DirectiveIds.Add(directive.Id);
                    break;
                case "economic-region":
                    player.EconomicRegions.FirstOrDefault(entity => entity.Id == directive.ScopeId)?.DirectiveIds.Add(directive.Id);
                    break;
                case "front":
                    player.Fronts.FirstOrDefault(entity => entity.Id == directive.ScopeId)?.DirectiveIds.Add(directive.Id);
                    break;
            }
        }
    }

    private static void RefreshProductionPrograms(SimulationWorld world, PlayerFactionRuntime player)
    {
        foreach (var program in player.ProductionPrograms)
        {
            if (!string.IsNullOrWhiteSpace(program.TargetShipKind))
            {
                program.CurrentCount = world.Ships.Count(ship =>
                  ship.FactionId == player.SovereignFactionId &&
                  string.Equals(GetShipCategory(ship.Definition), program.TargetShipKind, StringComparison.Ordinal));
            }
            else
            {
                program.CurrentCount = 0;
            }
        }
    }

    private static void ApplyStrategicIntegration(SimulationWorld world, PlayerFactionRuntime player)
    {
        var faction = world.Factions.FirstOrDefault(candidate => candidate.Id == player.SovereignFactionId);
        if (faction is null)
        {
            return;
        }

        var corePolicy = player.Policies.FirstOrDefault(policy => policy.Id == "player-core-policy");
        faction.Doctrine.StrategicPosture = player.StrategicIntent.StrategicPosture;
        faction.Doctrine.EconomicPosture = player.StrategicIntent.EconomicPosture;
        faction.Doctrine.MilitaryPosture = player.StrategicIntent.MilitaryPosture;
        faction.Doctrine.ReserveCreditsRatio = corePolicy?.ReserveCreditsRatio ?? faction.Doctrine.ReserveCreditsRatio;
        faction.Doctrine.ReserveMilitaryRatio = corePolicy?.ReserveMilitaryRatio ?? faction.Doctrine.ReserveMilitaryRatio;
    }

    private static void RefreshGeopoliticalOrganizationContext(SimulationWorld world, PlayerFactionRuntime player)
    {
        var regions = world.Geopolitics?.EconomyRegions.Regions
          .Where(region => string.Equals(region.FactionId, player.SovereignFactionId, StringComparison.Ordinal))
          .ToList() ?? [];
        var fronts = world.Geopolitics?.Territory.FrontLines
          .Where(front => front.FactionIds.Contains(player.SovereignFactionId, StringComparer.Ordinal))
          .ToList() ?? [];

        foreach (var region in player.EconomicRegions)
        {
            if (region.SystemIds.Count == 0)
            {
                region.SystemIds.AddRange(
                  region.StationGroupIds
                    .SelectMany(groupId => player.StationGroups.FirstOrDefault(group => group.Id == groupId)?.StationIds ?? [])
                    .Select(stationId => world.Stations.FirstOrDefault(station => station.Id == stationId)?.SystemId)
                    .Where(systemId => !string.IsNullOrWhiteSpace(systemId))
                    .Cast<string>()
                    .Distinct(StringComparer.Ordinal)
                    .OrderBy(systemId => systemId, StringComparer.Ordinal));
            }

            var matchedRegion = regions
              .Select(candidate => new
              {
                  Region = candidate,
                  Overlap = candidate.SystemIds.Intersect(region.SystemIds, StringComparer.Ordinal).Count(),
              })
              .OrderByDescending(entry => entry.Overlap)
              .ThenBy(entry => entry.Region.Id, StringComparer.Ordinal)
              .Select(entry => entry.Region)
              .FirstOrDefault();
            region.SharedEconomicRegionId = matchedRegion?.Id;
            if (matchedRegion is null)
            {
                continue;
            }

            if (region.SystemIds.Count == 0)
            {
                region.SystemIds.AddRange(matchedRegion.SystemIds.OrderBy(systemId => systemId, StringComparer.Ordinal));
            }

            if (string.Equals(region.Role, "balanced-region", StringComparison.Ordinal))
            {
                region.Role = matchedRegion.Kind;
            }
        }

        foreach (var front in player.Fronts)
        {
            if (front.SystemIds.Count == 0)
            {
                var fleetSystems = front.FleetIds
                  .SelectMany(fleetId => player.Fleets.FirstOrDefault(fleet => fleet.Id == fleetId)?.AssetIds ?? [])
                  .Select(assetId => world.Ships.FirstOrDefault(ship => ship.Id == assetId)?.SystemId)
                  .Where(systemId => !string.IsNullOrWhiteSpace(systemId))
                  .Cast<string>()
                  .Distinct(StringComparer.Ordinal)
                  .OrderBy(systemId => systemId, StringComparer.Ordinal)
                  .ToList();
                front.SystemIds.AddRange(fleetSystems);
            }

            var matchedFront = fronts
              .Select(candidate => new
              {
                  Front = candidate,
                  Overlap = candidate.SystemIds.Intersect(front.SystemIds, StringComparer.Ordinal).Count(),
                  TargetBias = front.TargetFactionId is not null && candidate.FactionIds.Contains(front.TargetFactionId, StringComparer.Ordinal) ? 1 : 0,
              })
              .OrderByDescending(entry => entry.Overlap + entry.TargetBias)
              .ThenBy(entry => entry.Front.Id, StringComparer.Ordinal)
              .Select(entry => entry.Front)
              .FirstOrDefault();
            front.SharedFrontLineId = matchedFront?.Id;
            if (matchedFront is null)
            {
                continue;
            }

            if (front.SystemIds.Count == 0)
            {
                front.SystemIds.AddRange(matchedFront.SystemIds.OrderBy(systemId => systemId, StringComparer.Ordinal));
            }

            front.TargetFactionId ??= matchedFront.FactionIds.FirstOrDefault(id => !string.Equals(id, player.SovereignFactionId, StringComparison.Ordinal));
        }
    }

    private static PlayerDirectiveRuntime? SelectScopedDirective(
      IEnumerable<PlayerDirectiveRuntime> directives,
      PlayerFactionRuntime player,
      PlayerAssignmentRuntime? assignment,
      string assetKind,
      string assetId) =>
      directives
        .Where(directive => ScopeMatches(player, directive.ScopeKind, directive.ScopeId, assignment, assetKind, assetId))
        .OrderByDescending(directive => ScopePriority(directive.ScopeKind))
        .ThenByDescending(directive => directive.Priority)
        .ThenByDescending(directive => directive.UpdatedAtUtc)
        .ThenBy(directive => directive.Id, StringComparer.Ordinal)
        .FirstOrDefault();

    private static PlayerAutomationPolicyRuntime? SelectScopedAutomationPolicy(
      PlayerFactionRuntime player,
      PlayerAssignmentRuntime? assignment,
      string assetKind,
      string assetId) =>
      player.AutomationPolicies
        .Where(policy => policy.Enabled && ScopeMatches(player, policy.ScopeKind, policy.ScopeId, assignment, assetKind, assetId))
        .OrderByDescending(policy => ScopePriority(policy.ScopeKind))
        .ThenByDescending(policy => policy.UpdatedAtUtc)
        .ThenBy(policy => policy.Id, StringComparer.Ordinal)
        .FirstOrDefault();

    private static PlayerFactionPolicyRuntime? SelectScopedFactionPolicy(
      PlayerFactionRuntime player,
      PlayerAssignmentRuntime? assignment,
      string assetKind,
      string assetId) =>
      player.Policies
        .Where(policy => ScopeMatches(player, policy.ScopeKind, policy.ScopeId, assignment, assetKind, assetId))
        .OrderByDescending(policy => ScopePriority(policy.ScopeKind))
        .ThenByDescending(policy => policy.UpdatedAtUtc)
        .ThenBy(policy => policy.Id, StringComparer.Ordinal)
        .FirstOrDefault();

    private static bool ScopeMatches(
      PlayerFactionRuntime player,
      string scopeKind,
      string? scopeId,
      PlayerAssignmentRuntime? assignment,
      string assetKind,
      string assetId)
    {
        return scopeKind switch
        {
            "player-faction" => string.IsNullOrWhiteSpace(scopeId)
              || string.Equals(scopeId, player.Id, StringComparison.Ordinal)
              || string.Equals(scopeId, player.SovereignFactionId, StringComparison.Ordinal),
            "asset" => string.Equals(scopeId, assetId, StringComparison.Ordinal),
            "ship" => assetKind == "ship" && string.Equals(scopeId, assetId, StringComparison.Ordinal),
            "station" => assetKind == "station" && string.Equals(scopeId, assetId, StringComparison.Ordinal),
            "fleet" => string.Equals(scopeId, assignment?.FleetId, StringComparison.Ordinal),
            "task-force" => string.Equals(scopeId, assignment?.TaskForceId, StringComparison.Ordinal),
            "station-group" => string.Equals(scopeId, assignment?.StationGroupId, StringComparison.Ordinal),
            "economic-region" => string.Equals(scopeId, assignment?.EconomicRegionId, StringComparison.Ordinal),
            "front" => string.Equals(scopeId, assignment?.FrontId, StringComparison.Ordinal),
            "reserve" => string.Equals(scopeId, assignment?.ReserveId, StringComparison.Ordinal),
            _ => false,
        };
    }

    private static int ScopePriority(string scopeKind) => scopeKind switch
    {
        "ship" or "station" or "asset" => 100,
        "task-force" => 90,
        "fleet" or "station-group" or "reserve" => 80,
        "economic-region" or "front" => 70,
        "player-faction" => 10,
        _ => 0,
    };

    private static PlayerAssignmentRuntime GetOrCreateAssignment(PlayerFactionRuntime player, string assetKind, string assetId)
    {
        var assignment = player.Assignments.FirstOrDefault(candidate =>
          string.Equals(candidate.AssetKind, assetKind, StringComparison.Ordinal) &&
          string.Equals(candidate.AssetId, assetId, StringComparison.Ordinal));
        if (assignment is not null)
        {
            return assignment;
        }

        assignment = new PlayerAssignmentRuntime
        {
            Id = $"assignment-{assetKind}-{assetId}",
            AssetKind = assetKind,
            AssetId = assetId,
        };
        player.Assignments.Add(assignment);
        return assignment;
    }

    private static void AddMembership(Dictionary<string, List<string>> memberships, string assetId, string organizationId)
    {
        if (!memberships.TryGetValue(assetId, out var values))
        {
            values = [];
            memberships[assetId] = values;
        }

        if (!values.Contains(organizationId, StringComparer.Ordinal))
        {
            values.Add(organizationId);
        }
    }

    private static string? SelectSingleMembership(Dictionary<string, List<string>> memberships, string assetId) =>
      memberships.TryGetValue(assetId, out var values)
        ? values.OrderBy(value => value, StringComparer.Ordinal).FirstOrDefault()
        : null;

    private static void RemoveAssetFromOrganizations(PlayerFactionRuntime player, string assetKind, string assetId)
    {
        foreach (var fleet in player.Fleets)
        {
            fleet.AssetIds.RemoveAll(id => string.Equals(id, assetId, StringComparison.Ordinal));
        }
        foreach (var taskForce in player.TaskForces)
        {
            taskForce.AssetIds.RemoveAll(id => string.Equals(id, assetId, StringComparison.Ordinal));
        }
        foreach (var reserve in player.Reserves)
        {
            reserve.AssetIds.RemoveAll(id => string.Equals(id, assetId, StringComparison.Ordinal));
        }
        if (assetKind == "station")
        {
            foreach (var group in player.StationGroups)
            {
                group.StationIds.RemoveAll(id => string.Equals(id, assetId, StringComparison.Ordinal));
            }
        }
    }

    private static void AddAssetToFleet(PlayerFactionRuntime player, string fleetId, string assetId)
    {
        var fleet = player.Fleets.FirstOrDefault(entity => entity.Id == fleetId)
          ?? throw new InvalidOperationException($"Unknown fleet '{fleetId}'.");
        if (!fleet.AssetIds.Contains(assetId, StringComparer.Ordinal))
        {
            fleet.AssetIds.Add(assetId);
        }
    }

    private static void AddAssetToTaskForce(PlayerFactionRuntime player, string taskForceId, string assetId)
    {
        var taskForce = player.TaskForces.FirstOrDefault(entity => entity.Id == taskForceId)
          ?? throw new InvalidOperationException($"Unknown task force '{taskForceId}'.");
        if (!taskForce.AssetIds.Contains(assetId, StringComparer.Ordinal))
        {
            taskForce.AssetIds.Add(assetId);
        }
    }

    private static void AddAssetToStationGroup(PlayerFactionRuntime player, string groupId, string assetId)
    {
        var group = player.StationGroups.FirstOrDefault(entity => entity.Id == groupId)
          ?? throw new InvalidOperationException($"Unknown station group '{groupId}'.");
        if (!group.StationIds.Contains(assetId, StringComparer.Ordinal))
        {
            group.StationIds.Add(assetId);
        }
    }

    private static void AddAssetToReserve(PlayerFactionRuntime player, string reserveId, string assetId)
    {
        var reserve = player.Reserves.FirstOrDefault(entity => entity.Id == reserveId)
          ?? throw new InvalidOperationException($"Unknown reserve '{reserveId}'.");
        if (!reserve.AssetIds.Contains(assetId, StringComparer.Ordinal))
        {
            reserve.AssetIds.Add(assetId);
        }
    }

    private static void RefreshAlerts(SimulationWorld world, PlayerFactionRuntime player)
    {
        player.Alerts.Clear();

        foreach (var shipId in player.AssetRegistry.ShipIds
          .Where(shipId => player.Fleets.Count(fleet => fleet.AssetIds.Contains(shipId, StringComparer.Ordinal)) > 1)
          .OrderBy(id => id, StringComparer.Ordinal)
          .Take(4))
        {
            player.Alerts.Add(new PlayerAlertRuntime
            {
                Id = $"alert-conflict-fleet-{shipId}",
                Kind = "conflicting-fleet-membership",
                Severity = "warning",
                Summary = $"Ship {shipId} belongs to multiple fleets.",
                AssetKind = "ship",
                AssetId = shipId,
            });
        }

        foreach (var shipId in player.AssetRegistry.ShipIds
          .Where(shipId => player.TaskForces.Count(taskForce => taskForce.AssetIds.Contains(shipId, StringComparer.Ordinal)) > 1)
          .OrderBy(id => id, StringComparer.Ordinal)
          .Take(4))
        {
            player.Alerts.Add(new PlayerAlertRuntime
            {
                Id = $"alert-conflict-task-force-{shipId}",
                Kind = "conflicting-task-force-membership",
                Severity = "warning",
                Summary = $"Ship {shipId} belongs to multiple task forces.",
                AssetKind = "ship",
                AssetId = shipId,
            });
        }

        foreach (var stationId in player.AssetRegistry.StationIds
          .Where(stationId => player.StationGroups.Count(group => group.StationIds.Contains(stationId, StringComparer.Ordinal)) > 1)
          .OrderBy(id => id, StringComparer.Ordinal)
          .Take(4))
        {
            player.Alerts.Add(new PlayerAlertRuntime
            {
                Id = $"alert-conflict-station-group-{stationId}",
                Kind = "conflicting-station-group-membership",
                Severity = "warning",
                Summary = $"Station {stationId} belongs to multiple station groups.",
                AssetKind = "station",
                AssetId = stationId,
            });
        }

        foreach (var shipId in player.AssetRegistry.ShipIds
          .Where(shipId => !player.Assignments.Any(assignment => assignment.AssetKind == "ship" && assignment.AssetId == shipId))
          .OrderBy(id => id, StringComparer.Ordinal)
          .Take(10))
        {
            player.Alerts.Add(new PlayerAlertRuntime
            {
                Id = $"alert-unassigned-ship-{shipId}",
                Kind = "unassigned-ship",
                Severity = "warning",
                Summary = $"Ship {shipId} has no player assignment.",
                AssetKind = "ship",
                AssetId = shipId,
            });
        }

        foreach (var stationId in player.AssetRegistry.StationIds
          .Where(stationId => !player.Assignments.Any(assignment => assignment.AssetKind == "station" && assignment.AssetId == stationId))
          .OrderBy(id => id, StringComparer.Ordinal)
          .Take(6))
        {
            player.Alerts.Add(new PlayerAlertRuntime
            {
                Id = $"alert-unassigned-station-{stationId}",
                Kind = "unassigned-station",
                Severity = "info",
                Summary = $"Station {stationId} is not part of a player station group.",
                AssetKind = "station",
                AssetId = stationId,
            });
        }

        foreach (var directive in player.Directives.Where(directive =>
          directive.Status == "active" &&
          !player.Assignments.Any(assignment => assignment.DirectiveId == directive.Id)).Take(6))
        {
            player.Alerts.Add(new PlayerAlertRuntime
            {
                Id = $"alert-orphan-directive-{directive.Id}",
                Kind = "orphan-directive",
                Severity = "warning",
                Summary = $"Directive {directive.Label} is not assigned to any asset or group.",
                RelatedDirectiveId = directive.Id,
            });
        }

        foreach (var policy in player.ReinforcementPolicies
          .Where(policy => policy.DesiredAssetCount > 0)
          .OrderBy(policy => policy.Id, StringComparer.Ordinal)
          .Take(6))
        {
            var available = world.Ships.Count(ship =>
              ship.FactionId == player.SovereignFactionId &&
              string.Equals(GetShipCategory(ship.Definition), policy.ShipKind, StringComparison.Ordinal));
            if (available < policy.DesiredAssetCount)
            {
                player.Alerts.Add(new PlayerAlertRuntime
                {
                    Id = $"alert-reinforcement-{policy.Id}",
                    Kind = "reinforcement-deficit",
                    Severity = "warning",
                    Summary = $"Reinforcement policy {policy.Label} is short {policy.DesiredAssetCount - available} {policy.ShipKind} assets.",
                });
            }
        }

        foreach (var program in player.ProductionPrograms
          .Where(program => program.TargetCount > 0 && program.CurrentCount < program.TargetCount)
          .OrderBy(program => program.Id, StringComparer.Ordinal)
          .Take(6))
        {
            player.Alerts.Add(new PlayerAlertRuntime
            {
                Id = $"alert-production-{program.Id}",
                Kind = "production-program-deficit",
                Severity = "info",
                Summary = $"Production program {program.Label} is at {program.CurrentCount}/{program.TargetCount}.",
            });
        }

        foreach (var systemId in world.Geopolitics?.Territory.ControlStates
          .Where(state => state.IsContested
            && (string.Equals(state.ControllerFactionId, player.SovereignFactionId, StringComparison.Ordinal)
              || string.Equals(state.PrimaryClaimantFactionId, player.SovereignFactionId, StringComparison.Ordinal)
              || state.ClaimantFactionIds.Contains(player.SovereignFactionId, StringComparer.Ordinal)))
          .Select(state => state.SystemId)
          .Where(systemId => player.Fronts.All(front => !front.SystemIds.Contains(systemId, StringComparer.Ordinal)))
          .OrderBy(systemId => systemId, StringComparer.Ordinal)
          .Take(4) ?? [])
        {
            player.Alerts.Add(new PlayerAlertRuntime
            {
                Id = $"alert-contested-system-{systemId}",
                Kind = "uncovered-contested-system",
                Severity = "warning",
                Summary = $"Contested player system {systemId} is not covered by a player front.",
            });
        }

        foreach (var region in player.EconomicRegions.Take(6))
        {
            var sharedRegion = world.Geopolitics?.EconomyRegions.Regions.FirstOrDefault(candidate =>
              string.Equals(candidate.FactionId, player.SovereignFactionId, StringComparison.Ordinal)
              && candidate.SystemIds.Intersect(region.SystemIds, StringComparer.Ordinal).Any());
            if (sharedRegion is null)
            {
                continue;
            }

            var bottleneck = world.Geopolitics?.EconomyRegions.Bottlenecks
              .Where(candidate => string.Equals(candidate.RegionId, sharedRegion.Id, StringComparison.Ordinal))
              .OrderByDescending(candidate => candidate.Severity)
              .ThenBy(candidate => candidate.ItemId, StringComparer.Ordinal)
              .FirstOrDefault();
            var security = world.Geopolitics?.EconomyRegions.SecurityAssessments.FirstOrDefault(candidate => string.Equals(candidate.RegionId, sharedRegion.Id, StringComparison.Ordinal));
            if (bottleneck is not null && bottleneck.Severity >= 2.5f)
            {
                player.Alerts.Add(new PlayerAlertRuntime
                {
                    Id = $"alert-region-bottleneck-{region.Id}-{bottleneck.ItemId}",
                    Kind = "economic-region-bottleneck",
                    Severity = "warning",
                    Summary = $"Region {region.Label} is bottlenecked on {bottleneck.ItemId}.",
                });
            }
            if ((security?.SupplyRisk ?? 0f) >= 0.55f)
            {
                player.Alerts.Add(new PlayerAlertRuntime
                {
                    Id = $"alert-region-risk-{region.Id}",
                    Kind = "economic-region-risk",
                    Severity = "warning",
                    Summary = $"Region {region.Label} has elevated logistics risk.",
                });
            }
        }

        foreach (var front in player.Fronts
          .Where(front => !string.IsNullOrWhiteSpace(front.TargetFactionId))
          .Take(6))
        {
            var relation = GeopoliticalSimulationService.FindRelation(world, player.SovereignFactionId, front.TargetFactionId!);
            if (relation is not null
                && relation.Posture is not "hostile" and not "war"
                && front.Priority >= 60f
                && !string.Equals(front.Posture, "hold", StringComparison.Ordinal))
            {
                player.Alerts.Add(new PlayerAlertRuntime
                {
                    Id = $"alert-front-posture-{front.Id}",
                    Kind = "front-diplomatic-misalignment",
                    Severity = "info",
                    Summary = $"Front {front.Label} targets {front.TargetFactionId} while diplomatic posture is {relation.Posture}.",
                });
            }
        }

        while (player.Alerts.Count > MaxAlerts)
        {
            player.Alerts.RemoveAt(player.Alerts.Count - 1);
        }
    }

    private static void AddDecision(PlayerFactionRuntime player, string kind, string summary, string? relatedKind, string? relatedId)
    {
        player.DecisionLog.Insert(0, new PlayerDecisionLogEntryRuntime
        {
            Id = $"player-decision-{Guid.NewGuid():N}",
            Kind = kind,
            Summary = summary,
            RelatedEntityKind = relatedKind,
            RelatedEntityId = relatedId,
            OccurredAtUtc = DateTimeOffset.UtcNow,
        });

        while (player.DecisionLog.Count > MaxDecisionEntries)
        {
            player.DecisionLog.RemoveAt(player.DecisionLog.Count - 1);
        }
    }

    private static PolicySetRuntime EnsurePolicySet(SimulationWorld world, PlayerFactionRuntime player, PlayerFactionPolicyRuntime policy, string? requestedPolicySetId)
    {
        if (requestedPolicySetId is not null && world.Policies.FirstOrDefault(candidate => candidate.Id == requestedPolicySetId) is { } existing)
        {
            return existing;
        }

        if (policy.PolicySetId is not null && world.Policies.FirstOrDefault(candidate => candidate.Id == policy.PolicySetId) is { } current)
        {
            return current;
        }

        var created = new PolicySetRuntime
        {
            Id = $"policy-player-{policy.Id}",
            OwnerKind = "player-faction-policy",
            OwnerId = policy.Id,
        };
        world.Policies.Add(created);
        player.AssetRegistry.PolicySetIds.Add(created.Id);
        return created;
    }

    private static void CopyPolicySetToPlayerPolicy(PolicySetRuntime policySet, PlayerFactionPolicyRuntime policy)
    {
        policy.TradeAccessPolicy = policySet.TradeAccessPolicy;
        policy.DockingAccessPolicy = policySet.DockingAccessPolicy;
        policy.ConstructionAccessPolicy = policySet.ConstructionAccessPolicy;
        policy.OperationalRangePolicy = policySet.OperationalRangePolicy;
        policy.CombatEngagementPolicy = policySet.CombatEngagementPolicy;
        policy.AvoidHostileSystems = policySet.AvoidHostileSystems;
        policy.FleeHullRatio = policySet.FleeHullRatio;
        policy.BlacklistedSystemIds.Clear();
        foreach (var systemId in policySet.BlacklistedSystemIds)
        {
            policy.BlacklistedSystemIds.Add(systemId);
        }
    }

    private static void ApplyPolicySetRequest(PolicySetRuntime policySet, PlayerPolicyCommandRequest request)
    {
        if (request.TradeAccessPolicy is not null)
        {
            policySet.TradeAccessPolicy = request.TradeAccessPolicy;
        }
        if (request.DockingAccessPolicy is not null)
        {
            policySet.DockingAccessPolicy = request.DockingAccessPolicy;
        }
        if (request.ConstructionAccessPolicy is not null)
        {
            policySet.ConstructionAccessPolicy = request.ConstructionAccessPolicy;
        }
        if (request.OperationalRangePolicy is not null)
        {
            policySet.OperationalRangePolicy = request.OperationalRangePolicy;
        }
        if (request.CombatEngagementPolicy is not null)
        {
            policySet.CombatEngagementPolicy = request.CombatEngagementPolicy;
        }
        if (request.AvoidHostileSystems.HasValue)
        {
            policySet.AvoidHostileSystems = request.AvoidHostileSystems.Value;
        }
        if (request.FleeHullRatio.HasValue)
        {
            policySet.FleeHullRatio = Math.Clamp(request.FleeHullRatio.Value, 0f, 1f);
        }
        policySet.BlacklistedSystemIds.Clear();
        foreach (var systemId in request.BlacklistedSystemIds ?? [])
        {
            policySet.BlacklistedSystemIds.Add(systemId);
        }
    }

    private static string? ResolvePolicySetId(SimulationWorld world, PlayerFactionRuntime player, string? policyId)
    {
        if (policyId is null)
        {
            return player.Policies.FirstOrDefault(policy => policy.Id == "player-core-policy")?.PolicySetId;
        }

        return player.Policies.FirstOrDefault(policy => policy.Id == policyId)?.PolicySetId
          ?? world.Policies.FirstOrDefault(policy => policy.Id == policyId)?.Id;
    }

    private static void RemoveOrganization(PlayerFactionRuntime player, string organizationId)
    {
        if (player.Fleets.RemoveAll(entity => entity.Id == organizationId) > 0)
        {
            player.AssetRegistry.FleetIds.Remove(organizationId);
            return;
        }
        if (player.TaskForces.RemoveAll(entity => entity.Id == organizationId) > 0)
        {
            player.AssetRegistry.TaskForceIds.Remove(organizationId);
            return;
        }
        if (player.StationGroups.RemoveAll(entity => entity.Id == organizationId) > 0)
        {
            player.AssetRegistry.StationGroupIds.Remove(organizationId);
            return;
        }
        if (player.EconomicRegions.RemoveAll(entity => entity.Id == organizationId) > 0)
        {
            player.AssetRegistry.EconomicRegionIds.Remove(organizationId);
            return;
        }
        if (player.Fronts.RemoveAll(entity => entity.Id == organizationId) > 0)
        {
            player.AssetRegistry.FrontIds.Remove(organizationId);
            return;
        }
        if (player.Reserves.RemoveAll(entity => entity.Id == organizationId) > 0)
        {
            player.AssetRegistry.ReserveIds.Remove(organizationId);
            return;
        }

        throw new InvalidOperationException($"Unknown organization '{organizationId}'.");
    }

    private static string ResolveOrganizationKind(PlayerFactionRuntime player, string organizationId)
    {
        if (player.Fleets.Any(entity => entity.Id == organizationId)) return "fleet";
        if (player.TaskForces.Any(entity => entity.Id == organizationId)) return "task-force";
        if (player.StationGroups.Any(entity => entity.Id == organizationId)) return "station-group";
        if (player.EconomicRegions.Any(entity => entity.Id == organizationId)) return "economic-region";
        if (player.Fronts.Any(entity => entity.Id == organizationId)) return "front";
        if (player.Reserves.Any(entity => entity.Id == organizationId)) return "reserve";
        throw new InvalidOperationException($"Unknown organization '{organizationId}'.");
    }

    private static IEnumerable<string> ExistingOrganizationIds(PlayerFactionRuntime player) =>
      player.Fleets.Select(entity => entity.Id)
        .Concat(player.TaskForces.Select(entity => entity.Id))
        .Concat(player.StationGroups.Select(entity => entity.Id))
        .Concat(player.EconomicRegions.Select(entity => entity.Id))
        .Concat(player.Fronts.Select(entity => entity.Id))
        .Concat(player.Reserves.Select(entity => entity.Id));

    private static string NormalizeKind(string value) =>
      value.Trim().ToLowerInvariant();

    private static string CreateDomainId(string prefix, string label, IEnumerable<string> existingIds)
    {
        var slug = new string(label
          .Trim()
          .ToLowerInvariant()
          .Select(character => char.IsLetterOrDigit(character) ? character : '-')
          .ToArray())
          .Trim('-');
        if (string.IsNullOrWhiteSpace(slug))
        {
            slug = prefix;
        }

        var candidate = $"{prefix}-{slug}";
        var known = existingIds.ToHashSet(StringComparer.Ordinal);
        if (!known.Contains(candidate))
        {
            return candidate;
        }

        var suffix = 2;
        while (known.Contains($"{candidate}-{suffix}"))
        {
            suffix += 1;
        }
        return $"{candidate}-{suffix}";
    }

    private static void UpdateStringList(List<string> target, IEnumerable<string>? requested, bool replace, IEnumerable<string> allowedValues)
    {
        if (replace)
        {
            target.Clear();
        }
        if (requested is null)
        {
            return;
        }

        var allowed = allowedValues.ToHashSet(StringComparer.Ordinal);
        foreach (var value in requested.Where(value => !string.IsNullOrWhiteSpace(value)))
        {
            if (allowed.Contains(value) && !target.Contains(value, StringComparer.Ordinal))
            {
                target.Add(value);
            }
        }
    }

    private static void SyncSet(HashSet<string> target, IEnumerable<string> source)
    {
        target.Clear();
        foreach (var value in source.Where(value => !string.IsNullOrWhiteSpace(value)))
        {
            target.Add(value);
        }
    }
}
