namespace ReactorMaintenance.Simulation;

public static class LevelEditor
{
    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 => SetCarrierProp(level, position, EPropType.Consumer, command.Carrier),
            EEditorTool.Junction => SetFloorProp(level, position, new() { Type = EPropType.Junction }),
            EEditorTool.Door => SetFloorProp(level, position, new() { Type = EPropType.Door }),
            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 SetDoorEdge(LevelState level, GridPosition a, GridPosition b)
    {
        if (!level.IsFloor(a) || !level.IsFloor(b) || a.ManhattanDistance(b) != 1)
            return level;

        return level.SetProp(a, new() { Type = EPropType.Door }) with {
            Doors = [
                .. level.Doors.Where(door => !SameDoorEdge(door, a, b)),
                new() { A = a, B = b, State = EDoorState.Closed }
            ]
        };
    }

    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 });
        return next with {
            Leaks = [
                .. next.Leaks.Where(leak => leak.UndergroundPosition != undergroundPosition || leak.Carrier != carrier),
                new() {
                    Carrier = carrier,
                    UndergroundPosition = undergroundPosition,
                    AccessPosition = accessPosition
                }
            ]
        };
    }

    public static LevelState BindReactorConsumer(LevelState level, int reactorId, ECarrierType carrier, GridPosition consumerPosition)
    {
        if (!level.InBounds(consumerPosition) || level.GetProp(consumerPosition) is not { Type: EPropType.Consumer } prop || prop.Carrier != carrier)
            return level;

        var reactors = level.Reactors.Select(reactor => reactor.ReactorId == reactorId ? BindConsumer(reactor, carrier, consumerPosition) : reactor).ToArray();
        return level with { Reactors = reactors };
    }

    public static LevelState AddRuleEvent(LevelState level, RuleEventState ruleEvent)
    {
        var id = string.IsNullOrWhiteSpace(ruleEvent.Id) ? NextRuleEventId(level) : ruleEvent.Id;
        var authoredEvent = ruleEvent with { Id = id };
        return level with {
            RuleEvents = [.. level.RuleEvents.Where(existing => existing.Id != id), authoredEvent]
        };
    }

    public static LevelState RemoveRuleEvent(LevelState level, string id)
    {
        return level with { RuleEvents = level.RuleEvents.Where(ruleEvent => ruleEvent.Id != id).ToArray() };
    }

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

    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 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,
                    FuelConsumerPosition = position,
                    CoolantConsumerPosition = position,
                    ElectricityConsumerPosition = position
                }
            ]
        };
    }

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

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

    private static bool SameDoorEdge(DoorState door, GridPosition a, GridPosition b)
    {
        return door.A == a && door.B == b || door.A == b && door.B == a;
    }

    private static ReactorBinding BindConsumer(ReactorBinding reactor, ECarrierType carrier, GridPosition consumerPosition)
    {
        return carrier switch {
            ECarrierType.Fuel => reactor with { FuelConsumerPosition = consumerPosition },
            ECarrierType.Coolant => reactor with { CoolantConsumerPosition = consumerPosition },
            ECarrierType.Electricity => reactor with { ElectricityConsumerPosition = consumerPosition },
            _ => reactor
        };
    }

    private static string NextRuleEventId(LevelState level)
    {
        var next = level.RuleEvents.Count + 1;
        while (level.RuleEvents.Any(ruleEvent => ruleEvent.Id == $"rule-{next}"))
            next++;

        return $"rule-{next}";
    }
}