using static SpaceGame.Api.Shared.Runtime.SimulationRuntimeSupport;
using static SpaceGame.Api.Shared.Runtime.ShipBehaviorKinds;
using static SpaceGame.Api.Stations.Simulation.InfrastructureSimulationService;

namespace SpaceGame.Api.Ships.AI;

public sealed partial class ShipAiService
{
    private static ShipOrderRuntime? GetTopOrder(ShipRuntime ship) =>
      ship.OrderQueue
        .Where(order => order.Status is OrderStatus.Queued or OrderStatus.Active)
        .OrderByDescending(GetOrderSourcePriority)
        .ThenByDescending(order => order.Priority)
        .ThenBy(order => order.CreatedAtUtc)
        .FirstOrDefault();

    private static int GetOrderSourcePriority(ShipOrderRuntime order) => order.SourceKind switch
    {
        ShipOrderSourceKind.Player => 300,
        ShipOrderSourceKind.Commander => 200,
        ShipOrderSourceKind.Behavior => 100,
        _ => 0,
    };

    private void SyncBehaviorOrders(SimulationWorld world, ShipRuntime ship)
    {
        var desiredOrder = BuildManagedBehaviorOrder(world, ship);
        ship.OrderQueue.RemoveAll(order =>
            order.SourceKind == ShipOrderSourceKind.Behavior
            && (desiredOrder is null || !string.Equals(order.Id, desiredOrder.Id, StringComparison.Ordinal)));

        if (desiredOrder is null)
        {
            return;
        }

        var existing = ship.OrderQueue.FirstOrDefault(order => string.Equals(order.Id, desiredOrder.Id, StringComparison.Ordinal));
        if (existing is null)
        {
            ship.OrderQueue.Add(desiredOrder);
            return;
        }

        if (ManagedOrdersEqual(existing, desiredOrder))
        {
            return;
        }

        ship.OrderQueue.Remove(existing);
        ship.OrderQueue.Add(desiredOrder);
    }

