using System;
using UnityEngine;

namespace Marro.PacManUdon
{
    enum PacManGhostType
    {
        Caught,
        Scared,
        ScaredWhite,
        Blinky,
        Pinky,
        Inky,
        Clyde,
        Special,
    }

    enum PacManGhostAnimatorState
    {
        Normal,
        Caught,
        Scared,
        ScaredWhite,
        Special
    }

    public enum PacManGhostState
    {
        Normal,
        CaughtScore,
        Returning,
        Entering,
        Home,
        Exiting,
        WaitingForStart,
    }

    public enum PacManGhostStartState
    {
        Outside,
        TargetingIdlePosition1,
        TargetingIdlePosition2
    }

    [RequireComponent(typeof(Renderer))]
    [RequireComponent(typeof(Animator))]
    public class Ghost : GridMover
    {
        [SerializeField] private PacManGhostType ghostType;
        [SerializeField] private PacManGhostStartState startState;
        [SerializeField] private GameObject targetIndicator;

        // External references
        private GhostManager ghostManager;
        private CollisionManager collisionManager;
        private Animator animator;
        private new Renderer renderer;
        private PacMan pacMan;
        private Ghost blinky;
        private ScoreBonusDisplay scoreBonusDisplay;

        private Vector2 startPosition;
        private Vector2 homePosition;
        private Vector2 idlePosition1;
        private Vector2 idlePosition2;
        private Vector2 cornerPosition;

        // Pathfinding
        private Vector2 target;
        private bool horizontalOnly;
        private bool inTunnel;
        private int rngState;
        private bool turnAroundSoon;

        private float speed;

        // State
        private PacManGhostState ghostState;
        private bool isScared;
        private bool whiteScared;
        private bool scattering;
        private PacManGhostFrozenState frozenState;

        // Home
        private bool offGrid;
        private int housePelletCounter;
        private bool housePelletCounterActive;
        private int housePelletCounterLimit;

        // Cutscene
        private bool kinematic;
        private bool specialLook;

        private bool followingPredefinedPath;
        private Direction[] predefinedPath;
        private int predefinedPathIndex;

        public bool IsScared => isScared;
        public int Index { get; private set; }

        private readonly int animatorKeyDirection = Animator.StringToHash("Direction");
        private readonly int animatorKeyGhostType = Animator.StringToHash("GhostType");

        public void Initialize(CollisionManager collisionManager, PacMan pacMan, Ghost blinky, Transform startTransform, Vector2 homePosition, Vector2 idlePosition1, Vector2 idlePosition2, Vector2 cornerPosition, int index)
        {
            ghostManager = transform.parent.GetComponent<GhostManager>();
            animator = GetComponent<Animator>();
            renderer = GetComponent<Renderer>();

            this.collisionManager = collisionManager;
            this.pacMan = pacMan;
            this.blinky = blinky;
            this.homePosition = homePosition;
            this.idlePosition1 = idlePosition1;
            this.idlePosition2 = idlePosition2;
            this.cornerPosition = cornerPosition;

            scoreBonusDisplay = transform.Find("ScoreBonusDisplay").gameObject.GetComponent<ScoreBonusDisplay>();
            scoreBonusDisplay.Initialize();
            startPosition = startTransform.localPosition;

            frozenState = PacManGhostFrozenState.Frozen;

            Index = index;

            targetIndicator.transform.parent = transform.parent;
        }

        public void Reset()
        {
            // Debug.Log($"{gameObject} Reset!");
            SetPosition(startPosition);

            offGrid = true;
            isScared = false;
            inTunnel = false;
            kinematic = false;
            followingPredefinedPath = false;
            turnAroundSoon = false;
            specialLook = false;
            rngState = 1;

            ghostState = PacManGhostState.WaitingForStart;

            switch (startState)
            {
                case PacManGhostStartState.TargetingIdlePosition1:
                    SetTargetDirection(Direction.Up);
                    break;
                case PacManGhostStartState.TargetingIdlePosition2:
                    SetTargetDirection(Direction.Down);
                    break;
                default:
                    SetTargetDirection(Direction.Left);
                    break;
            }

            UpdateSpeed();

            // Debug.Log($"{gameObject} reset with state: {state}, target: {target}, offGrid: {offGrid}");
        }

