public async Task <bool> CastIfReady(KeyAction item, int sleepBeforeCast = 0)
        {
            if (!CanRun(item))
            {
                return(false);
            }

            if (item.ConsoleKey == 0)
            {
                return(false);
            }

            if (!await SwitchToCorrectShapeShiftForm(item))
            {
                return(false);
            }

            if (this.playerReader.IsShooting)
            {
                await input.TapInteractKey("Stop casting shoot");

                await Task.Delay(1500); // wait for shooting to end
            }

            if (sleepBeforeCast > 0)
            {
                item.LogInformation($" Wait before {sleepBeforeCast}.");
                await Task.Delay(sleepBeforeCast);
            }

            await PressKey(item.ConsoleKey, item.Name, item.PressDuration);

            item.SetClicked();

            if (!item.HasCastBar)
            {
                var result = await WaitInterrupt(item.DelayAfterCast,
                                                 () => playerReader.PlayerBitValues.TargetIsDead || playerReader.TargetHealthPercentage < 5);

                item.LogInformation($" ... no castbar delay after cast {result.Item2}ms");
                await InteractOnUIError();
            }
            else
            {
                await playerReader.WaitForNUpdate(1);

                if (!this.playerReader.IsCasting && this.playerReader.HasTarget)
                {
                    await this.InteractOnUIError();

                    item.LogInformation($"Not casting, pressing it again");
                    await PressKey(item.ConsoleKey, item.Name, item.PressDuration);

                    await playerReader.WaitForNUpdate(1);

                    if (!this.playerReader.IsCasting && this.playerReader.HasTarget)
                    {
                        item.LogInformation($"Still not casting !");
                        await this.InteractOnUIError();

                        return(false);
                    }
                }

                item.LogInformation(" waiting for cast bar to end.");
                for (int i = 0; i < 15000; i += 100)
                {
                    if (!this.playerReader.IsCasting)
                    {
                        await playerReader.WaitForNUpdate(1);

                        break;
                    }

                    // wait after cast if the delay is different to the default value
                    if (item.DelayAfterCast != new KeyAction().DelayAfterCast)
                    {
                        item.LogInformation($" ... delay after cast {item.DelayAfterCast}");

                        if (item.DelayUntilCombat) // stop waiting if the mob is targetting me
                        {
                            var sw = new Stopwatch();
                            sw.Start();
                            while (sw.ElapsedMilliseconds < item.DelayAfterCast)
                            {
                                await Task.Delay(10);

                                if (this.playerReader.PlayerBitValues.TargetOfTargetIsPlayer)
                                {
                                    break;
                                }
                            }
                        }
                        else
                        {
                            var result = await WaitInterrupt(item.DelayAfterCast,
                                                             () => playerReader.PlayerBitValues.TargetIsDead || playerReader.TargetHealthPercentage < 5);

                            item.LogInformation($" ... castbar delay after cast {result.Item2}ms");
                        }
                    }

                    await playerReader.WaitForNUpdate(1);
                }
            }
            if (item.StepBackAfterCast > 0)
            {
                await this.input.KeyPress(ConsoleKey.DownArrow, item.StepBackAfterCast, $"Step back for {item.StepBackAfterCast}ms");
            }

            item.ConsumeCharge();
            return(true);
        }
        public async Task <bool> CastIfReady(KeyAction item, int sleepBeforeCast = 0)
        {
            if (!CanRun(item))
            {
                return(false);
            }

            if (item.ConsoleKey == 0)
            {
                return(false);
            }

            if (item.Name == classConfig.Approach.Name ||
                item.Name == classConfig.AutoAttack.Name ||
                item.Name == classConfig.Interact.Name)
            {
                await PressKeyAction(item);

                return(true);
            }

            bool beforeUsable = addonReader.UsableAction.Is(item);
            var  beforeForm   = playerReader.Form;

            if (!await SwitchToCorrectStanceForm(beforeForm, item))
            {
                return(false);
            }

            if (beforeForm != playerReader.Form && !beforeUsable && !addonReader.UsableAction.Is(item))
            {
                item.LogInformation(" ... after Form switch still not usable!");
                return(false);
            }

            if (playerReader.Bits.IsAutoRepeatSpellOn_Shoot)
            {
                await input.TapStopAttack("Stop AutoRepeat Shoot");

                await input.TapStopAttack("Stop AutoRepeat Shoot");

                await wait.Update(1);

                (bool interrupted, double elapsedMs) = await wait.InterruptTask(GCD,
                                                                                () => addonReader.UsableAction.Is(item));

                if (!interrupted)
                {
                    item.LogInformation($" ... waited to end Shoot {elapsedMs}ms");
                }
            }

            if (sleepBeforeCast > 0)
            {
                if (item.StopBeforeCast || item.HasCastBar)
                {
                    await stopMoving.Stop();

                    await wait.Update(1);

                    await stopMoving.Stop();

                    await wait.Update(1);
                }

                item.LogInformation($" Wait {sleepBeforeCast}ms before press.");
                await Task.Delay(sleepBeforeCast);
            }

            bool beforeHasTarget = playerReader.HasTarget;
            int  auraHash        = playerReader.AuraCount.Hash;


            if (!await WaitForGCD(item, beforeHasTarget))
            {
                return(false);
            }

            if (!item.HasCastBar)
            {
                if (!await CastInstant(item))
                {
                    // try again after reacted to UI_ERROR
                    if (!await CastInstant(item))
                    {
                        return(false);
                    }
                }
            }
            else
            {
                if (!await CastCastbar(item))
                {
                    // try again after reacted to UI_ERROR
                    if (!await CastCastbar(item))
                    {
                        return(false);
                    }
                }
            }

            if (item.AfterCastWaitBuff)
            {
                (bool notappeared, double elapsedMs) = await wait.InterruptTask(MaxWaitBuffTimeMs, () => auraHash != playerReader.AuraCount.Hash);

                item.LogInformation($" ... AfterCastWaitBuff: Buff: {!notappeared} | pb: {playerReader.AuraCount.PlayerBuff} | pd: {playerReader.AuraCount.PlayerDebuff} | tb: {playerReader.AuraCount.TargetBuff} | td: {playerReader.AuraCount.TargetDebuff} | Delay: {elapsedMs}ms");
            }

            if (item.DelayAfterCast != defaultKeyAction.DelayAfterCast)
            {
                if (item.DelayUntilCombat) // stop waiting if the mob is targetting me
                {
                    item.LogInformation($" ... DelayUntilCombat ... delay after cast {item.DelayAfterCast}ms");

                    var sw = new Stopwatch();
                    sw.Start();
                    while (sw.ElapsedMilliseconds < item.DelayAfterCast)
                    {
                        await wait.Update(1);

                        if (playerReader.Bits.TargetOfTargetIsPlayer)
                        {
                            break;
                        }
                    }
                }
                else if (item.DelayAfterCast > 0)
                {
                    item.LogInformation($" ... delay after cast {item.DelayAfterCast}ms");
                    var result = await wait.InterruptTask(item.DelayAfterCast, () => beforeHasTarget != playerReader.HasTarget);

                    if (!result.Item1)
                    {
                        item.LogInformation($" .... delay after cast interrupted, target changed {result.Item2}ms");
                    }
                    else
                    {
                        item.LogInformation($" .... delay after cast not interrupted {result.Item2}ms");
                    }
                }
            }
            else
            {
                if (item.RequirementObjects.Any())
                {
                    (bool firstReq, double firstReqElapsedMs) = await wait.InterruptTask(SpellQueueTimeMs,
                                                                                         () => !item.CanRun()
                                                                                         );

                    item.LogInformation($" ... instant interrupt: {!firstReq} | CanRun:{item.CanRun()} | Delay: {firstReqElapsedMs}ms");
                }
            }

            if (item.StepBackAfterCast > 0)
            {
                input.SetKeyState(ConsoleKey.DownArrow, true, false, $"Step back for {item.StepBackAfterCast}ms");
                (bool notStepback, double stepbackElapsedMs) =
                    await wait.InterruptTask(item.StepBackAfterCast, () => beforeHasTarget != playerReader.HasTarget);

                if (!notStepback)
                {
                    item.LogInformation($" .... interrupted stepback | lost target? {beforeHasTarget != playerReader.HasTarget} | {stepbackElapsedMs}ms");
                }
                input.SetKeyState(ConsoleKey.DownArrow, false, false);
            }

            if (item.AfterCastWaitNextSwing)
            {
                await wait.Update(1);
            }

            item.ConsumeCharge();
            return(true);
        }