    private ShipOrderRuntime? BuildManagedBehaviorOrder(SimulationWorld world, ShipRuntime ship)
    {
        var assignment = ResolveAssignment(world, ship);
        var behaviorKind = assignment?.BehaviorKind ?? ship.DefaultBehavior.Kind;
        var systemId = assignment?.TargetSystemId ?? ship.DefaultBehavior.AreaSystemId ?? ship.SystemId;

        if (string.Equals(behaviorKind, HoldPosition, StringComparison.Ordinal))
        {
            return new ShipOrderRuntime
            {
                Id = $"behavior-{ship.Id}-hold-position",
                Kind = ShipOrderKinds.HoldPosition,
                SourceKind = ShipOrderSourceKind.Behavior,
                SourceId = behaviorKind,
                Priority = 0,
                InterruptCurrentPlan = false,
                Label = "Hold position",
                TargetSystemId = systemId,
                TargetPosition = ship.DefaultBehavior.TargetPosition ?? ship.Position,
                WaitSeconds = MathF.Max(2f, ship.DefaultBehavior.WaitSeconds),
                Radius = ship.DefaultBehavior.Radius,
                MaxSystemRange = ship.DefaultBehavior.MaxSystemRange,
                KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,
            };
        }

        if (string.Equals(behaviorKind, DockAndWait, StringComparison.Ordinal))
        {
            var station = ResolveStation(world, assignment?.TargetEntityId ?? ship.DefaultBehavior.TargetEntityId ?? ship.DefaultBehavior.HomeStationId);
            if (station is null)
            {
                ship.LastAccessFailureReason = "station-missing";
                return null;
            }

            ship.LastAccessFailureReason = null;
            return new ShipOrderRuntime
            {
                Id = $"behavior-{ship.Id}-dock-and-wait",
                Kind = ShipOrderKinds.DockAndWait,
                SourceKind = ShipOrderSourceKind.Behavior,
                SourceId = behaviorKind,
                Priority = 0,
                InterruptCurrentPlan = false,
                Label = $"Dock and wait at {station.Label}",
                TargetEntityId = station.Id,
                TargetSystemId = station.SystemId,
                DestinationStationId = station.Id,
                WaitSeconds = MathF.Max(2f, ship.DefaultBehavior.WaitSeconds),
                Radius = ship.DefaultBehavior.Radius,
                MaxSystemRange = ship.DefaultBehavior.MaxSystemRange,
                KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,
            };
        }

        if (string.Equals(behaviorKind, FlyAndWait, StringComparison.Ordinal))
        {
            ship.LastAccessFailureReason = null;
            return new ShipOrderRuntime
            {
                Id = $"behavior-{ship.Id}-fly-and-wait",
                Kind = ShipOrderKinds.FlyAndWait,
                SourceKind = ShipOrderSourceKind.Behavior,
                SourceId = behaviorKind,
                Priority = 0,
                InterruptCurrentPlan = false,
                Label = "Fly and wait",
                TargetSystemId = systemId,
                TargetPosition = ship.DefaultBehavior.TargetPosition ?? ship.Position,
                WaitSeconds = MathF.Max(2f, ship.DefaultBehavior.WaitSeconds),
                Radius = ship.DefaultBehavior.Radius,
                MaxSystemRange = ship.DefaultBehavior.MaxSystemRange,
                KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,
            };
        }

        if (string.Equals(behaviorKind, FollowShip, StringComparison.Ordinal))
        {
            var targetShip = world.Ships.FirstOrDefault(candidate =>
                candidate.Id == (assignment?.TargetEntityId ?? ship.DefaultBehavior.TargetEntityId)
                && candidate.Health > 0f);
            if (targetShip is null)
            {
                ship.LastAccessFailureReason = "target-ship-missing";
                return null;
            }

            ship.LastAccessFailureReason = null;
            return new ShipOrderRuntime
            {
                Id = $"behavior-{ship.Id}-follow-ship",
                Kind = ShipOrderKinds.FollowShip,
                SourceKind = ShipOrderSourceKind.Behavior,
                SourceId = behaviorKind,
                Priority = 0,
                InterruptCurrentPlan = false,
                Label = $"Follow {targetShip.Definition.Name}",
                TargetEntityId = targetShip.Id,
                TargetSystemId = targetShip.SystemId,
                WaitSeconds = MathF.Max(2f, ship.DefaultBehavior.WaitSeconds),
                Radius = MathF.Max(16f, ship.DefaultBehavior.Radius),
                MaxSystemRange = ship.DefaultBehavior.MaxSystemRange,
                KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,
            };
        }

        if (string.Equals(behaviorKind, FlyToObject, StringComparison.Ordinal))
        {
            var targetEntityId = assignment?.TargetEntityId ?? ship.DefaultBehavior.TargetEntityId;
            var target = ResolveObjectTarget(world, targetEntityId);
            if (target is null)
            {
                ship.LastAccessFailureReason = "target-missing";
                return null;
            }

            ship.LastAccessFailureReason = null;
            return new ShipOrderRuntime
            {
                Id = $"behavior-{ship.Id}-fly-to-object",
                Kind = ShipOrderKinds.FlyToObject,
                SourceKind = ShipOrderSourceKind.Behavior,
                SourceId = behaviorKind,
                Priority = 0,
                InterruptCurrentPlan = false,
                Label = "Fly to object",
                TargetEntityId = targetEntityId,
                TargetSystemId = target.Value.SystemId,
                TargetPosition = target.Value.Position,
                WaitSeconds = MathF.Max(1f, ship.DefaultBehavior.WaitSeconds),
                Radius = MathF.Max(8f, ship.DefaultBehavior.Radius),
                MaxSystemRange = ship.DefaultBehavior.MaxSystemRange,
                KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,
            };
        }

        if (string.Equals(behaviorKind, Patrol, StringComparison.Ordinal))
        {
            return BuildManagedPatrolOrder(world, ship, assignment, behaviorKind);
        }

        if (string.Equals(behaviorKind, AttackTarget, StringComparison.Ordinal))
        {
            var targetEntityId = assignment?.TargetEntityId ?? ship.DefaultBehavior.TargetEntityId;
            if (string.IsNullOrWhiteSpace(targetEntityId))
            {
                return BuildManagedPatrolOrder(world, ship, assignment, behaviorKind);
            }

            var target = ResolveObjectTarget(world, targetEntityId);
            ship.LastAccessFailureReason = target is null ? "target-missing" : null;
            return new ShipOrderRuntime
            {
                Id = $"behavior-{ship.Id}-attack-target",
                Kind = ShipOrderKinds.AttackTarget,
                SourceKind = ShipOrderSourceKind.Behavior,
                SourceId = behaviorKind,
                Priority = 0,
                InterruptCurrentPlan = false,
                Label = "Attack target",
                TargetEntityId = targetEntityId,
                TargetSystemId = target?.SystemId ?? assignment?.TargetSystemId ?? ship.DefaultBehavior.AreaSystemId ?? ship.SystemId,
                TargetPosition = target?.Position ?? ship.Position,
                WaitSeconds = 0f,
                Radius = 26f,
                MaxSystemRange = ship.DefaultBehavior.MaxSystemRange,
                KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,
            };
        }

        if (string.Equals(behaviorKind, ConstructStation, StringComparison.Ordinal))
        {
            var site = world.ConstructionSites.FirstOrDefault(candidate => candidate.Id == (assignment?.TargetEntityId ?? ship.DefaultBehavior.PreferredConstructionSiteId))
              ?? world.ConstructionSites
                .Where(candidate => candidate.FactionId == ship.FactionId && candidate.State is ConstructionSiteStateKinds.Active or ConstructionSiteStateKinds.Planned)
                .OrderBy(candidate => candidate.Id, StringComparer.Ordinal)
                .FirstOrDefault();
            if (site is null)
            {
                ship.LastAccessFailureReason = "no-construction-site";
                return null;
            }

            if (ResolveSupportStation(world, ship, site) is null)
            {
                ship.LastAccessFailureReason = "support-station-missing";
                return null;
            }

            ship.LastAccessFailureReason = null;
            return new ShipOrderRuntime
            {
                Id = $"behavior-{ship.Id}-construct-station",
                Kind = ShipOrderKinds.BuildAtSite,
                SourceKind = ShipOrderSourceKind.Behavior,
                SourceId = behaviorKind,
                Priority = 0,
                InterruptCurrentPlan = false,
                Label = $"Build {site.BlueprintId}",
                TargetEntityId = site.Id,
                TargetSystemId = site.SystemId,
                ConstructionSiteId = site.Id,
                MaxSystemRange = ship.DefaultBehavior.MaxSystemRange,
                KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,
            };
        }

        if (string.Equals(behaviorKind, AdvancedAutoMine, StringComparison.Ordinal)
            || string.Equals(behaviorKind, ExpertAutoMine, StringComparison.Ordinal))
        {
            var homeStation = ResolveStation(world, assignment?.HomeStationId ?? ship.DefaultBehavior.HomeStationId);
            if (homeStation is null)
            {
                ship.LastAccessFailureReason = "no-home-station";
                return null;
            }

            var opportunity = SelectMiningOpportunity(world, ship, homeStation, assignment, behaviorKind);
            if (opportunity is null)
            {
                ship.LastAccessFailureReason = "no-mineable-node";
                return null;
            }

            ship.LastAccessFailureReason = null;
            return new ShipOrderRuntime
            {
                Id = $"behavior-{ship.Id}-{behaviorKind}-mine-and-deliver",
                Kind = ShipOrderKinds.MineAndDeliverRun,
                SourceKind = ShipOrderSourceKind.Behavior,
                SourceId = behaviorKind,
                Priority = 0,
                InterruptCurrentPlan = false,
                Label = opportunity.Summary,
                TargetEntityId = opportunity.Node.Id,
                TargetSystemId = opportunity.Node.SystemId,
                DestinationStationId = opportunity.DropOffStation.Id,
                ItemId = opportunity.Node.ItemId,
                NodeId = opportunity.Node.Id,
                MaxSystemRange = ship.DefaultBehavior.MaxSystemRange,
                KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,
            };
        }

        if (string.Equals(behaviorKind, ProtectPosition, StringComparison.Ordinal))
        {
            var targetSystemId = assignment?.TargetSystemId ?? ship.DefaultBehavior.AreaSystemId ?? ship.SystemId;
            var targetPosition = ship.DefaultBehavior.TargetPosition ?? assignment?.TargetPosition ?? ship.Position;
            var threat = SelectThreatTarget(world, ship, targetSystemId, targetPosition, MathF.Max(90f, ship.DefaultBehavior.Radius));
            if (threat is not null)
            {
                ship.LastAccessFailureReason = null;
                return CreateManagedAttackOrder(ship, behaviorKind, "Protect position", threat.EntityId, threat.SystemId, threat.Position);
            }

            ship.LastAccessFailureReason = null;
            return CreateManagedFlyAndWaitOrder(
                ship,
                behaviorKind,
                "Protect position",
                targetSystemId,
                targetPosition,
                MathF.Max(2f, ship.DefaultBehavior.WaitSeconds),
                MathF.Max(6f, ship.DefaultBehavior.Radius));
        }

        if (string.Equals(behaviorKind, ProtectShip, StringComparison.Ordinal))
        {
            var guardTarget = world.Ships.FirstOrDefault(candidate =>
                candidate.Id == (assignment?.TargetEntityId ?? ship.DefaultBehavior.TargetEntityId)
                && candidate.Health > 0f);
            if (guardTarget is null)
            {
                return BuildManagedPatrolOrder(world, ship, assignment, Patrol);
            }

            var threat = SelectThreatTarget(
                world,
                ship,
                guardTarget.SystemId,
                guardTarget.Position,
                MathF.Max(90f, ship.DefaultBehavior.Radius),
                excludeEntityId: guardTarget.Id);
            if (threat is not null)
            {
                ship.LastAccessFailureReason = null;
                return CreateManagedAttackOrder(ship, behaviorKind, $"Protect {guardTarget.Definition.Name}", threat.EntityId, threat.SystemId, threat.Position);
            }

            ship.LastAccessFailureReason = null;
            return CreateManagedFollowShipOrder(
                ship,
                behaviorKind,
                $"Escort {guardTarget.Definition.Name}",
                guardTarget,
                MathF.Max(18f, ship.DefaultBehavior.Radius * 0.5f),
                MathF.Max(2f, ship.DefaultBehavior.WaitSeconds));
        }

        if (string.Equals(behaviorKind, ProtectStation, StringComparison.Ordinal))
        {
            var station = ResolveStation(world, assignment?.TargetEntityId ?? ship.DefaultBehavior.TargetEntityId ?? assignment?.HomeStationId ?? ship.DefaultBehavior.HomeStationId);
            if (station is null)
            {
                return BuildManagedPatrolOrder(world, ship, assignment, Patrol);
            }

            var threat = SelectThreatTarget(world, ship, station.SystemId, station.Position, MathF.Max(station.Radius + 80f, ship.DefaultBehavior.Radius));
            if (threat is not null)
            {
                ship.LastAccessFailureReason = null;
                return CreateManagedAttackOrder(ship, behaviorKind, $"Protect {station.Label}", threat.EntityId, threat.SystemId, threat.Position);
            }

            ship.LastAccessFailureReason = null;
            return CreateManagedFlyAndWaitOrder(
                ship,
                behaviorKind,
                $"Guard {station.Label}",
                station.SystemId,
                GetFormationPosition(station.Position, ship.Id, MathF.Max(station.Radius + 18f, ship.DefaultBehavior.Radius)),
                MathF.Max(2f, ship.DefaultBehavior.WaitSeconds),
                MathF.Max(6f, ship.DefaultBehavior.Radius));
        }

        if (string.Equals(behaviorKind, Police, StringComparison.Ordinal))
        {
            var homeStation = ResolveStation(world, assignment?.HomeStationId ?? ship.DefaultBehavior.HomeStationId);
            var policeSystemId = assignment?.TargetSystemId ?? ship.DefaultBehavior.AreaSystemId ?? homeStation?.SystemId ?? ship.SystemId;
            var areaPosition = homeStation?.Position ?? ship.DefaultBehavior.TargetPosition ?? ship.Position;
            var contact = SelectPoliceContact(world, ship, policeSystemId, areaPosition, MathF.Max(80f, ship.DefaultBehavior.Radius));
            if (contact is null)
            {
                return BuildManagedPatrolOrder(world, ship, assignment, Patrol);
            }

            ship.LastAccessFailureReason = null;
            return contact.Engage
                ? CreateManagedAttackOrder(ship, behaviorKind, "Police engage", contact.EntityId, contact.SystemId, contact.Position)
                : CreateManagedFollowTargetOrder(ship, behaviorKind, "Police inspect", contact.EntityId, contact.SystemId, contact.Position, MathF.Max(14f, ship.DefaultBehavior.Radius * 0.5f), MathF.Max(2f, ship.DefaultBehavior.WaitSeconds));
        }

        if (string.Equals(behaviorKind, LocalAutoTrade, StringComparison.Ordinal)
            || string.Equals(behaviorKind, AdvancedAutoTrade, StringComparison.Ordinal)
            || string.Equals(behaviorKind, FillShortages, StringComparison.Ordinal)
            || string.Equals(behaviorKind, FindBuildTasks, StringComparison.Ordinal)
            || string.Equals(behaviorKind, RevisitKnownStations, StringComparison.Ordinal))
        {
            var homeStation = ResolveStation(world, assignment?.HomeStationId ?? ship.DefaultBehavior.HomeStationId);
            var route = SelectTradeRoute(world, ship, homeStation, behaviorKind, ship.DefaultBehavior.KnownStationsOnly);
            if (route is not null)
            {
                ship.LastAccessFailureReason = null;
                return CreateManagedTradeRouteOrder(ship, behaviorKind, route);
            }

            if (string.Equals(behaviorKind, RevisitKnownStations, StringComparison.Ordinal)
                && SelectKnownStationVisit(world, ship, homeStation) is { } visitStation)
            {
                ship.LastAccessFailureReason = null;
                return CreateManagedDockAndWaitOrder(ship, behaviorKind, visitStation, MathF.Max(2f, ship.DefaultBehavior.WaitSeconds), $"Revisit {visitStation.Label}");
            }

            ship.LastAccessFailureReason = "no-trade-route";
            return null;
        }

        if (string.Equals(behaviorKind, SupplyFleet, StringComparison.Ordinal))
        {
            var homeStation = ResolveStation(world, assignment?.HomeStationId ?? ship.DefaultBehavior.HomeStationId);
            var plan = SelectFleetSupplyPlan(world, ship, homeStation);
            if (plan is null)
            {
                ship.LastAccessFailureReason = "no-fleet-to-supply";
                return null;
            }

            ship.LastAccessFailureReason = null;
            return new ShipOrderRuntime
            {
                Id = $"behavior-{ship.Id}-supply-fleet",
                Kind = ShipOrderKinds.SupplyFleetRun,
                SourceKind = ShipOrderSourceKind.Behavior,
                SourceId = behaviorKind,
                Priority = 0,
                InterruptCurrentPlan = false,
                Label = plan.Summary,
                TargetEntityId = plan.TargetShip.Id,
                TargetSystemId = plan.TargetShip.SystemId,
                SourceStationId = plan.SourceStation.Id,
                ItemId = plan.ItemId,
                Radius = plan.Radius,
                MaxSystemRange = ship.DefaultBehavior.MaxSystemRange,
                KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,
            };
        }

        if (string.Equals(behaviorKind, AutoSalvage, StringComparison.Ordinal))
        {
            var homeStation = ResolveStation(world, assignment?.HomeStationId ?? ship.DefaultBehavior.HomeStationId);
            if (homeStation is null)
            {
                ship.LastAccessFailureReason = "no-home-station";
                return null;
            }

            var salvage = SelectSalvageOpportunity(world, ship, homeStation);
            if (salvage is null)
            {
                ship.LastAccessFailureReason = "no-salvage-target";
                return null;
            }

            ship.LastAccessFailureReason = null;
            return new ShipOrderRuntime
            {
                Id = $"behavior-{ship.Id}-auto-salvage",
                Kind = ShipOrderKinds.SalvageRun,
                SourceKind = ShipOrderSourceKind.Behavior,
                SourceId = behaviorKind,
                Priority = 0,
                InterruptCurrentPlan = false,
                Label = salvage.Summary,
                TargetEntityId = salvage.Wreck.Id,
                TargetSystemId = salvage.Wreck.SystemId,
                TargetPosition = salvage.Wreck.Position,
                SourceStationId = homeStation.Id,
                ItemId = salvage.Wreck.ItemId,
                Radius = MathF.Max(8f, ship.DefaultBehavior.Radius * 0.25f),
                MaxSystemRange = ship.DefaultBehavior.MaxSystemRange,
                KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,
            };
        }

        if (string.Equals(behaviorKind, RepeatOrders, StringComparison.Ordinal))
        {
            if (ship.DefaultBehavior.RepeatOrders.Count == 0)
            {
                ship.LastAccessFailureReason = "no-repeat-orders";
                return null;
            }

            var template = ship.DefaultBehavior.RepeatOrders[ship.DefaultBehavior.RepeatIndex % ship.DefaultBehavior.RepeatOrders.Count];
            ship.LastAccessFailureReason = null;
            return new ShipOrderRuntime
            {
                Id = $"behavior-{ship.Id}-repeat-{ship.DefaultBehavior.RepeatIndex}",
                Kind = template.Kind,
                SourceKind = ShipOrderSourceKind.Behavior,
                SourceId = behaviorKind,
                Priority = 0,
                InterruptCurrentPlan = false,
                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 ?? ship.DefaultBehavior.MaxSystemRange,
                KnownStationsOnly = template.KnownStationsOnly,
            };
        }

        if (!string.Equals(behaviorKind, LocalAutoMine, StringComparison.Ordinal))
        {
            return null;
        }

        var itemId = assignment?.ItemId ?? ship.DefaultBehavior.ItemId;
        if (string.IsNullOrWhiteSpace(itemId))
        {
            ship.LastAccessFailureReason = "missing-item";
            return null;
        }

        if (GetShipCargoAmount(ship) >= ship.Definition.GetTotalCargoCapacity() - 0.01f)
        {
            var buyer = SelectLocalAutoMineBuyer(world, ship, systemId, itemId);
            if (buyer is null)
            {
                ship.LastAccessFailureReason = "no-suitable-buyer";
                return null;
            }

            ship.LastAccessFailureReason = null;
            return new ShipOrderRuntime
            {
                Id = $"behavior-{ship.Id}-local-auto-mine-sell",
                Kind = ShipOrderKinds.SellMinedCargo,
                SourceKind = ShipOrderSourceKind.Behavior,
                SourceId = behaviorKind,
                Priority = 0,
                InterruptCurrentPlan = false,
                Label = $"Sell {itemId} in {systemId}",
                TargetEntityId = buyer.Id,
                TargetSystemId = buyer.SystemId,
                DestinationStationId = buyer.Id,
                ItemId = itemId,
                WaitSeconds = 0f,
                Radius = 0f,
                MaxSystemRange = 0,
                KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,
            };
        }

        var node = SelectLocalMiningNode(world, ship, systemId, itemId);
        if (node is null)
        {
            ship.LastAccessFailureReason = "no-mineable-node";
            return null;
        }

        ship.LastAccessFailureReason = null;
        return new ShipOrderRuntime
        {
            Id = $"behavior-{ship.Id}-local-auto-mine-mine",
            Kind = ShipOrderKinds.MineLocal,
            SourceKind = ShipOrderSourceKind.Behavior,
            SourceId = behaviorKind,
            Priority = 0,
            InterruptCurrentPlan = false,
            Label = $"Mine {itemId} in {systemId}",
            TargetSystemId = node.SystemId,
            NodeId = node.Id,
            ItemId = node.ItemId,
            WaitSeconds = 0f,
            Radius = 0f,
            MaxSystemRange = 0,
            KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,
        };
    }