        public override void SyncedUpdate()
        {
            if (frozenState == PacManGhostFrozenState.Frozen ||
                (frozenState == PacManGhostFrozenState.FrozenIfNotCaught && ghostState != PacManGhostState.Returning && ghostState != PacManGhostState.Entering))
            {
                return;
            }

            Vector2 position = GetPosition();
            Vector2 nextPosition = GetNextPosition(position, directionVectors[(int)direction], speed, networkManager.SyncedDeltaTime);

            nextPosition = ProcessNextPosition(position, nextPosition);

            SetPosition(nextPosition);
        }

        private Vector2 ProcessNextPosition(Vector2 position, Vector2 nextPosition)
        {
            if (turnAroundSoon && ghostState == PacManGhostState.Normal
                && CrossesTileCenter(position, nextPosition, direction))
            {
                var newDirection = GetInverseDirection(direction);
                SetDirectionAndTargetDirection(newDirection);
                turnAroundSoon = false;
                return nextPosition;
            }

            if (kinematic)
            {
                return nextPosition;
            }

            if (ghostState == PacManGhostState.WaitingForStart)
            {
                switch (startState)
                {
                    case PacManGhostStartState.TargetingIdlePosition1:
                        SetOffGridTarget(idlePosition1, false);
                        ghostState = PacManGhostState.Entering;
                        break;
                    case PacManGhostStartState.TargetingIdlePosition2:
                        SetOffGridTarget(idlePosition2, false);
                        ghostState = PacManGhostState.Entering;
                        break;
                    default:
                        ghostState = PacManGhostState.Exiting;
                        break;
                }
                OffGridTargetReached();
            }

            if (offGrid || ghostState == PacManGhostState.Returning)
            {
                PerformOffgridRelatedMovement(position, ref nextPosition);
            }

            if (!offGrid)
            {
                if (followingPredefinedPath)
                {
                    ProcessPredefinedPath(position, ref nextPosition);
                }
                else if (CrossesTileCenter(position, nextPosition, direction))
                {
                    if (targetDirection != direction)
                    {
                        ApplyTargetDirection(position, out nextPosition);
                    }
                    else
                    {
                        TryToQueueTurn(position);
                    }
                }
            }
            
            if (CrossesTileBorder(position, nextPosition, direction))
            {
                var inTunnel = collisionManager.GhostMoveToTile(nextPosition, Index);
                
                if (inTunnel != this.inTunnel)
                {
                    this.inTunnel = inTunnel;
                    UpdateSpeed();
                }
            }

            return nextPosition;
        }

        private Vector2 PerformOffgridRelatedMovement(Vector2 position, ref Vector2 nextPosition)
        {
            bool XAxisAlligned = CheckAndAllignToTargetX(position, nextPosition, target);
            bool YAxisAlligned = CheckAndAllignToTargetY(position, nextPosition, target);
            if (offGrid)
            {
                if (XAxisAlligned)
                {
                    nextPosition.x = target.x;
                }
                if (YAxisAlligned)
                {
                    nextPosition.y = target.y;
                }
            }
            if (XAxisAlligned && YAxisAlligned)
            {
                if (!offGrid)
                {
                    nextPosition = target;
                }
                OffGridTargetReached();
                // Debug.Log($"{gameObject} reached offGridTarget {target} at position {position}, new state {state}, offGrid is now {offGrid}, new direction: {direction}");
            }
            if ((XAxisAlligned || YAxisAlligned) && offGrid)
            {
                SetDirectionAndTargetDirection(GetOffGridDirectionToTarget(nextPosition, target, direction));
                // Debug.Log($"{gameObject} Alligned X Axis: {XAxisAlligned}, Y Axis: {YAxisAlligned} with position: {position}, new nextPosition: {nextPosition}, new target: {target}, now moving in direction {direction}");
                // nextPosition = GridMover.GetNextPosition(position, direction, speed);
            }

            return nextPosition;
        }

        private void TryToQueueTurn(Vector2 position)
        {
            var upcomingGridPosition = PositionToGrid(position + directionVectors[(int)direction]);
            var availableDirections = collisionManager.GetAvailableDirections(upcomingGridPosition);

            if ((availableDirections & (int)PacManCollisionInfoType.NoTurn) != 0)
            {
                return;
            }

            if (!isScared && (availableDirections & (int)PacManCollisionInfoType.HorizontalOnly) != 0)
            {
                availableDirections &= 0b1100;
            }

            availableDirections &= ~(int)GetInverseDirection(direction) & 0b1111; // Not allowed to turn around, also filter flags other than direction

            if (availableDirections == 0)
            {
                return;
            }

            Direction newDirection;
            if (isSingleBitSet[availableDirections])
            {
                newDirection = (Direction)availableDirections;
            }
            else
            {
                target = GetGridTarget(upcomingGridPosition);
                targetIndicator.transform.localPosition = target;
                newDirection = GetGridDirectionToTargetGreedy(availableDirections, upcomingGridPosition, target);
            }

            if (newDirection == direction)
            {
                return;
            }

            SetTargetDirection(newDirection);
        }

