using Sandbox;
using System;

namespace GravCoin;

public enum BotTier { T1 = 1, T2 = 2, T3 = 3, T4 = 4 }

/// <summary>
/// Trading bot component. Attach multiple to the player GameObject.
/// Each tier uses a progressively smarter buy/sell strategy.
/// </summary>
public sealed class TradingBot : Component
{
	// ── Config ────────────────────────────────────────────────────────
	private static readonly float[]  TickSeconds  = { 4.0f, 3.2f, 2.4f, 1.6f };
	private static readonly double[] BotCosts     = { 500, 25_000, 3_600_000, 1_800_000_000 };
	private static readonly double[] MaintPerMin  = { 20, 1_200, 150_000, 45_000_000 };
	private static readonly string[] BotNames     = {
		"Basic Script", "Sentiment Algo", "ML Predictor", "Quantum AI" };

	// Base autonomy per tier in seconds (60min / 90min / 120min / 180min)
	private static readonly float[] BaseAutonomy = { 3600f, 5400f, 7200f, 10800f };
	public static float GetBaseAutonomy( BotTier t ) => BaseAutonomy[Math.Clamp((int)t - 1, 0, 3)];
	// Max autonomy cap: 6h = 21600s
	public static float GetMaxAutonomy( BotTier t ) => 21600f;

	public static double GetCost( BotTier t )
	{
		double baseC = BotCosts[Math.Clamp((int)t - 1, 0, 3)];
		var market = MarketManager.Instance;
		if ( market != null && market.ActiveEventName == "Bots Flash Sale" && market.EventTimeLeft > 0 )
			return baseC * 0.5;
		return baseC;
	}
	public static double   GetMaint( BotTier t ) => MaintPerMin[Math.Clamp((int)t - 1, 0, 3)];
	public static double   GetRefuelCost( BotTier t ) => GetMaint(t) * 2;
	/// <summary>Actual refuel cost proportional to missing fuel.</summary>
	public double ActualRefuelCost => GetRefuelCost(Tier) * (1f - FuelLevel);
	public static string   GetName( BotTier t ) => BotNames[Math.Clamp((int)t - 1, 0, 3)];

	private static readonly double[] CalibrateCosts = { 400, 8_000, 220_000, 18_000_000 };
	public static double GetCalibrateCost( BotTier t, int quality )
	{
		double baseC = CalibrateCosts[Math.Clamp((int)t - 1, 0, 3)];
		double quality01 = Math.Clamp( quality, 0, 100 ) / 100.0;
		return baseC * (1.0 + 4.0 * Math.Pow( quality01, 1.30 ));
	}

	private static readonly double[] AutonUpgCosts = { 900, 18_000, 420_000, 28_000_000 };
	public static double GetAutonUpgCost( BotTier t, float currentMax )
	{
		double baseC = AutonUpgCosts[Math.Clamp((int)t - 1, 0, 3)];
		float baseAuton = GetBaseAutonomy(t);
		// More expensive as you approach 6h
		float totalUpgs = Math.Max( 0f, (currentMax - baseAuton) / 600f );
		return baseC * Math.Pow( 1.0 + totalUpgs * 0.45, 1.18 ) * 1.15;
	}

	public static string GetQualityLabel( int q ) => q switch
	{
		>= 90 => "Legendary",
		>= 75 => "Excellent",
		>= 50 => "Good",
		>= 25 => "Fair",
		_     => "Poor"
	};

	public static string GetQualityColor( int q ) => q switch
	{
		>= 90 => "#fbbf24",
		>= 75 => "#10b981",
		>= 50 => "#3b82f6",
		>= 25 => "#a1a1aa",
		_     => "#ef4444"
	};

	// ── Offline earnings estimation per minute ────────────────────
	private static readonly double[] OfflineBasePerMin = { 40, 2_500, 220_000, 60_000_000 };

	/// <summary>Estimated earnings/min while the player is offline (~90% efficiency).</summary>
	public static double GetOfflineEarningsPerMin( BotTier tier, int quality )
	{
		double baseRate    = OfflineBasePerMin[Math.Clamp( (int)tier - 1, 0, 3 )];
		double quality01   = Math.Clamp( quality, 0, 100 ) / 100.0;
		double qualityMult = 0.70 + quality01 * 0.90;
		const double offlineEfficiency = 0.50; // 50% offline penalty
		return baseRate * qualityMult * offlineEfficiency;
	}

