namespace ReactorMaintenance.Simulation;

public static class LevelEditor
{
    public sealed record MoveOccupantResult(bool Success, LevelState Level, string Reason)
    {
        public static MoveOccupantResult Succeeded(LevelState level)
        {
            return new(true, level, string.Empty);
        }

        public static MoveOccupantResult Failed(LevelState level, string reason)
        {
            return new(false, level, reason);
        }
    }

    public static LevelState MoveOccupant(LevelState level, GridPosition source, GridPosition destination)
    {
        return TryMoveOccupant(level, source, destination).Level;
    }

    public static MoveOccupantResult TryMoveOccupant(LevelState level, GridPosition source, GridPosition destination)
    {
        if (!level.InBounds(source))
            return MoveOccupantResult.Failed(level, "Drag start is outside the level.");

        if (!level.InBounds(destination))
            return MoveOccupantResult.Failed(level, "Drop target is outside the level.");

        if (source == destination)
            return MoveOccupantResult.Failed(level, "Drop target is the same cell.");

        var prop = level.GetProp(source);
        if (prop.Type != EPropType.None)
            return TryMoveProp(level, source, destination, prop);

        var leak = level.Leaks.FirstOrDefault(leak => !leak.Repaired && (leak.AccessPosition == source || leak.UndergroundPosition == source));
        if (leak is not null)
            return TryMoveLeak(level, leak, destination);

        return level.Robot.Position == source
            ? TryMoveRobot(level, destination)
            : MoveOccupantResult.Failed(level, "No movable robot, prop, source, or leak starts here.");
    }

    private static MoveOccupantResult TryMoveRobot(LevelState level, GridPosition destination)
    {
        if (!level.IsFloor(destination))
            return MoveOccupantResult.Failed(level, "Robot destination must be a floor cell.");

        return MoveOccupantResult.Succeeded(level with { Robot = level.Robot with { Position = destination } });
    }

    private static MoveOccupantResult TryMoveProp(LevelState level, GridPosition source, GridPosition destination, PropState prop)
    {
        if (!level.IsFloor(destination))
            return MoveOccupantResult.Failed(level, "Prop destination must be a floor cell.");

        if (level.GetProp(destination).Type != EPropType.None)
            return MoveOccupantResult.Failed(level, "Prop destination is already occupied.");

        var next = level.SetProp(source, new()).SetProp(destination, prop);
        if (prop.Type != EPropType.ReactorControl)
            return MoveOccupantResult.Succeeded(next);

        return MoveOccupantResult.Succeeded(next with {
            Reactors = next.Reactors
                           .Select(reactor => reactor.ControlPosition == source ? reactor with { ControlPosition = destination } : reactor)
                           .ToArray()
        });
    }

    private static MoveOccupantResult TryMoveLeak(LevelState level, LeakState leak, GridPosition destination)
    {
        if (leak.Carrier is ECarrierType.Fuel or ECarrierType.Coolant)
        {
            if (!level.IsFloor(destination))
                return MoveOccupantResult.Failed(level, "Fuel and coolant leaks must move to a floor cell.");

            var next = ClearLeak(level, leak)
                .SetUnderground(leak.UndergroundPosition, leak.Carrier, new());
            return MoveOccupantResult.Succeeded(SetLeak(next, destination, destination, leak.Carrier));
        }

        if (leak.Carrier == ECarrierType.Electricity)
        {
            if (!level.IsFloor(destination))
                return MoveOccupantResult.Failed(level, "Electric leak destination must be an adjacent floor access cell.");

            var undergroundPosition = leak.UndergroundPosition;
            if (undergroundPosition.ManhattanDistance(destination) != 1)
                return MoveOccupantResult.Failed(level, "Electric leak destination must stay adjacent to its underground wall cell.");

            return MoveOccupantResult.Succeeded(SetLeak(ClearLeak(level, leak), undergroundPosition, destination, leak.Carrier));
        }

        return MoveOccupantResult.Failed(level, "Unsupported leak carrier.");
    }

    private static LevelState ClearLeak(LevelState level, LeakState leak)
    {
        return level with {
            Leaks = level.Leaks.Where(candidate => candidate != leak).ToArray()
        };
    }

    public static LevelState CycleElectricityLeakAccess(LevelState level, GridPosition undergroundPosition)
    {
        if (!level.InBounds(undergroundPosition))
            return level;

        if (!level.GetUnderground(undergroundPosition, ECarrierType.Electricity).IsPresent)
            return level;

        var accessPositions = undergroundPosition.Neighbors().Where(level.IsFloor).ToArray();
        if (accessPositions.Length == 0)
            return level;

        var existingLeak = level.Leaks.FirstOrDefault(leak => leak.Carrier == ECarrierType.Electricity && leak.UndergroundPosition == undergroundPosition);
        var nextAccessPosition = accessPositions[0];
        if (existingLeak is not null)
        {
            var index = Array.IndexOf(accessPositions, existingLeak.AccessPosition);
            nextAccessPosition = accessPositions[(index + 1) % accessPositions.Length];
        }

        return SetLeak(level, undergroundPosition, nextAccessPosition, ECarrierType.Electricity);
    }

