using GDWeave.Godot;
using GDWeave.Godot.Variants;
using GDWeave.Modding;

namespace BetterFirstPerson;

public class PlayerPatch(BetterFirstPersonMod mod) : IScriptMod
{
    public bool ShouldRun(string path) => path == "res://Scenes/Entities/Player/player.gdc";

    public IEnumerable<Token> Modify(string path, IEnumerable<Token> tokens)
    {
        // patch _process_animation
        var waiterFollow = new MultiTokenWaiter([
            t => t is IdentifierToken{Name: "skeleton"},
            t => t.Type is TokenType.Period,
            t => t is IdentifierToken{Name: "set_bone_custom_pose"},
            t => t.Type is TokenType.ParenthesisOpen,
            t => t is ConstantToken{Value: IntVariant{Value: 1}},
            t => t.Type is TokenType.Comma,
            t => t.Type is TokenType.BuiltInType,
            t => t.Type is TokenType.ParenthesisOpen,
            t => t.Type is TokenType.ParenthesisClose,
            t => t.Type is TokenType.ParenthesisClose,
            t => t.Type is TokenType.Newline,
            t => t.Type is TokenType.Newline,
        ], allowPartialMatch: true);

        // patch animation_data dict
        var waiterAnimationData = new MultiTokenWaiter([
            t => t is ConstantToken{Value: StringVariant{Value: "back_bend"}},
            t => t.Type is TokenType.Colon,
            t => t is ConstantToken{Value: RealVariant{Value: 0.0}},
            t => t.Type is TokenType.Comma,
            t => t.Type is TokenType.Newline,
        ]);

        // patch to _process_movement(delta)
        // move the A and D rotation into an if statement
        var waiterDisableLeftRightAutoRotate = new MultiTokenWaiter([
            t => t is IdentifierToken{Name: "dir"},
            t => t.Type is TokenType.OpAssign,
            t => t is IdentifierToken{Name: "dive_vec"},
            t => t.Type is TokenType.Period,
            t => t is IdentifierToken{Name: "normalized"},
            t => t.Type is TokenType.ParenthesisOpen,
            t => t.Type is TokenType.ParenthesisClose,
            t => t.Type is TokenType.Newline,
            t => t.Type is TokenType.Newline,
            t => t is IdentifierToken{Name: "rot_help"},
        ], allowPartialMatch: true);

        var waiterDisableLeftRightAutoRotate2 = new MultiTokenWaiter([
            t => t is IdentifierToken{Name: "dir"},
            t => t.Type is TokenType.Comma,
            t => t.Type is TokenType.BuiltInType,
            t => t.Type is TokenType.Period,
            t => t is IdentifierToken{Name: "UP"},
            t => t.Type is TokenType.ParenthesisClose,
            t => t.Type is TokenType.Newline,
        ], allowPartialMatch: true);

        // patch to _zoom_update()
        var waiterZoomUpdate = new MultiTokenWaiter([
            t => t.Type is TokenType.PrFunction,
            t => t is IdentifierToken{Name: "_zoom_update"},
            t => t.Type is TokenType.ParenthesisOpen,
            t => t.Type is TokenType.ParenthesisClose,
            t => t.Type is TokenType.Colon,
        ], allowPartialMatch: true);

        foreach (var token in tokens)
        {
            //mod.Logger.Information(token.ToString());

            // man made horrors beyond comprehension
            // proceed with caution

            if (!mod.Config.UseBendingInstead)
            {
                if (waiterAnimationData.Check(token))
                {
                    yield return token;
                    yield return new ConstantToken(new StringVariant("head_rotate"));
                    yield return new Token(TokenType.Colon);
                    yield return new ConstantToken(new RealVariant(0));
                    yield return new Token(TokenType.Comma);
                    yield return new Token(TokenType.Newline, 1);
                    yield return token;
                }
            }

            if (waiterZoomUpdate.Check(token))
            {
                yield return token;
                yield return new Token(TokenType.Newline, 2);
                yield return new IdentifierToken("body");
                yield return new Token(TokenType.Period);
                yield return new IdentifierToken("visible");
                yield return new Token(TokenType.OpAssign);
                yield return new ConstantToken(new BoolVariant(true));
                yield return new Token(TokenType.Newline);
                yield return new Token(TokenType.Newline);
            }
            else if (waiterDisableLeftRightAutoRotate.Check(token))
            {
                yield return new Token(TokenType.Newline, 2);

                // if camera_zoom > 0.5
                yield return new Token(TokenType.CfIf);
                yield return new IdentifierToken("camera_zoom");
                yield return new Token(TokenType.OpGreater);
                yield return new ConstantToken(new RealVariant(0.5));
                yield return new Token(TokenType.Colon);
                yield return new Token(TokenType.Newline, 3);

                yield return token;
            }
            else if (waiterDisableLeftRightAutoRotate2.Check(token))
            {
                yield return new Token(TokenType.Newline, 3);
            }
            else if (waiterFollow.Check(token))
            {
                yield return new Token(TokenType.Newline, 1);

                if (!mod.Config.UseBendingInstead)
                {
                    // if animation_data["head_rotate"] != 0.0:
                    yield return new Token(TokenType.CfIf);
                    yield return new IdentifierToken("animation_data");
                    yield return new Token(TokenType.BracketOpen);
                    yield return new ConstantToken(new StringVariant("head_rotate"));
                    yield return new Token(TokenType.BracketClose);
                    yield return new Token(TokenType.OpNotEqual);
                    yield return new ConstantToken(new RealVariant(0.0));
                    yield return new Token(TokenType.Colon);
                    yield return new Token(TokenType.Newline, 2);

                    // skeleton.set_bone_custom_pose(11, skeleton.get_bone_pose(11).rotated(Vector3(1, 0, 0), animation_data["head_rotate"]))
                    yield return new IdentifierToken("skeleton");
                    yield return new Token(TokenType.Period);
                    yield return new IdentifierToken("set_bone_custom_pose");
                    yield return new Token(TokenType.ParenthesisOpen);
                    yield return new ConstantToken(new IntVariant(11));
                    yield return new Token(TokenType.Comma);
                    yield return new IdentifierToken("skeleton");
                    yield return new Token(TokenType.Period);
                    yield return new IdentifierToken("get_bone_pose");
                    yield return new Token(TokenType.ParenthesisOpen);
                    yield return new ConstantToken(new IntVariant(11));
                    yield return new Token(TokenType.ParenthesisClose);
                    yield return new Token(TokenType.Period);
                    yield return new IdentifierToken("rotated");
                    yield return new Token(TokenType.ParenthesisOpen);
                    yield return new Token(TokenType.BuiltInType, 7);
                    yield return new Token(TokenType.ParenthesisOpen);
                    yield return new ConstantToken(new IntVariant(1));
                    yield return new Token(TokenType.Comma);
                    yield return new ConstantToken(new IntVariant(0));
                    yield return new Token(TokenType.Comma);
                    yield return new ConstantToken(new IntVariant(0));
                    yield return new Token(TokenType.ParenthesisClose);
                    yield return new Token(TokenType.Comma);
                    yield return new IdentifierToken("animation_data");
                    yield return new Token(TokenType.BracketOpen);
                    yield return new ConstantToken(new StringVariant("head_rotate"));
                    yield return new Token(TokenType.BracketClose);
                    yield return new Token(TokenType.ParenthesisClose);
                    yield return new Token(TokenType.ParenthesisClose);
                    yield return new Token(TokenType.Newline, 2);
                    yield return new Token(TokenType.Newline, 1);

                    // else:
                    yield return new Token(TokenType.CfElse);
                    yield return new Token(TokenType.Colon);
                    yield return new Token(TokenType.Newline, 2);

                    // skeleton.set_bone_custom_pose(11, Transform())
                    yield return new IdentifierToken("skeleton");
                    yield return new Token(TokenType.Period);
                    yield return new IdentifierToken("set_bone_custom_pose");
                    yield return new Token(TokenType.ParenthesisOpen);
                    yield return new ConstantToken(new IntVariant(11));
                    yield return new Token(TokenType.Comma);
                    yield return new Token(TokenType.BuiltInType, 13);
                    yield return new Token(TokenType.ParenthesisOpen);
                    yield return new Token(TokenType.ParenthesisClose);
                    yield return new Token(TokenType.ParenthesisClose);
                    yield return new Token(TokenType.Newline, 2);
                    yield return new Token(TokenType.Newline, 1);
                }

                // if camera_zoom < 0.5 and not freecamming:
                yield return new Token(TokenType.CfIf);
                yield return new IdentifierToken("camera_zoom");
                yield return new Token(TokenType.OpLess);
                yield return new ConstantToken(new RealVariant(0.5));
                yield return new Token(TokenType.OpAnd);
                yield return new Token(TokenType.OpNot);
                yield return new IdentifierToken("freecamming");
                yield return new Token(TokenType.Colon);
                yield return new Token(TokenType.Newline, 2);

                // $body / player_body / Armature / Skeleton / face.visible = false
                yield return new Token(TokenType.Dollar);
                yield return new IdentifierToken("body");
                yield return new Token(TokenType.OpDiv);
                yield return new IdentifierToken("player_body");
                yield return new Token(TokenType.OpDiv);
                yield return new IdentifierToken("Armature");
                yield return new Token(TokenType.OpDiv);
                yield return new IdentifierToken("Skeleton");
                yield return new Token(TokenType.OpDiv);
                yield return new IdentifierToken("face");
                yield return new Token(TokenType.Period);
                yield return new IdentifierToken("visible");
                yield return new Token(TokenType.OpAssign);
                yield return new ConstantToken(new BoolVariant(false));
                yield return new Token(TokenType.Newline, 2);

                // if state != STATES.FISHING and state != STATES.FISHING_STRUGGLE ...:
                yield return new Token(TokenType.CfIf);
                yield return new IdentifierToken("state");
                yield return new Token(TokenType.OpNotEqual);
                yield return new ConstantToken(new IntVariant(1)); // 1 - BUSY
                yield return new Token(TokenType.OpAnd);
                yield return new IdentifierToken("state");
                yield return new Token(TokenType.OpNotEqual);
                yield return new ConstantToken(new IntVariant(6)); // 6 - FISHING_CAST
                yield return new Token(TokenType.OpAnd);
                yield return new IdentifierToken("state");
                yield return new Token(TokenType.OpNotEqual);
                yield return new ConstantToken(new IntVariant(7)); // 7 - FISHING
                yield return new Token(TokenType.OpAnd);
                yield return new IdentifierToken("state");
                yield return new Token(TokenType.OpNotEqual);
                yield return new ConstantToken(new IntVariant(9)); // 9 - FISHING_STRUGGLE
                yield return new Token(TokenType.OpAnd);
                yield return new IdentifierToken("state");
                yield return new Token(TokenType.OpNotEqual);
                yield return new ConstantToken(new IntVariant(10)); // 10 - SHOVEL_CAST
                yield return new Token(TokenType.OpAnd);
                yield return new IdentifierToken("state");
                yield return new Token(TokenType.OpNotEqual);
                yield return new ConstantToken(new IntVariant(11)); // 11 - SHOVEL_STRUGGLE
                yield return new Token(TokenType.OpAnd);
                yield return new IdentifierToken("state");
                yield return new Token(TokenType.OpNotEqual);
                yield return new ConstantToken(new IntVariant(16)); // 16 - SHOVEL_STRUGGLE?
                yield return new Token(TokenType.Colon);
                yield return new Token(TokenType.Newline, 3);

                // self.rotation.y = camera.rotation.y
                yield return new Token(TokenType.Self);
                yield return new Token(TokenType.Period);
                yield return new IdentifierToken("rotation");
                yield return new Token(TokenType.Period);
                yield return new IdentifierToken("y");
                yield return new Token(TokenType.OpAssign);
                yield return new IdentifierToken("camera");
                yield return new Token(TokenType.Period);
                yield return new IdentifierToken("rotation");
                yield return new Token(TokenType.Period);
                yield return new IdentifierToken("y");
                yield return new Token(TokenType.Newline, 3);

                if (!mod.Config.UseBendingInstead)
                {
                    // var head_rotate = clamp(camera.rotation.x * 54, -65.0, 45.0)
                    yield return new Token(TokenType.PrVar);
                    yield return new IdentifierToken("head_rotate");
                    yield return new Token(TokenType.OpAssign);
                    yield return new Token(TokenType.BuiltInFunc, 53);
                    yield return new Token(TokenType.ParenthesisOpen);
                    yield return new IdentifierToken("camera");
                    yield return new Token(TokenType.Period);
                    yield return new IdentifierToken("rotation");
                    yield return new Token(TokenType.Period);
                    yield return new IdentifierToken("x");
                    yield return new Token(TokenType.OpMul);
                    yield return new ConstantToken(new IntVariant(54));
                    yield return new Token(TokenType.Comma);
                    yield return new ConstantToken(new RealVariant(-65.0));
                    yield return new Token(TokenType.Comma);
                    yield return new ConstantToken(new RealVariant(45.0));
                    yield return new Token(TokenType.ParenthesisClose);
                    yield return new Token(TokenType.Newline, 3);

                    // animation_data["head_rotate"] = deg2rad(-head_rotate)
                    yield return new IdentifierToken("animation_data");
                    yield return new Token(TokenType.BracketOpen);
                    yield return new ConstantToken(new StringVariant("head_rotate"));
                    yield return new Token(TokenType.BracketClose);
                    yield return new Token(TokenType.OpAssign);
                    yield return new Token(TokenType.BuiltInFunc, 43);
                    yield return new Token(TokenType.ParenthesisOpen);
                    yield return new Token(TokenType.OpSub);
                    yield return new IdentifierToken("head_rotate");
                    yield return new Token(TokenType.ParenthesisClose);
                    yield return new Token(TokenType.Newline, 1);
                }
                else
                {
                    // var back_bend = clamp(camera.rotation.x * 54, -50.0, 50.0)
                    yield return new Token(TokenType.PrVar);
                    yield return new IdentifierToken("back_bend");
                    yield return new Token(TokenType.OpAssign);
                    yield return new Token(TokenType.BuiltInFunc, 53);
                    yield return new Token(TokenType.ParenthesisOpen);
                    yield return new IdentifierToken("camera");
                    yield return new Token(TokenType.Period);
                    yield return new IdentifierToken("rotation");
                    yield return new Token(TokenType.Period);
                    yield return new IdentifierToken("x");
                    yield return new Token(TokenType.OpMul);
                    yield return new ConstantToken(new IntVariant(54));
                    yield return new Token(TokenType.Comma);
                    yield return new ConstantToken(new RealVariant(-50.0));
                    yield return new Token(TokenType.Comma);
                    yield return new ConstantToken(new RealVariant(50.0));
                    yield return new Token(TokenType.ParenthesisClose);
                    yield return new Token(TokenType.Newline, 3);

                    // animation_data["back_bend"] = deg2rad( - back_bend)
                    yield return new IdentifierToken("animation_data");
                    yield return new Token(TokenType.BracketOpen);
                    yield return new ConstantToken(new StringVariant("back_bend"));
                    yield return new Token(TokenType.BracketClose);
                    yield return new Token(TokenType.OpAssign);
                    yield return new Token(TokenType.BuiltInFunc, 43);
                    yield return new Token(TokenType.ParenthesisOpen);
                    yield return new Token(TokenType.OpSub);
                    yield return new IdentifierToken("back_bend");
                    yield return new Token(TokenType.ParenthesisClose);
                    yield return new Token(TokenType.Newline, 1);
                }

                // else:
                yield return new Token(TokenType.CfElse);
                yield return new Token(TokenType.Colon);
                yield return new Token(TokenType.Newline, 2);

                // $body / player_body / Armature / Skeleton / face.visible = true
                yield return new Token(TokenType.Dollar);
                yield return new IdentifierToken("body");
                yield return new Token(TokenType.OpDiv);
                yield return new IdentifierToken("player_body");
                yield return new Token(TokenType.OpDiv);
                yield return new IdentifierToken("Armature");
                yield return new Token(TokenType.OpDiv);
                yield return new IdentifierToken("Skeleton");
                yield return new Token(TokenType.OpDiv);
                yield return new IdentifierToken("face");
                yield return new Token(TokenType.Period);
                yield return new IdentifierToken("visible");
                yield return new Token(TokenType.OpAssign);
                yield return new ConstantToken(new BoolVariant(true));
                yield return new Token(TokenType.Newline, 2);

                // if not freecamming:
                yield return new Token(TokenType.CfIf);
                yield return new Token(TokenType.OpNot);
                yield return new IdentifierToken("freecamming");
                yield return new Token(TokenType.Colon);
                yield return new Token(TokenType.Newline, 3);

                var animationType = mod.Config.UseBendingInstead ? "back_bend" : "head_rotate";

                // if animation_data[{animationType}] != 0.0:
                yield return new Token(TokenType.CfIf);
                yield return new IdentifierToken("animation_data");
                yield return new Token(TokenType.BracketOpen);
                yield return new ConstantToken(new StringVariant(animationType));
                yield return new Token(TokenType.BracketClose);
                yield return new Token(TokenType.OpNotEqual);
                yield return new ConstantToken(new RealVariant(0.0));
                yield return new Token(TokenType.Colon);
                yield return new Token(TokenType.Newline, 4);

                // animation_data[{animationType}] = 0.0
                yield return new IdentifierToken("animation_data");
                yield return new Token(TokenType.BracketOpen);
                yield return new ConstantToken(new StringVariant(animationType));
                yield return new Token(TokenType.BracketClose);
                yield return new Token(TokenType.OpAssign);
                yield return new ConstantToken(new RealVariant(0.0));
                yield return new Token(TokenType.Newline, 1);
                yield return token;
            }
            else
            {
                yield return token;
            }
        }
    }
}