        // Static fields are not yet supported on user-defined types
        private readonly bool[] isSingleBitSet = new bool[]
        {
            //0000,0001, 0010, 0011 , 0100, 0101 , 0110 , 0111 , 1000, 1001 , 1010 , 1011 , 1100 , 1101 , 1110 , 1111
            false, true, true, false, true, false, false, false, true, false, false, false, false, false, false, false
        };

        private void ApplyTargetDirection(Vector2 position, out Vector2 nextPosition)
        {
            var gridPosition = PositionToGrid(position);
            nextPosition = GetNextPosition(gridPosition, directionVectors[(int)targetDirection], speed, networkManager.SyncedDeltaTime);
            SetDirection(targetDirection);
        }

        private void ProcessPredefinedPath(Vector2 position, ref Vector2 nextPosition)
        {
            if (CrossesTileCenter(position, nextPosition, direction))
            {
                // Find the next valid direction which isn't Vector2.zero
                int nextValidDirectionIndex = predefinedPathIndex;
                while (predefinedPath[nextValidDirectionIndex] == Direction.Zero)
                {
                    nextValidDirectionIndex += 1;
                }
                if (!collisionManager.IsWallUpcoming(nextPosition, directionVectors[(int)predefinedPath[nextValidDirectionIndex]]))
                {
                    // If we're at a Vector2.zero, we skip applying the direction and only increment.
                    if (nextValidDirectionIndex == predefinedPathIndex)
                    {
                        SetDirectionAndTargetDirection(predefinedPath[nextValidDirectionIndex]);
                        nextPosition = PositionToGrid(nextPosition) + directionVectors[(int)direction] * 0.01f;

                        // Check if we've reached the end of the path, which includes making sure the path doesn't end on Vector2.zero
                        do
                        {
                            nextValidDirectionIndex += 1;
                            if (nextValidDirectionIndex >= predefinedPath.Length)
                            {
                                followingPredefinedPath = false;
                                break;
                            }
                        } while (predefinedPath[nextValidDirectionIndex] == Direction.Zero);
                    }

                    // ghostManager.gameStateManager.statusDisplay.SetDebugText(1, predefinedPathIndex.ToString());

                    predefinedPathIndex++;
                }
            }
        }

        private Vector2 GetGridTarget(Vector2 gridPosition)
        {
            if (isScared)
            {
                return gridPosition + directionVectors[(int)cardinalDirections[PseudoRNG() % 4]];
            }

            if (ghostState == PacManGhostState.Returning)
            {
                return homePosition;
            }

            if (scattering)
            {
                return cornerPosition;
            }

            switch (ghostType)
            {
                default:
                    return gridPosition;
                case PacManGhostType.Blinky: // Chase PacMan directly
                    return PositionToGrid(pacMan.GetPosition());
                case PacManGhostType.Pinky: // Try to get ahead of PacMan
                    return GetTargetAheadOfPacMan(4);
                case PacManGhostType.Inky: // Try to attack from the opposite side of Blinky
                    var blinkyPosition = PositionToGrid(blinky.GetPosition());
                    return ((GetTargetAheadOfPacMan(2) - blinkyPosition) * 2) + blinkyPosition;
                case PacManGhostType.Clyde: // Chase PacMan, but retreat to corner if PacMan gets to close
                    var pacManPosition = PositionToGrid(pacMan.GetPosition());
                    if (Vector2.Distance(gridPosition, pacManPosition) < 8)
                    {
                        return cornerPosition;
                    }
                    return pacManPosition;
            }
        }

        private Vector2 GetTargetAheadOfPacMan(int tilesInFront)
        {
            var direction = pacMan.GetTargetDirection();
            var result = PositionToGrid(pacMan.GetPosition()) + directionVectors[(int)direction] * tilesInFront;

            if (direction == Direction.Up) // Reproducing a bug in the original game
            {
                result.x -= tilesInFront;
            }

            return result;
        }