	// ── Synced state ──────────────────────────────────────────────────
	[Property, Sync] public BotTier Tier             { get; set; } = BotTier.T1;
	[Property, Sync] public bool    IsActive         { get; set; }
	[Sync]           public float   FuelSeconds      { get; set; }  // seconds remaining
	[Sync]           public float   MaxFuelSeconds   { get; set; }  // max autonomy in seconds
	[Sync]           public bool    NeedsMaintenance { get; set; }
	[Sync]           public float   EarningsMin      { get; set; }
	[Sync]           public double  TotalEarned      { get; set; }
	[Sync]           public double  NetProfit        { get; set; }
	[Sync]           public int     TradesTotal      { get; set; }
	[Sync]           public int     TradesWon        { get; set; }
	[Sync]           public int     Quality          { get; set; } = 50;
	[Sync]           public float   HeldCoins        { get; set; }  // coins currently held by bot
	[Sync]           public float   EntryPrice       { get; set; }  // buy price of current position
	[Sync]           public double  InvestedAmount   { get; set; }  // USD invested in current position
	[Sync]           public float   PositionTP       { get; set; }  // TP% used for current position
	[Sync]           public float   PositionSL       { get; set; }  // SL% used for current position
	[Sync]           public float   PositionTargetPrice { get; set; } // projected target at entry
	[Sync]           public float   PositionHoldSeconds { get; set; }
	[Sync]           public int     PositionCooldownTicks { get; set; }
	[Sync]           public string  LastTradeInfo    { get; set; } = "";

	// ── User-configurable settings ──────────────────────────────────
	[Sync] public double UserTradeAmount    { get; set; }  // $ amount per trade (0 = auto)
	[Sync] public float  UserTakeProfitPct  { get; set; }  // take profit % (e.g. 0.5 = 0.5%)
	[Sync] public float  UserStopLossPct    { get; set; }  // stop loss % (e.g. 3.5 = 3.5%)

	public static double GetDefaultTradeAmount( BotTier t ) => GetCost(t) * 0.65;
	public static float  GetDefaultTakeProfitPct( BotTier t ) => 0.5f;
	public static float  GetDefaultStopLossPct( BotTier t )   => 25.0f;

	/// <summary>Max trade amount allowed based on tier cost + quality.</summary>
	public double MaxTradeAmount
	{
		get
		{
			_owner ??= Components.GetInAncestors<PlayerTrader>();

			double quality01 = Math.Clamp( Quality, 0, 100 ) / 100.0;
			double baseCost = GetCost( Tier );
			double baseCap = (baseCost * 0.9) * (1.0 + quality01 * 1.5);

			// Late-game speculative scaling:
			// keeps bots as a strong leap mechanic in trillion+ ranges.
			double netWorth = Math.Max( 0.0, _owner?.NetWorth ?? 0.0 );
			double tierPct = Tier switch
			{
				BotTier.T1 => 0.00060,
				BotTier.T2 => 0.00150,
				BotTier.T3 => 0.00450,
				BotTier.T4 => 0.01000,
				_ => 0.00060
			};
			double speculativeCap = netWorth * tierPct * (0.75 + quality01 * 0.75);

			return Math.Max( baseCap, speculativeCap );
		}
	}

	/// <summary>Initialize user settings to tier defaults if not yet set.</summary>
	public void InitSettingsIfNeeded()
	{
		if ( UserTradeAmount <= 0 )    UserTradeAmount    = GetDefaultTradeAmount( Tier );
		if ( UserTakeProfitPct <= 0f ) UserTakeProfitPct  = GetDefaultTakeProfitPct( Tier );
		if ( UserStopLossPct <= 0f )   UserStopLossPct    = GetDefaultStopLossPct( Tier );
	}

	[Rpc.Owner]
	public void CmdToggleActive()
	{
		_owner ??= Components.GetInAncestors<PlayerTrader>();
		if ( _owner?.CanProcessGameplay() != true ) return;
		if ( _sinceLastOwnerRpc < MinOwnerRpcInterval ) return;
		_sinceLastOwnerRpc = 0;
		IsActive = !IsActive;
		_owner?.RequestSave();
	}