    public static LevelState Apply(LevelState level, GridPosition position, EditorToolCommand command)
    {
        if (!level.InBounds(position))
            return level;

        return command.Tool switch {
            EEditorTool.Cursor => level,
            EEditorTool.Floor => level.SetTerrain(position, ECellTerrain.Floor),
            EEditorTool.Wall => level.SetTerrain(position, ECellTerrain.Wall),
            EEditorTool.Underground => SetUnderground(level, position, command.Carrier),
            EEditorTool.Flow => SetCarrierProp(level, position, EPropType.Flow, command.Carrier),
            EEditorTool.Consumer => SetFloorProp(level, position, new() { Type = EPropType.Consumer, SwitchState = EPropSwitchState.Enabled }),
            EEditorTool.Junction => SetFloorProp(level, position, new() { Type = EPropType.Junction }),
            EEditorTool.Door => ToggleOrSetDoor(level, position),
            EEditorTool.AllSeeingEyeTerminal => SetFloorProp(level, position, new() { Type = EPropType.AllSeeingEyeTerminal }),
            EEditorTool.RemedySupply => SetFloorProp(level, position, new() { Type = EPropType.RemedySupply, RemedyType = command.RemedyType }),
            EEditorTool.ReactorControl => SetReactorControl(level, position),
            EEditorTool.Leak => SetLeak(level, position, command.Carrier),
            EEditorTool.SurfaceHazard => AddSurfaceHazard(level, position, command.Carrier),
            EEditorTool.Heat => level.SetSurface(position, level.GetSurface(position) with { Heat = level.GetSurface(position).Heat + 1 }),
            EEditorTool.Robot => level.IsFloor(position) ? level with { Robot = level.Robot with { Position = position } } : level,
            _ => level
        };
    }

    public static LevelState SetLeak(LevelState level, GridPosition undergroundPosition, GridPosition accessPosition, ECarrierType carrier)
    {
        if (!level.InBounds(undergroundPosition) || !level.IsFloor(accessPosition))
            return level;

        if (carrier is ECarrierType.Fuel or ECarrierType.Coolant && undergroundPosition != accessPosition)
            return level;

        if (carrier == ECarrierType.Electricity && undergroundPosition.ManhattanDistance(accessPosition) != 1)
            return level;

        var next = level.SetUnderground(undergroundPosition, carrier, new() {
            State = EUndergroundState.Leaking,
            StructuralIntegrity = Balancing.Current.StructuralIntegrityLeakThreshold
        });
        return next with {
            Leaks = [
                .. next.Leaks.Where(leak => leak.UndergroundPosition != undergroundPosition || leak.Carrier != carrier),
                new() {
                    Carrier = carrier,
                    UndergroundPosition = undergroundPosition,
                    AccessPosition = accessPosition
                }
            ]
        };
    }

    private static LevelState SetUnderground(LevelState level, GridPosition position, ECarrierType carrier)
    {
        return level.SetUnderground(position, carrier, new() {
            State = EUndergroundState.Intact,
            StructuralIntegrity = Balancing.Current.MaxStructuralIntegrity
        });
    }

    private static LevelState SetCarrierProp(LevelState level, GridPosition position, EPropType type, ECarrierType carrier)
    {
        return SetFloorProp(level, position, new() { Type = type, Carrier = carrier, SwitchState = EPropSwitchState.Enabled });
    }

    private static LevelState AddSurfaceHazard(LevelState level, GridPosition position, ECarrierType carrier)
    {
        var surface = level.GetSurface(position);
        return carrier switch {
            ECarrierType.Fuel => level.SetSurface(position, surface with { Fuel = surface.Fuel + 1 }),
            ECarrierType.Coolant => level.SetSurface(position, surface with { Coolant = surface.Coolant + 1 }),
            ECarrierType.Electricity => level.SetSurface(position, surface with { Electricity = surface.Electricity + 1 }),
            _ => level
        };
    }

    private static LevelState SetFloorProp(LevelState level, GridPosition position, PropState prop)
    {
        return level.IsFloor(position) ? level.SetProp(position, prop) : level;
    }

    private static LevelState ToggleOrSetDoor(LevelState level, GridPosition position)
    {
        if (!level.IsFloor(position))
            return level;

        var prop = level.GetProp(position);
        if (prop.Type == EPropType.Door)
        {
            var nextState = prop.DoorState == EDoorState.Open ? EDoorState.Closed : EDoorState.Open;
            return level.SetProp(position, prop with { DoorState = nextState });
        }

        return level.SetProp(position, new() { Type = EPropType.Door, DoorState = EDoorState.Closed });
    }

    private static LevelState SetReactorControl(LevelState level, GridPosition position)
    {
        if (!level.IsFloor(position))
            return level;

        var id = level.Reactors.Count == 0 ? 1 : level.Reactors.Max(reactor => reactor.ReactorId) + 1;
        var levelWithProp = level.SetProp(position, new() { Type = EPropType.ReactorControl, ReactorId = id });
        return levelWithProp with {
            Reactors = [
                .. level.Reactors,
                new() {
                    ReactorId = id,
                    ControlPosition = position
                }
            ]
        };
    }

    private static LevelState SetLeak(LevelState level, GridPosition position, ECarrierType carrier)
    {
        if (!level.InBounds(position))
            return level;

        return SetLeak(level, position, position, carrier);
    }
}