    private static bool ManagedOrdersEqual(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)
      && left.TargetPosition == right.TargetPosition
      && string.Equals(left.DestinationStationId, right.DestinationStationId, StringComparison.Ordinal)
      && string.Equals(left.ItemId, right.ItemId, StringComparison.Ordinal)
      && string.Equals(left.NodeId, right.NodeId, StringComparison.Ordinal)
      && left.WaitSeconds.Equals(right.WaitSeconds)
      && left.Radius.Equals(right.Radius)
      && left.MaxSystemRange == right.MaxSystemRange
      && left.KnownStationsOnly == right.KnownStationsOnly;

    private ShipOrderRuntime BuildManagedPatrolOrder(SimulationWorld world, ShipRuntime ship, CommanderAssignmentRuntime? assignment, string sourceKind)
    {
        var patrolSystemId = assignment?.TargetSystemId ?? ship.DefaultBehavior.AreaSystemId ?? ship.SystemId;
        var protectPosition = ship.DefaultBehavior.TargetPosition ?? assignment?.TargetPosition ?? ship.Position;
        var patrolThreat = SelectThreatTarget(world, ship, patrolSystemId, protectPosition, MathF.Max(60f, ship.DefaultBehavior.Radius));
        if (patrolThreat is not null)
        {
            ship.LastAccessFailureReason = null;
            return CreateManagedAttackOrder(ship, sourceKind, "Patrol intercept", patrolThreat.EntityId, patrolThreat.SystemId, patrolThreat.Position, orderIdSuffix: "patrol-attack");
        }

        Vector3 targetPosition;
        string targetSystemId;
        if (ship.DefaultBehavior.PatrolPoints.Count > 0)
        {
            var index = ship.DefaultBehavior.PatrolIndex % ship.DefaultBehavior.PatrolPoints.Count;
            targetPosition = ship.DefaultBehavior.PatrolPoints[index];
            ship.DefaultBehavior.PatrolIndex = (index + 1) % ship.DefaultBehavior.PatrolPoints.Count;
            targetSystemId = ship.DefaultBehavior.AreaSystemId ?? ship.SystemId;
        }
        else if (ResolveStation(world, ship.DefaultBehavior.HomeStationId ?? assignment?.HomeStationId) is { } homeStation)
        {
            var patrolRadius = homeStation.Radius + 90f;
            targetPosition = new Vector3(homeStation.Position.X + patrolRadius, homeStation.Position.Y, homeStation.Position.Z);
            targetSystemId = homeStation.SystemId;
        }
        else
        {
            targetPosition = ship.Position;
            targetSystemId = ship.SystemId;
        }

        ship.LastAccessFailureReason = null;
        return CreateManagedFlyAndWaitOrder(ship, sourceKind, "Patrol waypoint", targetSystemId, targetPosition, MathF.Max(2f, ship.DefaultBehavior.WaitSeconds), MathF.Max(6f, ship.DefaultBehavior.Radius), orderIdSuffix: "patrol-fly-and-wait");
    }