	/// <summary>Fuel as 0-1 ratio for UI.</summary>
	public float FuelLevel => MaxFuelSeconds > 0 ? Math.Clamp( FuelSeconds / MaxFuelSeconds, 0f, 1f ) : 0f;
	/// <summary>Remaining autonomy in minutes for display.</summary>
	public int FuelMinutesLeft => Math.Max( 0, (int)MathF.Ceiling( FuelSeconds / 60f ) );

	[Rpc.Owner]
	public void CmdRefuel()
	{
		_owner ??= Components.GetInAncestors<PlayerTrader>();
		if ( _owner?.CanProcessGameplay() != true ) return;
		if ( _sinceLastOwnerRpc < MinOwnerRpcInterval ) return;
		_sinceLastOwnerRpc = 0;
		if ( FuelSeconds >= MaxFuelSeconds ) return; // already full
		
		double cost = ActualRefuelCost;
		if ( cost <= 0 ) return;
		if ( _owner.DeductMoney(cost) )
		{
			FuelSeconds = MaxFuelSeconds;
			NeedsMaintenance = false;
			IsActive = true;
			NetProfit -= cost;
		}
	}

	[Rpc.Owner]
	public void CmdCalibrate()
	{
		_owner ??= Components.GetInAncestors<PlayerTrader>();
		if ( _owner?.CanProcessGameplay() != true || Quality >= 100 ) return;
		if ( _sinceLastOwnerRpc < MinOwnerRpcInterval ) return;
		_sinceLastOwnerRpc = 0;
		double cost = GetCalibrateCost( Tier, Quality );
		if ( !_owner.DeductMoney( cost ) ) return;
		Quality = Math.Min( 100, Quality + Random.Shared.Next( 8, 20 ) );
		_owner.RequestSave();
	}

	public bool IsMaxAutonomy => MaxFuelSeconds >= GetMaxAutonomy( Tier );

	[Rpc.Owner]
	public void CmdUpgradeAutonomy()
	{
		_owner ??= Components.GetInAncestors<PlayerTrader>();
		if ( _owner?.CanProcessGameplay() != true ) return;
		if ( _sinceLastOwnerRpc < MinOwnerRpcInterval ) return;
		_sinceLastOwnerRpc = 0;
		float maxCap = GetMaxAutonomy( Tier );
		if ( MaxFuelSeconds >= maxCap ) return;
		
		double cost = GetAutonUpgCost( Tier, MaxFuelSeconds );
		if ( !_owner.DeductMoney( cost ) ) return;

		MaxFuelSeconds = MathF.Min( MaxFuelSeconds + 600f, maxCap );
		FuelSeconds = MaxFuelSeconds;
		NeedsMaintenance = false;
		IsActive = true;
		_owner.RequestSave();
	}

	[Rpc.Owner]
	public void CmdScrap()
	{
		_owner ??= Components.GetInAncestors<PlayerTrader>();
		if ( _owner?.CanProcessGameplay() != true ) return;
		if ( _sinceLastOwnerRpc < MinOwnerRpcInterval ) return;
		_sinceLastOwnerRpc = 0;
		// 25% refund on scrap
		double refund = GetCost( Tier ) * 0.25;
		_owner?.AddMoney( refund );
		_owner?.RemoveBot( this );
		GameObject.Destroy();
	}

	// ── Private ───────────────────────────────────────────────────────
	private PlayerTrader  _owner;
	private MarketManager _market;
	private TimeSince     _lastTick;
	private bool          _fuelInitialized;  // prevent OnStart overwriting loaded fuel
	private RealTimeSince _sinceLastOwnerRpc = 10;
	private const float MinOwnerRpcInterval = 0.15f;

	// ── Multi-position: up to 2 simultaneous trades ─────────────────
	private const int MaxSlots = 1;
	private readonly float[]  _slotBuyPrice   = new float[MaxSlots];
	private readonly float[]  _slotCoins      = new float[MaxSlots];
	private readonly double[] _slotInvested   = new double[MaxSlots];
	private readonly float[]  _slotHoldTime   = new float[MaxSlots];
	private readonly float[]  _slotPeakProfit = new float[MaxSlots];
	private readonly bool[]   _slotActive     = new bool[MaxSlots];
	private readonly int[]    _slotCooldown   = new int[MaxSlots];
	private readonly float[]  _slotTP         = new float[MaxSlots]; // TP% snapshot at entry
	private readonly float[]  _slotSL         = new float[MaxSlots]; // SL% snapshot at entry
	private readonly float[]  _slotTargetPrice = new float[MaxSlots]; // The vision target that triggered the trade