        bool CheckAndAllignToTargetX(Vector2 currentPosition, Vector2 nextPosition, Vector2 target)
        {
            return (currentPosition.x - target.x) * (nextPosition.x - target.x) <= 0.01;
        }

        bool CheckAndAllignToTargetY(Vector2 currentPosition, Vector2 nextPosition, Vector2 target)
        {
            return (currentPosition.y - target.y) * (nextPosition.y - target.y) <= 0.01;
        }

        void OffGridTargetReached()
        {
            switch (ghostState)
            {
                case PacManGhostState.Returning:
                    offGrid = true;
                    SetState(PacManGhostState.Entering);
                    SetOffGridTarget(idlePosition2, false);
                    break;
                case PacManGhostState.Entering:
                    offGrid = true;
                    SetState(PacManGhostState.Home);
                    ghostManager.GhostReturned();
                    if (!target.Equals(idlePosition1))
                    { // This is idlePosition1 if PacManGhostStartState == TargetingIdlePosition1
                        SetOffGridTarget(idlePosition2, false);
                    }
                    TryToExit();
                    break;
                case PacManGhostState.Home:
                    if (target.Equals(idlePosition1))
                    {
                        SetOffGridTarget(idlePosition2, false);
                    }
                    else
                    {
                        SetOffGridTarget(idlePosition1, false);
                    }
                    break;
                case PacManGhostState.Exiting:
                    offGrid = false;
                    SetState(PacManGhostState.Normal);
                    SetDirectionAndTargetDirection(Direction.Left);
                    break;
            }
        }

        void SetOffGridTarget(Vector2 newTarget, bool startHorizontal)
        {
            if (startHorizontal)
            {
                SetDirectionAndTargetDirection(GetOffGridDirectionToTarget(GetPosition(), newTarget, Direction.Right));
            }
            else
            {
                SetDirectionAndTargetDirection(GetOffGridDirectionToTarget(GetPosition(), newTarget, Direction.Down));
            }
            // Debug.Log($"{gameObject} SetOffGridTarget with position {GetPosition()}, newTarget {newTarget}, startHorizontal {startHorizontal} resulted in direction {direction}");
            target = newTarget;
        }

        public int PseudoRNG()
        {
            rngState ^= rngState << 13;
            rngState ^= rngState >> 17;
            rngState ^= rngState << 5;
            return Math.Abs(rngState);
        }

        private readonly Direction[] cardinalDirections = new Direction[] { Direction.Up, Direction.Left, Direction.Down, Direction.Right };
        Direction GetGridDirectionToTargetGreedy(int availableDirections, Vector2 gridPosition, Vector2 targetGridPosition)
        {
            Direction bestDirection = Direction.Zero;
            float bestDistance = float.MaxValue;

            foreach (var direction in cardinalDirections)
            {
                if (((int)direction & availableDirections) == 0)
                {
                    continue;
                }

                // Debug.Log("Evaluating direction " + direction);
                float distance = Vector2.Distance(gridPosition + directionVectors[(int)direction], targetGridPosition);
                if (distance < bestDistance)
                {
                    bestDistance = distance;
                    bestDirection = direction;
                }
            }
            //Debug.Log($"{gameObject.name} Closest next tile is in direction {bestDirection}");
            return bestDirection;
        }

        private Direction GetOffGridDirectionToTarget(Vector2 position, Vector2 target, Direction direction)
        {
            if ((IsHorizontal(direction) && position.x != target.x) || position.y == target.y)
            {
                // Debug.Log($"{gameObject} getOffGridDirectionToTarget with position {position}, target {target} and direction {direction} gives movement on the X axis in direction {new Vector2(target.x-position.x, 0).normalized}");
                return HorizontalToDirection(target.x - position.x);
            }
            else
            {
                // Debug.Log($"{gameObject} getOffGridDirectionToTarget with position {position}, target {target} and direction {direction} gives movement on the Y axis in direction {new Vector2(0, target.y-position.y).normalized}");
                return VerticalToDirection(target.y - position.y);
            }
        }