    private static ShipOrderRuntime CreateManagedAttackOrder(
        ShipRuntime ship,
        string behaviorKind,
        string label,
        string targetEntityId,
        string targetSystemId,
        Vector3 targetPosition,
        string? orderIdSuffix = null) =>
        new()
        {
            Id = $"behavior-{ship.Id}-{orderIdSuffix ?? behaviorKind}",
            Kind = ShipOrderKinds.AttackTarget,
            SourceKind = ShipOrderSourceKind.Behavior,
            SourceId = behaviorKind,
            Priority = 0,
            InterruptCurrentPlan = false,
            Label = label,
            TargetEntityId = targetEntityId,
            TargetSystemId = targetSystemId,
            TargetPosition = targetPosition,
            WaitSeconds = 0f,
            Radius = 26f,
            MaxSystemRange = ship.DefaultBehavior.MaxSystemRange,
            KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,
        };

    private static ShipOrderRuntime CreateManagedTradeRouteOrder(ShipRuntime ship, string behaviorKind, TradeRoutePlan route) =>
        new()
        {
            Id = $"behavior-{ship.Id}-{behaviorKind}-trade-route",
            Kind = ShipOrderKinds.TradeRoute,
            SourceKind = ShipOrderSourceKind.Behavior,
            SourceId = behaviorKind,
            Priority = 0,
            InterruptCurrentPlan = false,
            Label = route.Summary,
            SourceStationId = route.SourceStation.Id,
            DestinationStationId = route.DestinationStation.Id,
            ItemId = route.ItemId,
            MaxSystemRange = ship.DefaultBehavior.MaxSystemRange,
            KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,
        };