	// Position size per tier (fraction of money)
	private static readonly float[] PositionPct = { 0.15f, 0.20f, 0.25f, 0.32f };

	// Max hold durations before force-sell (seconds)
	private static readonly float[] MaxHoldTime = { 120f, 240f, 480f, 900f };

	// Minimum hold time before considering selling (seconds)
	private static readonly float[] MinHoldTime = { 10f, 8f, 5f, 3f };

	// Cooldown ticks after a sell before next buy (per slot)
	private static readonly int[] CooldownAfterSell = { 2, 2, 2, 2 };

	// How many seconds ahead each tier can look (bots see the future!)
	private static readonly int[] LookaheadSeconds = { 15, 20, 30, 40 };

	// ────── Intelligence Logic ──────

	/// <summary>
	/// Generates a "perceived" price by adding noise to the actual future price.
	/// Winrate controller in ShouldBuy targets roughly 72%-96% based on quality.
	/// </summary>
	private double GetPerceivedPrice( double actualPrice )
	{
		double q = Math.Clamp( Quality, 0, 100 ) / 100.0;
		double maxError = 0.15 - 0.11 * q; // 15% error at low quality, 4% at max quality
		double error = (Random.Shared.NextDouble() * 2.0 - 1.0) * maxError;
		return actualPrice * (1.0 + error);
	}

	protected override void OnStart()
	{
		_owner  = Components.GetInAncestors<PlayerTrader>();
		_market = MarketManager.Instance;

		if ( !_fuelInitialized )
		{
			if ( MaxFuelSeconds <= 0f )
				MaxFuelSeconds = GetBaseAutonomy( Tier );
			if ( FuelSeconds <= 0f && !NeedsMaintenance )
				FuelSeconds = MaxFuelSeconds;
		}

		InitSettingsIfNeeded();
	}

	/// <summary>Mark fuel as loaded from stats so OnStart won't overwrite it.</summary>
	public void MarkFuelLoaded() => _fuelInitialized = true;

	protected override void OnFixedUpdate()
	{
		// Auto-sell if disabled but holding positions
		if ( !IsActive && HeldCoins > 0 )
		{
			SellPositions();
			return;
		}

		if ( IsProxy || !IsActive || _owner is null || _owner.CanProcessGameplay() != true ) return;
		_market ??= MarketManager.Instance;
		if ( _market is null ) return;

		int tierIdx = Math.Clamp((int)Tier - 1, 0, 3);
		float interval = TickSeconds[tierIdx];
		if ( _lastTick < interval ) return;
		_lastTick = 0;

		// Fuel Mechanic
		FuelSeconds -= interval;
		if ( FuelSeconds <= 0f )
		{
			FuelSeconds = 0f;
			IsActive = false;
			NeedsMaintenance = true;
			return;
		}

		float price = _market.CurrentPrice;
		int lookahead = LookaheadSeconds[tierIdx];

		// ── Sell pass: check each active slot ──
		for ( int s = 0; s < MaxSlots; s++ )
		{
			if ( !_slotActive[s] ) continue;
			_slotHoldTime[s] += interval;

			if ( ShouldSellSlot( s, price, lookahead ) )
			{
				float taxRate = GetTaxRate();
				double revenue = _slotCoins[s] * price * (1.0 - taxRate);
				double creditedRevenue = _owner.ApplyWelcomeBoostToTradeRevenue( revenue, _slotInvested[s] );
				double profit  = creditedRevenue - _slotInvested[s];

				_owner.AddMoney( creditedRevenue );
				_owner.TrackBotProfit( profit, $"{GetName( Tier ).ToLowerInvariant()} bot profit" );
				_market.AddGlobalVolume( (float)revenue );

				TotalEarned  += creditedRevenue;
				NetProfit    += profit;
				TradesTotal  ++;
				if ( profit > 0 ) TradesWon++;

				double pctPnl = _slotInvested[s] > 0 ? profit / _slotInvested[s] * 100.0 : 0;
				LastTradeInfo  = $"{(profit >= 0 ? "SELL +" : "SELL ")}{PlayerTrader.DollarM(Math.Abs(profit))} ({pctPnl:F1}%) at ${price:F2}";
				EarningsMin    = (float)(profit / Math.Max( _slotHoldTime[s], 1f ) * 60f);

				_slotActive[s]     = false;
				_slotCoins[s]      = 0;
				_slotInvested[s]   = 0;
				_slotBuyPrice[s]   = 0;
				_slotHoldTime[s]   = 0;
				_slotPeakProfit[s] = 0;
				_slotTP[s]         = 0;
				_slotSL[s]         = 0;
				_slotTargetPrice[s] = 0;
				_slotCooldown[s]   = CooldownAfterSell[tierIdx];
				_owner.RequestSave();
			}
		}

		// ── Buy pass: try to fill one empty slot per tick ──
		for ( int s = 0; s < MaxSlots; s++ )
		{
			if ( _slotActive[s] ) continue;
			if ( _slotCooldown[s] > 0 ) { _slotCooldown[s]--; continue; }

			if ( ShouldBuy( price, lookahead ) )
			{
				double invest = Math.Clamp( UserTradeAmount, 1.0, Math.Max( 1.0, MaxTradeAmount ) );
				double perceivedFuture = _lastPerceivedPrice;
				if ( invest > _owner.Money ) invest = _owner.Money;
				if ( invest < 1.0 ) invest = Math.Min( _owner.Money, 20.0 );
				if ( invest > 0 && _owner.DeductMoney( invest ) )
				{
					float boughtCoins = (float)(invest / price);
					_slotBuyPrice[s]   = price;
					_slotCoins[s]      = boughtCoins;
					_slotInvested[s]   = invest;
					_slotActive[s]     = true;
					_slotHoldTime[s]   = 0f;
					_slotPeakProfit[s] = 0f;
					_slotTP[s]         = UserTakeProfitPct;
					_slotSL[s]         = UserStopLossPct;
					_slotTargetPrice[s] = (float)perceivedFuture; 
					_market.AddGlobalVolume( (float)invest );
					LastTradeInfo      = $"BUY {PlayerTrader.FmtM(_slotCoins[s])} GRAV at ${price:F2} (Target: ${_slotTargetPrice[s]:F2})";
					break; 
				}
			}
			break; // ShouldBuy failed, don't check next slot (same result)
		}

		// ── Update synced aggregates for UI ──
		SyncPositionState();
	}