        protected override void UpdateAnimator()
        {
            if (!gameObject.activeInHierarchy)
                return;

            if (frozenState == PacManGhostFrozenState.FrozenIfNotCaught) // Looks like a bug but matches the original game
            {
                return;
            }

            if (specialLook || targetDirection != Direction.Zero)
            {
                SetAnimatorDirection((int)targetDirection);
            }

            PacManGhostType ghostType = this.ghostType;

            if (isScared)
            {
                ghostType = whiteScared ? PacManGhostType.ScaredWhite : PacManGhostType.Scared;
            }
            else if (ghostState == PacManGhostState.Returning || ghostState == PacManGhostState.Entering)
            {
                ghostType = PacManGhostType.Caught;
            }
            else if (specialLook)
            {
                ghostType = PacManGhostType.Special;
            }

            SetAnimatorGhostType((int)ghostType);
        }

        // A Udon bug means converting an enum to a float causes an exception unless the conversion from int to float is in a separate method
        private void SetAnimatorDirection(int value) =>
            animator.SetFloat(animatorKeyDirection, value);

        private void SetAnimatorGhostType(int value) =>
            animator.SetFloat(animatorKeyGhostType, value);

        public void UpdateSpeed()
        {
            speed = ghostManager.GetTargetSpeed(this, ghostState, isScared, inTunnel);
            // Debug.Log($"Ghost with type {ghostType} updated speed to {speed}, ghostState: {ghostState}, isScared: {isScared}, inTunnel: {inTunnel}, elroyLevel: {ghostManager.elroyLevel}");
        }

        public void ResetHousePelletCounter()
        {
            housePelletCounter = 0;
        }

        public void IncrementHousePelletCounter()
        {
            housePelletCounter++;
            TryToExit();
        }

        void TryToExit()
        {
            if ((housePelletCounterActive && housePelletCounter >= housePelletCounterLimit) || startState == PacManGhostStartState.Outside)
            {
                Release();
            }
        }

        internal bool HitPacMan()
        {
            if (ghostState != PacManGhostState.Normal)
            {
                return false;
            }

            if (isScared)
            {
                //Debug.Log($"{gameObject} was cought!");
                ghostManager.GhostCaught(this);
                return true;
            }
            
            //Debug.Log($"{gameObject} cought PacMan!");
            ghostManager.CapturedPacMan();
            return true;
        }

        public void Caught(int scoreBonus)
        {
            isScared = false;
            // direction = direction * -1;
            target = homePosition;
            scoreBonusDisplay.Display(scoreBonus);
            SetVisibility(false);
            SetState(PacManGhostState.CaughtScore);
        }

        public void ReturnHome()
        {
            SetVisibility(true);
            scoreBonusDisplay.Hide();
            SetState(PacManGhostState.Returning);
        }

        public void BecomeScared()
        {
            if (ghostState == PacManGhostState.Returning || ghostState == PacManGhostState.Entering)
            {
                return;
            }

            if (ghostState == PacManGhostState.Normal || ghostState == PacManGhostState.Home || ghostState == PacManGhostState.Exiting)
            {
                turnAroundSoon = true;
            }

            SetScared(true);
        }

        public void CalmDown()
        {
            if (isScared)
            {
                SetScared(false);
            }
        }

        public void Release()
        {
            // Debug.Log($"{gameObject} was released.");
            if (ghostState == PacManGhostState.Home)
            {
                // Debug.Log($"{gameObject} released.");
                SetOffGridTarget(homePosition, true);
                SetState(PacManGhostState.Exiting);
            }
        }

        public void SetState(PacManGhostState state)
        {
            ghostState = state;
            UpdateAnimator();
            UpdateSpeed();
        }

        private void SetScared(bool scared)
        {
            isScared = scared;
            SetWhite(false);
            UpdateSpeed();
        }

        public void SetScattering(bool scattering, bool reverseDirection = true)
        {
            if (reverseDirection && this.scattering != scattering)
            {
                if (ghostState == PacManGhostState.Normal || ghostState == PacManGhostState.Home || ghostState == PacManGhostState.Exiting
                    // This is afaik not normal PacMan behaviour, but is needed to accomidate slight timing differences during the demo
                    || ghostState == PacManGhostState.Entering && ghostManager.gameController.GameState == PacManGameState.AttractModeDemo 
                )
                {
                    turnAroundSoon = true;
                }
            }
            this.scattering = scattering;
            UpdateAnimator();
        }