    private static ShipOrderRuntime CreateManagedDockAndWaitOrder(ShipRuntime ship, string behaviorKind, StationRuntime station, float waitSeconds, string label) =>
        new()
        {
            Id = $"behavior-{ship.Id}-{behaviorKind}-dock-and-wait",
            Kind = ShipOrderKinds.DockAndWait,
            SourceKind = ShipOrderSourceKind.Behavior,
            SourceId = behaviorKind,
            Priority = 0,
            InterruptCurrentPlan = false,
            Label = label,
            TargetEntityId = station.Id,
            TargetSystemId = station.SystemId,
            DestinationStationId = station.Id,
            WaitSeconds = waitSeconds,
            Radius = ship.DefaultBehavior.Radius,
            MaxSystemRange = ship.DefaultBehavior.MaxSystemRange,
            KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,
        };

    private static ShipOrderRuntime CreateManagedFlyAndWaitOrder(
        ShipRuntime ship,
        string behaviorKind,
        string label,
        string targetSystemId,
        Vector3 targetPosition,
        float waitSeconds,
        float radius,
        string? orderIdSuffix = null) =>
        new()
        {
            Id = $"behavior-{ship.Id}-{orderIdSuffix ?? behaviorKind}",
            Kind = ShipOrderKinds.FlyAndWait,
            SourceKind = ShipOrderSourceKind.Behavior,
            SourceId = behaviorKind,
            Priority = 0,
            InterruptCurrentPlan = false,
            Label = label,
            TargetSystemId = targetSystemId,
            TargetPosition = targetPosition,
            WaitSeconds = waitSeconds,
            Radius = radius,
            MaxSystemRange = ship.DefaultBehavior.MaxSystemRange,
            KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,
        };