	[Rpc.Owner]
	public void CmdSellPositions()
	{
		if ( _sinceLastOwnerRpc < MinOwnerRpcInterval ) return;
		_sinceLastOwnerRpc = 0;
		SellPositions();
	}

	/// <summary>
	/// Forces liquidation of all open bot positions.
	/// Used during disconnect/exit flow where owner RPC guards are not reliable.
	/// </summary>
	public bool ForceLiquidateAllPositions( bool disableBotAfter = true )
	{
		_owner ??= Components.GetInAncestors<PlayerTrader>();
		_market ??= MarketManager.Instance;
		if ( _market is null || _owner is null ) return false;

		float price = _market.CurrentPrice;
		bool soldAny = false;

		for ( int s = 0; s < MaxSlots; s++ )
		{
			if ( !_slotActive[s] ) continue;
			SellSlot( s, price );
			soldAny = true;
		}

		if ( disableBotAfter )
			IsActive = false;

		if ( !soldAny )
			SyncPositionState();

		return soldAny;
	}

	private void SellPositions()
	{
		_market ??= MarketManager.Instance;
		if ( _market is null || _owner is null || _owner.CanProcessGameplay() != true ) return;
		float price = _market.CurrentPrice;

		for ( int s = 0; s < MaxSlots; s++ )
		{
			if ( _slotActive[s] ) SellSlot( s, price );
		}
	}