        public void SetFrozen(bool frozen, bool ignoreIfCaught = false, bool keepAnimating = false)
        {
            animator.speed = frozen && !keepAnimating ? 0 : 1; // This would cause issues if the returning sprite was animated, luckily it isn't :)

            if (frozen && !ignoreIfCaught)
            {
                frozenState = PacManGhostFrozenState.Frozen;
                return;
            }
            
            if (frozen && ignoreIfCaught)
            {
                frozenState = PacManGhostFrozenState.FrozenIfNotCaught;
                return;
            }

            var oldFrozenState = frozenState;
            frozenState = PacManGhostFrozenState.NotFrozen;

            if (oldFrozenState == PacManGhostFrozenState.FrozenIfNotCaught) // Catch animator up after not updating during FrozenIfNotCaught
            {
                UpdateAnimator();
            }

            if (ghostState == PacManGhostState.CaughtScore)
            {
                ReturnHome(); // Return home when unfreezing after being caught
            }
        }

        public void SetHousePelletCounterActive(bool active)
        {
            housePelletCounterActive = active;
        }

        public void SetHousePelletCounterLimit(int limit)
        {
            housePelletCounterLimit = limit;
        }

        public void SetWhite(bool white)
        {
            whiteScared = white;
            UpdateAnimator();
        }

        public void SetElroy(int elroyLevel)
        {
            // Debug.Log($"{gameObject} became Elroy level {elroyLevel}");
            if (elroyLevel > 0)
            {
                SetScattering(false, reverseDirection: false);
            }
            UpdateSpeed();
        }

        public void SetActive(bool active)
        {
            gameObject.SetActive(active);
            renderer.enabled = active;
            if (active)
            {
                UpdateAnimator();
            }
        }

        public void SetKinematic(bool kinematic)
        {
            this.kinematic = kinematic;
        }

        public void SetPredefinedPath(Direction[] predefinedPath)
        {
            this.predefinedPath = predefinedPath;
            followingPredefinedPath = true;
            predefinedPathIndex = 0;
        }

        public void SetInTunnel(bool inTunnel)
        {
            this.inTunnel = inTunnel;
            UpdateSpeed();
        }

        public void SetSpecialLook(bool enabled)
        {
            specialLook = enabled;
            UpdateAnimator();
        }

        void SetVisibility(bool visible)
        {
            renderer.enabled = visible;
        }

        public PacManGhostState GetGhostState()
        {
            return ghostState;
        }

        public void SetSpeed(float speed)
        {
            this.speed = speed;
            UpdateAnimator();
        }

        public override void CollectSyncedData(byte[] data, ref int index, NetworkEventType eventType)
        {
            if (eventType != NetworkEventType.FullSync)
            {
                return;
            }

            data.Append(target, ref index);
            data.Append(horizontalOnly, ref index);
            data.Append(inTunnel, ref index);
            data.Append(rngState, ref index);
            data.Append(turnAroundSoon, ref index);
            data.Append(speed, ref index);

            data.AppendAsByte((int)ghostState, ref index);
            data.Append(isScared, ref index);
            data.Append(whiteScared, ref index);
            data.Append(scattering, ref index);

            data.Append(offGrid, ref index);
            data.AppendAsByte(housePelletCounter, ref index);
            data.Append(housePelletCounterActive, ref index);
            data.AppendAsByte(housePelletCounterLimit, ref index);

            base.CollectSyncedData(data, ref index, eventType);

            data.AppendAsByte((int)frozenState, ref index);
        }

        public override bool WriteSyncedData(byte[] data, ref int index, NetworkEventType eventType)
        {
            if (eventType != NetworkEventType.FullSync)
            {
                return true;
            }

            target = data.ReadVector2(ref index);
            horizontalOnly = data.ReadBool(ref index);
            inTunnel = data.ReadBool(ref index);
            rngState = data.ReadInt(ref index);
            turnAroundSoon = data.ReadBool(ref index);
            speed = data.ReadFloat(ref index);

            ghostState = (PacManGhostState)data.ReadByte(ref index);
            isScared = data.ReadBool(ref index);
            whiteScared = data.ReadBool(ref index);
            scattering = data.ReadBool(ref index);

            offGrid = data.ReadBool(ref index);
            housePelletCounter = data.ReadByte(ref index);
            housePelletCounterActive = data.ReadBool(ref index);
            housePelletCounterLimit = data.ReadByte(ref index);

            UpdateSpeed();

            frozenState = PacManGhostFrozenState.NotFrozen; // Ensure UpdateAnimator runs fully

            var success = base.WriteSyncedData(data, ref index, eventType);
            
            frozenState = (PacManGhostFrozenState)data.ReadByte(ref index);

            return success;
        }
    }
}