    private static ShipOrderRuntime CreateManagedFollowShipOrder(
        ShipRuntime ship,
        string behaviorKind,
        string label,
        ShipRuntime targetShip,
        float radius,
        float waitSeconds) =>
        new()
        {
            Id = $"behavior-{ship.Id}-{behaviorKind}",
            Kind = ShipOrderKinds.FollowShip,
            SourceKind = ShipOrderSourceKind.Behavior,
            SourceId = behaviorKind,
            Priority = 0,
            InterruptCurrentPlan = false,
            Label = label,
            TargetEntityId = targetShip.Id,
            TargetSystemId = targetShip.SystemId,
            WaitSeconds = waitSeconds,
            Radius = radius,
            MaxSystemRange = ship.DefaultBehavior.MaxSystemRange,
            KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,
        };

    private static ShipOrderRuntime CreateManagedFollowTargetOrder(
        ShipRuntime ship,
        string behaviorKind,
        string label,
        string targetEntityId,
        string targetSystemId,
        Vector3 targetPosition,
        float radius,
        float waitSeconds) =>
        new()
        {
            Id = $"behavior-{ship.Id}-{behaviorKind}",
            Kind = ShipOrderKinds.FollowShip,
            SourceKind = ShipOrderSourceKind.Behavior,
            SourceId = behaviorKind,
            Priority = 0,
            InterruptCurrentPlan = false,
            Label = label,
            TargetEntityId = targetEntityId,
            TargetSystemId = targetSystemId,
            TargetPosition = targetPosition,
            WaitSeconds = waitSeconds,
            Radius = radius,
            MaxSystemRange = ship.DefaultBehavior.MaxSystemRange,
            KnownStationsOnly = ship.DefaultBehavior.KnownStationsOnly,
        };
}