	private void SellSlot( int s, float price )
	{
		int tierIdx = Math.Clamp( (int)Tier - 1, 0, 3 );
		float taxRate = GetTaxRate();
		double revenue = _slotCoins[s] * price * (1.0 - taxRate);
		double creditedRevenue = _owner.ApplyWelcomeBoostToTradeRevenue( revenue, _slotInvested[s] );
		double profit = creditedRevenue - _slotInvested[s];

		_owner.AddMoney( creditedRevenue );
		_owner.TrackBotProfit( profit, $"{GetName( Tier ).ToLowerInvariant()} bot profit" );
		_market.AddGlobalVolume( (float)revenue );

		TotalEarned += creditedRevenue;
		NetProfit += profit;
		TradesTotal++;
		if ( profit > 0 ) TradesWon++;

		double pctPnl = _slotInvested[s] > 0 ? profit / _slotInvested[s] * 100.0 : 0;
		LastTradeInfo = $"{(profit >= 0 ? "SELL +" : "SELL ")}{PlayerTrader.DollarM(Math.Abs(profit))} ({pctPnl:F1}%) at ${price:F2}";
		EarningsMin = (float)(profit / Math.Max( _slotHoldTime[s], 1f ) * 60f);

		_slotActive[s] = false;
		_slotCoins[s] = 0;
		_slotInvested[s] = 0;
		_slotBuyPrice[s] = 0;
		_slotHoldTime[s] = 0;
		_slotPeakProfit[s] = 0;
		_slotTP[s] = 0;
		_slotSL[s] = 0;
		_slotTargetPrice[s] = 0;
		_slotCooldown[s] = CooldownAfterSell[tierIdx];
		_owner.RequestSave();
		SyncPositionState();
	}

	private void SyncPositionState()
	{
		float totalCoins = 0;
		double totalInvested = 0;
		float weightedPrice = 0;
		for ( int s = 0; s < MaxSlots; s++ )
		{
			if ( !_slotActive[s] ) continue;
			totalCoins   += _slotCoins[s];
			totalInvested += _slotInvested[s];
			weightedPrice += _slotCoins[s] * _slotBuyPrice[s];
		}
		HeldCoins      = totalCoins;
		InvestedAmount = totalInvested;
		EntryPrice     = totalCoins > 0 ? weightedPrice / totalCoins : 0;

		// Sync TP/SL from first active slot for UI
		PositionTP = 0;
		PositionSL = 0;
		PositionTargetPrice = 0;
		PositionHoldSeconds = 0;
		for ( int s = 0; s < MaxSlots; s++ )
		{
			if ( !_slotActive[s] ) continue;
			PositionTP = _slotTP[s];
			PositionSL = _slotSL[s];
			PositionTargetPrice = _slotTargetPrice[s];
			PositionHoldSeconds = _slotHoldTime[s];
			break;
		}

		int maxCooldown = 0;
		for ( int s = 0; s < MaxSlots; s++ )
			maxCooldown = Math.Max( maxCooldown, _slotCooldown[s] );
		PositionCooldownTicks = maxCooldown;
	}

	public void RestorePositionState(
		float heldCoins,
		double investedAmount,
		float entryPrice,
		float tp,
		float sl,
		float targetPrice,
		float holdSeconds,
		int cooldownTicks )
	{
		for ( int s = 0; s < MaxSlots; s++ )
		{
			_slotActive[s] = false;
			_slotCoins[s] = 0f;
			_slotInvested[s] = 0;
			_slotBuyPrice[s] = 0f;
			_slotHoldTime[s] = 0f;
			_slotPeakProfit[s] = 0f;
			_slotCooldown[s] = 0;
			_slotTP[s] = 0f;
			_slotSL[s] = 0f;
			_slotTargetPrice[s] = 0f;
		}

		if ( heldCoins > 0f && investedAmount > 0.01 )
		{
			_slotActive[0] = true;
			_slotCoins[0] = heldCoins;
			_slotInvested[0] = investedAmount;
			_slotBuyPrice[0] = Math.Max( 0.0001f, entryPrice );
			_slotTP[0] = Math.Max( 0f, tp );
			_slotSL[0] = Math.Max( 0f, sl );
			_slotTargetPrice[0] = Math.Max( 0.0001f, targetPrice );
			_slotHoldTime[0] = Math.Max( 0f, holdSeconds );
		}

		_slotCooldown[0] = Math.Max( 0, cooldownTicks );
		SyncPositionState();
	}

	// ── Helpers ───────────────────────────────────────────────────────
	private float GetTaxRate()
	{
		return 0f;
	}

	// ── Buy decision: peek at future prices ─────────────────────────
	private bool ShouldBuy( float price, int lookahead )
	{
		int tierIdx = Math.Clamp( (int)Tier - 1, 0, 3 );
		
		// 1. First priority: Check for ANY major crash (-25%) in our tiered vision window
		bool sawCrash = false;
		for ( int s = 1; s <= lookahead && s <= _market.MaxLookahead; s++ )
		{
			float fp = _market.GetFuturePrice( s );
			if ( GetPerceivedPrice( fp ) <= price * 0.75 )
			{
				sawCrash = true;
				break;
			}
		}

		if ( sawCrash ) return false;

		// 2. Scan for BOTH Profit and Loss opportunities
		float bestFuture = price;
		float worstFuture = price;

		for ( int s = 1; s <= lookahead && s <= _market.MaxLookahead; s++ )
		{
			float fp = _market.GetFuturePrice( s );
			if ( fp > bestFuture ) bestFuture = fp;
			if ( fp < worstFuture ) worstFuture = fp;
		}

		double perceivedBest = GetPerceivedPrice( bestFuture );
		double perceivedWorst = GetPerceivedPrice( worstFuture );

		double potentialGain = (perceivedBest - price) / price;
		double potentialLoss = (perceivedWorst - price) / price;

		bool hasProfitSignal = potentialGain >= 0.035;
		bool hasLossSignal = potentialLoss <= -0.025;

		// 3. Self-Balancing Win Rate by quality: 82% -> 98%
		float qualityFactor = Math.Clamp( Quality, 0, 100 ) / 100f;
		float targetWinRate = 0.82f + (qualityFactor * 0.16f);
		float recoveryFloor = MathF.Max( 0.80f, targetWinRate - 0.04f );
		float realisticCeil = MathF.Min( 0.99f, targetWinRate + 0.02f );

		float winRate = TradesTotal > 0 ? (float)TradesWon / TradesTotal : targetWinRate; 

		// RECOVERY MODE: If under-performing, strictly ignore all loss signals and wait for profit
		if ( winRate < recoveryFloor )
		{
			if ( hasProfitSignal )
			{
				_lastPerceivedPrice = (float)perceivedBest;
				return true;
			}
			return false; // Wait for a win, no matter what
		}

		// Force occasional mistakes only if win-rate is way above corridor.
		if ( winRate > realisticCeil + 0.03f && hasLossSignal && Random.Shared.NextDouble() < 0.30 )
		{
			_lastPerceivedPrice = (float)perceivedWorst;
			return true;
		}

		// If we're above target and no loss setup exists, wait for a better entry.
		if ( winRate > realisticCeil && !hasLossSignal )
			return false;

		// Standard Logic when in the target corridor
		float profitBias = Math.Clamp( targetWinRate, 0.70f, 0.96f );
		float soloProfitChance = 0.70f + qualityFactor * 0.26f; // 70% -> 96%
		float soloLossChance = 0.12f - qualityFactor * 0.09f;   // 12% -> 3%

		if ( hasProfitSignal && hasLossSignal )
		{
			if ( Random.Shared.NextDouble() < profitBias )
			{
				_lastPerceivedPrice = (float)perceivedBest;
				return true;
			}
			else
			{
				_lastPerceivedPrice = (float)perceivedWorst;
				return true;
			}
		}
		else if ( hasProfitSignal )
		{
			// Realistic delay: don't always take the first window
			if ( Random.Shared.NextDouble() < soloProfitChance )
			{
				_lastPerceivedPrice = (float)perceivedBest;
				return true;
			}
		}
		else if ( hasLossSignal )
		{
			// Controlled mistake chance only when no profit signal
			if ( Random.Shared.NextDouble() < soloLossChance )
			{
				_lastPerceivedPrice = (float)perceivedWorst;
				return true;
			}
		}

		return false; 
	}
	private float _lastPerceivedPrice; // Small hack to pass to CmdBuy

	// ── Sell decision per slot ───────────────────────────────────────
	private bool ShouldSellSlot( int slot, float price, int lookahead )
	{
		float entry = _slotBuyPrice[slot];
		float target = _slotTargetPrice[slot];
		double pnl = (price - entry) / entry;

		// ── Dynamic Safety Net ──
		if ( target > entry ) // Intended Profit Trade
		{
			// TP @ +16% / SL @ -8%
			if ( pnl >= 0.16 || pnl <= -0.08 ) return true;
		}
		else // Intended Loss Trade (Deliberate mistake)
		{
			// TP @ +8% / SL @ -10%
			if ( pnl >= 0.08 || pnl <= -0.10 ) return true;
		}

		// If it was a profit trade, sell when price >= target
		if ( target > entry )
		{
			if ( price >= target ) return true;
		}
		// If it was a deliberate loss trade, sell when price <= target
		else if ( target < entry )
		{
			if ( price <= target ) return true;
		}

		return false;
	}
}
