<?php
/* GENERATED FIX: news actual/recent releases dashboard + event-driven alert integration package */
ini_set('display_errors', 0);
error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);

session_start();
date_default_timezone_set('Africa/Lagos');

/*
|--------------------------------------------------------------------------
| User-friendly API Credit Protection
|--------------------------------------------------------------------------
| The dashboard must not auto-analyze anything on login or ordinary refresh.
|
| Production flow:
| 1. Plain GET/login => idle dashboard; no API call.
| 2. POST Analyze => save context, redirect to ?analysis=1.
| 3. GET ?analysis=1 => render the analysis using saved context.
| 4. Refreshing ?analysis=1 => re-render from saved/local data, no API refetch.
| 5. Tool actions such as lot-size calculation stay on the analysis view and
|    do not trigger a fresh market-data/API request.
|--------------------------------------------------------------------------
*/

$lotCalculationRequest = (
    $_SERVER['REQUEST_METHOD'] === 'POST'
    && isset($_POST['calculate_lot'])
);

$analyzeSubmitRequest = (
    $_SERVER['REQUEST_METHOD'] === 'POST'
    && isset($_POST['analyze'])
    && !$lotCalculationRequest
    && trim((string)($_POST['symbol'] ?? '')) !== ''
    && trim((string)($_POST['timeframe'] ?? '')) !== ''
);

$analysisViewRequest = (
    $_SERVER['REQUEST_METHOD'] === 'GET'
    && (string)($_GET['analysis'] ?? '') === '1'
    && !empty($_SESSION['last_dashboard_analysis_context'])
    && is_array($_SESSION['last_dashboard_analysis_context'])
);

$freshMarketDataRequested = false;

/*
| Real Analyze button click:
| Use Post/Redirect/Get to prevent browser refresh from resubmitting the
| analysis form and consuming API credits again.
*/
if ($analyzeSubmitRequest) {
    $_SESSION['last_dashboard_analysis_context'] = [
        'symbol' => trim((string)($_POST['symbol'] ?? '')),
        'timeframe' => trim((string)($_POST['timeframe'] ?? '')),
        'style' => trim((string)($_POST['style'] ?? 'intraday')),
        'asset_type' => trim((string)($_POST['asset_type'] ?? ($_GET['asset_type'] ?? ''))),
        'requested_at' => time(),
    ];

    /*
    | This one-time flag allows the redirected analysis page to fetch fresh
    | market data once. After that, refreshing ?analysis=1 uses saved/local data.
    */
    $_SESSION['dashboard_fetch_fresh_once'] = true;

    $basePath = strtok((string)($_SERVER['REQUEST_URI'] ?? 'dashboard.php'), '?');
    if ($basePath === '') {
        $basePath = 'dashboard.php';
    }

    header('Location: ' . $basePath . '?analysis=1');
    exit;
}

/*
| Saved analysis view:
| Recover symbol/timeframe/style from the last explicit Analyze click.
*/
if ($analysisViewRequest) {
    foreach (['symbol', 'timeframe', 'style'] as $contextKey) {
        if (isset($_SESSION['last_dashboard_analysis_context'][$contextKey])) {
            $_POST[$contextKey] = $_SESSION['last_dashboard_analysis_context'][$contextKey];
        }
    }

    if (!empty($_SESSION['last_dashboard_analysis_context']['asset_type'])) {
        $_GET['asset_type'] = $_SESSION['last_dashboard_analysis_context']['asset_type'];
    }

    if (!empty($_SESSION['dashboard_fetch_fresh_once'])) {
        $freshMarketDataRequested = true;
        unset($_SESSION['dashboard_fetch_fresh_once']);
    }
}

/*
| Lot-size calculation:
| It is a tool action inside the analysis view. Recover the current analysis
| context if the posted form is missing it, and do not refetch market data.
*/
if ($lotCalculationRequest && !empty($_SESSION['last_dashboard_analysis_context']) && is_array($_SESSION['last_dashboard_analysis_context'])) {
    foreach (['symbol', 'timeframe', 'style'] as $contextKey) {
        if (trim((string)($_POST[$contextKey] ?? '')) === '' && isset($_SESSION['last_dashboard_analysis_context'][$contextKey])) {
            $_POST[$contextKey] = $_SESSION['last_dashboard_analysis_context'][$contextKey];
        }
    }
}

$userRequestedAnalysis = (
    (
        $analysisViewRequest
        || $lotCalculationRequest
    )
    && trim((string)($_POST['symbol'] ?? '')) !== ''
    && trim((string)($_POST['timeframe'] ?? '')) !== ''
);

/*
| Save recovered context for later tool actions.
*/
if ($userRequestedAnalysis) {
    $_SESSION['last_dashboard_analysis_context'] = array_merge(
        $_SESSION['last_dashboard_analysis_context'] ?? [],
        [
            'symbol' => trim((string)($_POST['symbol'] ?? '')),
            'timeframe' => trim((string)($_POST['timeframe'] ?? '')),
            'style' => trim((string)($_POST['style'] ?? 'intraday')),
            'asset_type' => trim((string)($_GET['asset_type'] ?? ($_SESSION['last_dashboard_analysis_context']['asset_type'] ?? ''))),
            'last_viewed_at' => time(),
        ]
    );
}

require_once __DIR__ . '/../services/AuthService.php';

if (!AuthService::check()) {
    header('Location: ../public/login.php');
    exit;
}

require_once __DIR__ . '/../core/functions.php';
require_once __DIR__ . '/../services/CandleService.php';
require_once __DIR__ . '/../services/SmcAnalyzer.php';
require_once __DIR__ . '/../services/RiskService.php';
require_once __DIR__ . '/../services/SystemConfigService.php';
require_once __DIR__ . '/../services/MarketDataService.php';

$apiCreditGuardPath = __DIR__ . '/../services/ApiCreditGuard.php';
if (file_exists($apiCreditGuardPath)) {
    require_once $apiCreditGuardPath;
}

$quoteServicePath = __DIR__ . '/../services/QuoteService.php';
if (file_exists($quoteServicePath)) {
    require_once $quoteServicePath;
}

$optionalServices = [
    '/../services/MultiTimeframeService.php',
    '/../services/VolatilityService.php',
    '/../services/PremiumDiscountService.php',
    '/../services/LiquidityService.php',
    '/../services/FVGService.php',
    '/../services/OrderBlockService.php',
    '/../services/InstitutionalBiasService.php',
    '/../services/TradingStyleService.php',
    '/../services/MarketStructureService.php',
    '/../services/RiskScoringEngine.php',
    '/../services/NewsAggregatorService.php',
    '/../services/GeoPoliticalAggregator.php',
    '/../services/RSSFeedService.php',
    '/../services/GeoRiskService.php',
    '/../services/NewsRiskService.php',
    '/../services/EconomicCalendarService.php',
    '/../services/GeoPoliticalService.php',
    '/../services/ConfluenceService.php',
    '/../services/NewsService.php',
    '/../services/AlertService.php',
];

foreach ($optionalServices as $servicePath) {
    $fullPath = __DIR__ . $servicePath;
    if (file_exists($fullPath)) {
        require_once $fullPath;
    }
}

if (!function_exists('fp')) {
    function fp($value, $decimals)
    {
        if ($value === null || $value === '') {
            return 'N/A';
        }

        return format_price((float)$value, (int)$decimals);
    }
}


if (!function_exists('dashboard_symbol_config_safe')) {
    function dashboard_symbol_config_safe($symbolCode): array
    {
        $symbolCode = strtoupper(str_replace(['/', '-', '_', ' '], '', trim((string)$symbolCode)));

        if ($symbolCode === '') {
            return [];
        }

        try {
            if (class_exists('SystemConfigService') && method_exists('SystemConfigService', 'getSymbolConfig')) {
                $config = SystemConfigService::getSymbolConfig($symbolCode);
                return is_array($config) ? $config : [];
            }
        } catch (Throwable $e) {
        }

        return [];
    }
}

if (!function_exists('dashboard_symbol_asset_type_safe')) {
    function dashboard_symbol_asset_type_safe($symbolCode): string
    {
        $config = dashboard_symbol_config_safe($symbolCode);
        $assetType = strtoupper((string)($config['asset_type'] ?? ''));

        if ($assetType !== '') {
            return $assetType;
        }

        $symbolCode = strtoupper(str_replace(['/', '-', '_', ' '], '', trim((string)$symbolCode)));

        if (preg_match('/^(BTC|ETH|BNB|SOL|XRP|ADA|DOGE|LTC|BCH|AVAX|DOT|MATIC|TRX|LINK|ATOM|NEAR|APT|OP|ARB)/', $symbolCode)) {
            return 'CRYPTO';
        }

        if (strpos($symbolCode, 'XAU') !== false || strpos($symbolCode, 'XAG') !== false) {
            return 'METAL';
        }

        if (preg_match('/^[A-Z]{6}$/', $symbolCode)) {
            return 'FOREX';
        }

        return 'OTHER';
    }
}

if (!function_exists('dashboard_symbol_decimals_safe')) {
    function dashboard_symbol_decimals_safe($symbolCode, $fallback = 2): int
    {
        $config = dashboard_symbol_config_safe($symbolCode);
        $decimals = (int)($config['decimals'] ?? 0);

        if ($decimals > 0) {
            return $decimals;
        }

        if (class_exists('RiskService') && method_exists('RiskService', 'priceDecimals')) {
            return (int)RiskService::priceDecimals((string)$symbolCode);
        }

        $symbolCode = strtoupper((string)$symbolCode);

        if ($symbolCode === 'XAUUSD') return 2;
        if ($symbolCode === 'XAGUSD') return 3;
        if (strpos($symbolCode, 'JPY') !== false) return 3;

        return (int)$fallback;
    }
}

if (!function_exists('dashboard_symbol_pip_size_safe')) {
    function dashboard_symbol_pip_size_safe($symbolCode): float
    {
        $config = dashboard_symbol_config_safe($symbolCode);
        $pipSize = (float)($config['pip_size'] ?? 0);

        if ($pipSize > 0) {
            return $pipSize;
        }

        if (class_exists('RiskService') && method_exists('RiskService', 'pipSize')) {
            return (float)RiskService::pipSize((string)$symbolCode);
        }

        $symbolCode = strtoupper((string)$symbolCode);

        if ($symbolCode === 'XAUUSD') return 0.1;
        if ($symbolCode === 'XAGUSD') return 0.01;
        if (strpos($symbolCode, 'JPY') !== false) return 0.01;
        if (dashboard_symbol_asset_type_safe($symbolCode) === 'CRYPTO') return 1.0;

        return 0.0001;
    }
}

if (!function_exists('dashboard_symbol_contract_size_safe')) {
    function dashboard_symbol_contract_size_safe($symbolCode, $entryPrice = 0): float
    {
        $config = dashboard_symbol_config_safe($symbolCode);
        $contractSize = (float)($config['contract_size'] ?? 0);

        if ($contractSize > 0) {
            return $contractSize;
        }

        if (class_exists('RiskService') && method_exists('RiskService', 'valuePerOnePriceMovePerLot')) {
            return (float)RiskService::valuePerOnePriceMovePerLot((string)$symbolCode, (float)$entryPrice);
        }

        $symbolCode = strtoupper((string)$symbolCode);

        if ($symbolCode === 'XAUUSD' || (float)$entryPrice > 1000) return 100.0;
        if ($symbolCode === 'XAGUSD') return 5000.0;
        if (dashboard_symbol_asset_type_safe($symbolCode) === 'CRYPTO') return 1.0;
        if (preg_match('/^[A-Z]{6}$/', $symbolCode)) return 100000.0;

        return 1.0;
    }
}

if (!function_exists('dashboard_symbol_min_lot_safe')) {
    function dashboard_symbol_min_lot_safe($symbolCode): float
    {
        $config = dashboard_symbol_config_safe($symbolCode);
        $minLot = (float)($config['min_lot'] ?? 0);

        if ($minLot > 0) {
            return $minLot;
        }

        if (class_exists('RiskService') && method_exists('RiskService', 'minLotForSymbol')) {
            return (float)RiskService::minLotForSymbol((string)$symbolCode);
        }

        return dashboard_symbol_asset_type_safe($symbolCode) === 'CRYPTO' ? 0.001 : 0.01;
    }
}

if (!function_exists('dashboard_symbol_lot_step_safe')) {
    function dashboard_symbol_lot_step_safe($symbolCode): float
    {
        $config = dashboard_symbol_config_safe($symbolCode);
        $lotStep = (float)($config['lot_step'] ?? 0);

        if ($lotStep > 0) {
            return $lotStep;
        }

        if (class_exists('RiskService') && method_exists('RiskService', 'lotStepForSymbol')) {
            return (float)RiskService::lotStepForSymbol((string)$symbolCode);
        }

        return dashboard_symbol_asset_type_safe($symbolCode) === 'CRYPTO' ? 0.001 : 0.01;
    }
}

if (!function_exists('dashboard_lot_precision_safe')) {
    function dashboard_lot_precision_safe($step): int
    {
        $step = (float)$step;

        if (class_exists('RiskService') && method_exists('RiskService', 'lotPrecision')) {
            return (int)RiskService::lotPrecision($step);
        }

        if ($step <= 0) {
            return 2;
        }

        $text = rtrim(rtrim(sprintf('%.8F', $step), '0'), '.');
        $pos = strpos($text, '.');
        return $pos === false ? 0 : min(8, strlen($text) - $pos - 1);
    }
}


if (!function_exists('zone_distance_display')) {
    function zone_distance_display($price, $zoneLow, $zoneHigh, $symbolCode, $decimals)
    {
        if ($price === null || $zoneLow === null || $zoneHigh === null || $zoneLow === '' || $zoneHigh === '') {
            return 'N/A';
        }

        $price = (float)$price;
        $low = min((float)$zoneLow, (float)$zoneHigh);
        $high = max((float)$zoneLow, (float)$zoneHigh);

        if ($price >= $low && $price <= $high) {
            return 'Inside zone';
        }

        $distance = $price < $low ? ($low - $price) : ($price - $high);

        return trade_distance_label($distance, $symbolCode, $decimals);
    }
}

if (!function_exists('trade_distance_label')) {
    function trade_distance_label($distance, $symbolCode, $decimals)
    {
        if ($distance === null || $distance === '') {
            return 'N/A';
        }

        $distance = abs((float)$distance);
        $symbolCode = strtoupper(str_replace(['/', '-', '_', ' '], '', trim((string)$symbolCode)));
        $decimals = dashboard_symbol_decimals_safe($symbolCode, (int)$decimals);
        $pipSize = dashboard_symbol_pip_size_safe($symbolCode);
        $pips = $pipSize > 0 ? ($distance / $pipSize) : 0;
        $assetType = dashboard_symbol_asset_type_safe($symbolCode);

        if ($symbolCode === 'XAUUSD') {
            return fp($distance, $decimals) . ' (' . number_format($pips, 0) . ' pips)';
        }

        if ($symbolCode === 'XAGUSD') {
            return fp($distance, $decimals) . ' (' . number_format($pips, 0) . ' pips)';
        }

        if ($assetType === 'CRYPTO' || $assetType === 'INDICES') {
            return fp($distance, $decimals) . ' (' . number_format($pips, 0) . ' points)';
        }

        return fp($distance, $decimals) . ' (' . number_format($pips, 1) . ' pips)';
    }
}




if (!function_exists('scenario_risk_price_distance')) {
    function scenario_risk_price_distance($distance, $symbolCode, $referencePrice = null, array $style = [], $atr = null)
    {
        if ($distance === null || $distance === '' || !is_numeric($distance)) {
            return null;
        }

        $distance = abs((float)$distance);
        $symbolCode = strtoupper(str_replace(['/', '-', '_', ' '], '', trim((string)$symbolCode)));

        if ($symbolCode === 'XAUUSD') {
            /*
            |--------------------------------------------------------------------------
            | XAUUSD scenario risk normalization
            |--------------------------------------------------------------------------
            | The Execution Quality block may expose the dynamic SL as "Gold points"
            | while also showing the equivalent pip count, e.g.:
            | 3.04 Gold points = 304 pips.
            |--------------------------------------------------------------------------
            */
            if ($distance > 0 && $distance <= 10) {
                return $distance * 10;
            }

            if ($distance >= 100) {
                return $distance * 0.1;
            }

            return $distance;
        }

        /*
        |--------------------------------------------------------------------------
        | Crypto scenario risk normalization
        |--------------------------------------------------------------------------
        | BTC and ETH must not inherit forex-style or generic crypto distances.
        | A raw dashboard risk distance can become unrealistic when ATR/structure
        | produces a very wide value. For ETHUSD this created 800+ point stops and
        | targets thousands of dollars away. The planner now clamps crypto scenarios
        | to a symbol/style-aware, volatility-aware but practical distance.
        |--------------------------------------------------------------------------
        */
        if (function_exists('dashboard_symbol_asset_type_safe') && dashboard_symbol_asset_type_safe($symbolCode) === 'CRYPTO') {
            $referencePrice = is_numeric($referencePrice) ? abs((float)$referencePrice) : 0.0;
            $styleKey = strtolower((string)($style['style_key'] ?? 'intraday'));
            $atr = is_numeric($atr) ? abs((float)$atr) : 0.0;

            $isBtc = strpos($symbolCode, 'BTC') === 0;
            $isEth = strpos($symbolCode, 'ETH') === 0;

            if ($isBtc) {
                if ($styleKey === 'scalping') {
                    $base = 200.0; $min = 100.0; $maxPct = 0.012;
                } elseif ($styleKey === 'swing') {
                    $base = 1500.0; $min = 600.0; $maxPct = 0.060;
                } elseif ($styleKey === 'auto') {
                    $base = 600.0; $min = 250.0; $maxPct = 0.025;
                } else {
                    $base = 500.0; $min = 200.0; $maxPct = 0.025;
                }
            } elseif ($isEth) {
                if ($styleKey === 'scalping') {
                    $base = 10.0; $min = 6.0; $maxPct = 0.012;
                } elseif ($styleKey === 'swing') {
                    $base = 75.0; $min = 30.0; $maxPct = 0.060;
                } elseif ($styleKey === 'auto') {
                    $base = 30.0; $min = 12.0; $maxPct = 0.025;
                } else {
                    $base = 25.0; $min = 10.0; $maxPct = 0.025;
                }
            } else {
                if ($styleKey === 'scalping') {
                    $base = max(1.0, $referencePrice * 0.008); $min = max(0.5, $referencePrice * 0.004); $maxPct = 0.015;
                } elseif ($styleKey === 'swing') {
                    $base = max(1.0, $referencePrice * 0.040); $min = max(0.5, $referencePrice * 0.015); $maxPct = 0.080;
                } else {
                    $base = max(1.0, $referencePrice * 0.018); $min = max(0.5, $referencePrice * 0.006); $maxPct = 0.030;
                }
            }

            $max = $referencePrice > 0 ? max($base * 2.0, $referencePrice * $maxPct) : ($base * 2.0);
            $atrCandidate = $atr > 0 ? $atr * ($styleKey === 'swing' ? 2.5 : ($styleKey === 'scalping' ? 1.0 : 1.5)) : 0.0;
            $preferred = max($base, $atrCandidate);
            $preferred = max($min, min($preferred, $max));

            if ($distance <= 0) {
                return $preferred;
            }

            if ($distance < $min || $distance > $max) {
                return $preferred;
            }

            return $distance;
        }

        return $distance;
    }
}



if (!function_exists('scenario_target_rr_label')) {
    function scenario_target_rr_label($target, $entry, $rr, $symbolCode, $decimals)
    {
        if ($target === null || $entry === null || !is_numeric($target) || !is_numeric($entry)) {
            return 'N/A';
        }

        $distance = abs((float)$target - (float)$entry);
        return 'TP' . (int)$rr . ' (' . (int)$rr . 'R): ' . fp($target, (int)$decimals) . ' - ' . trade_distance_label($distance, $symbolCode, $decimals);
    }
}



if (!function_exists('scenario_target_rr_parts')) {
    function scenario_target_rr_parts($target, $entry, $rr, $symbolCode, $decimals)
    {
        if ($target === null || $entry === null || !is_numeric($target) || !is_numeric($entry)) {
            return null;
        }

        $target = (float)$target;
        $entry = (float)$entry;
        $distance = abs($target - $entry);

        return [
            'rr_label' => 'TP' . (int)$rr . ' (' . (int)$rr . 'R)',
            'target_label' => fp($target, (int)$decimals),
            'distance_label' => trade_distance_label($distance, $symbolCode, $decimals),
        ];
    }
}

if (!function_exists('dashboard_active_tracking_exists')) {
    function dashboard_active_tracking_exists($userId, $symbol, $timeframe, $side = null, $setupKey = null)
    {
        if (!$userId || !$symbol || !$timeframe) {
            return false;
        }

        try {
            $where = "
                user_id = ?
                AND symbol = ?
                AND timeframe = ?
                AND is_active = 1
                AND triggered_at IS NULL
                AND lifecycle_stage NOT IN ('ALERT_SENT', 'INVALIDATED', 'EXPIRED')
                AND created_at >= DATE_SUB(NOW(), INTERVAL 7 DAY)
            ";

            $params = [$userId, $symbol, $timeframe];

            if ($side !== null && $side !== '') {
                $where .= " AND side = ?";
                $params[] = strtoupper((string)$side);
            }

            /*
            |--------------------------------------------------------------------------
            | Important:
            |--------------------------------------------------------------------------
            | If dashboard generated a setup_key for the current analysis, use it.
            | This prevents a previously tracked setup on the same pair/timeframe from
            | making a new analysis look like it is already being tracked.
            |--------------------------------------------------------------------------
            */
            if ($setupKey !== null && $setupKey !== '') {
                $where .= " AND setup_key = ?";
                $params[] = (string)$setupKey;
            }

            $stmt = db()->prepare("SELECT COUNT(*) FROM institutional_alerts WHERE {$where}");
            $stmt->execute($params);

            return (int)$stmt->fetchColumn() > 0;
        } catch (Throwable $e) {
            return false;
        }
    }
}

if (!function_exists('dashboard_safe_separator')) {
    function dashboard_safe_separator()
    {
        return ' | ';
    }
}

if (!function_exists('dashboard_quote_source_label')) {
    function dashboard_quote_source_label(array $quote)
    {
        $source = strtolower((string)($quote['source'] ?? ''));
        $provider = strtolower((string)($quote['provider'] ?? ''));

        if ($source === 'live_ticker' || strpos($provider, 'binance') === 0) {
            if ($provider === 'binance_futures') return 'Live Binance Futures ticker';
            if ($provider === 'binance_spot') return 'Live Binance Spot ticker';
            return 'Live Binance ticker';
        }

        if ($source === 'live_quote' || $provider === 'twelvedata') {
            return 'Live provider quote';
        }

        if ($source === 'latest_candle_close' || $provider === 'database') {
            return 'Latest saved candle close';
        }

        return 'Market quote';
    }
}


if (!function_exists('dashboard_crypto_symbol_safe')) {
    function dashboard_crypto_symbol_safe($symbolCode): bool
    {
        $symbolCode = strtoupper(str_replace(['/', '-', '_', ' '], '', trim((string)$symbolCode)));
        return (bool)preg_match('/^(BTC|ETH|SOL|BNB|XRP|ADA|DOGE|AVAX|LINK|LTC|BCH|DOT|MATIC|TRX|ATOM|NEAR|APT|OP|ARB)/', $symbolCode);
    }
}

if (!function_exists('dashboard_crypto_provider_symbol_safe')) {
    function dashboard_crypto_provider_symbol_safe($symbolCode): string
    {
        $symbolCode = strtoupper(str_replace(['/', '-', '_', ' '], '', trim((string)$symbolCode)));
        if (class_exists('ApiCreditGuard') && method_exists('ApiCreditGuard', 'symbolProviderSymbol')) {
            return ApiCreditGuard::symbolProviderSymbol($symbolCode, [], 'binance');
        }
        if (preg_match('/USD$/', $symbolCode)) return preg_replace('/USD$/', 'USDT', $symbolCode);
        if (!str_ends_with($symbolCode, 'USDT')) return $symbolCode . 'USDT';
        return $symbolCode;
    }
}

if (!function_exists('dashboard_http_json_safe')) {
    function dashboard_http_json_safe($url, $timeout = 7): array
    {
        if (class_exists('ApiCreditGuard') && method_exists('ApiCreditGuard', 'httpJson')) {
            return ApiCreditGuard::httpJson((string)$url, (int)$timeout);
        }
        $ctx = stream_context_create(['http' => ['timeout' => (int)$timeout, 'ignore_errors' => true, 'header' => "User-Agent: InstitutionalTradingAssistant/1.0\r\n"]]);
        $raw = @file_get_contents((string)$url, false, $ctx);
        if ($raw === false || trim($raw) === '') return ['ok' => false, 'error' => 'Empty HTTP response.'];
        $json = json_decode($raw, true);
        return is_array($json) ? ['ok' => true, 'data' => $json] : ['ok' => false, 'error' => 'Invalid JSON response.'];
    }
}

if (!function_exists('dashboard_crypto_live_quote_safe')) {
    function dashboard_crypto_live_quote_safe($symbolCode): array
    {
        $providerSymbol = dashboard_crypto_provider_symbol_safe($symbolCode);
        $endpoints = [
            ['provider' => 'binance_futures', 'url' => 'https://fapi.binance.com/fapi/v1/ticker/price?' . http_build_query(['symbol' => $providerSymbol])],
            ['provider' => 'binance_spot', 'url' => 'https://api.binance.com/api/v3/ticker/price?' . http_build_query(['symbol' => $providerSymbol])],
            ['provider' => 'binance_vision', 'url' => 'https://data-api.binance.vision/api/v3/ticker/price?' . http_build_query(['symbol' => $providerSymbol])],
        ];
        $lastError = '';
        foreach ($endpoints as $endpoint) {
            $response = dashboard_http_json_safe($endpoint['url'], 7);
            if (empty($response['ok'])) {
                $lastError = $response['error'] ?? 'HTTP failed';
                continue;
            }
            $data = $response['data'];
            if (isset($data['price']) && is_numeric($data['price'])) {
                return [
                    'success' => true,
                    'provider' => $endpoint['provider'],
                    'source' => 'live_ticker',
                    'provider_symbol' => $providerSymbol,
                    'price' => (float)$data['price'],
                    'timestamp' => gmdate('Y-m-d H:i:s'),
                ];
            }
            $lastError = 'No numeric price returned.';
        }
        return ['success' => false, 'provider' => 'binance', 'source' => 'live_ticker_failed', 'provider_symbol' => $providerSymbol, 'error' => $lastError ?: 'All Binance endpoints failed.'];
    }
}

if (!function_exists('dashboard_user_alert_preferences')) {
    function dashboard_user_alert_preferences($userId)
    {
        $defaults = [
            /*
            |--------------------------------------------------------------------------
            | Advisory-first defaults
            |--------------------------------------------------------------------------
            | News should not stop setup generation. These preferences only control
            | how strongly the dashboard warns the user around news windows.
            |--------------------------------------------------------------------------
            */
            'avoid_news_trades' => 0,
            'trade_high_impact_news' => 1,
            'trade_medium_impact_news' => 1,
            'news_block_minutes_before' => 60,
            'news_block_minutes_after' => 30,
            'notify_stage_1' => 1,
            'notify_stage_2' => 1,
            'notify_stage_3' => 1,
            'notify_final_setup' => 1,
            'auto_expire_enabled' => 0,
            'expiration_option' => 'never',
        ];

        if (!$userId) {
            return $defaults;
        }

        try {
            $stmt = db()->prepare("SHOW COLUMNS FROM users");
            $stmt->execute();
            $columns = array_map('strtolower', $stmt->fetchAll(PDO::FETCH_COLUMN) ?: []);

            $select = [];
            foreach (array_keys($defaults) as $column) {
                if (in_array(strtolower($column), $columns, true)) {
                    $select[] = $column;
                }
            }

            if (empty($select)) {
                return $defaults;
            }

            $stmt = db()->prepare("SELECT " . implode(', ', $select) . " FROM users WHERE id = ? LIMIT 1");
            $stmt->execute([$userId]);
            $row = $stmt->fetch(PDO::FETCH_ASSOC) ?: [];
            return array_merge($defaults, $row);
        } catch (Throwable $e) {
            return $defaults;
        }
    }
}


if (!function_exists('dashboard_db_news_events')) {
    function dashboard_db_news_events($symbolCode, $minutesAhead = 1440, $minutesBehind = 360, $symbolOnly = false)
    {
        try {
            $pdo = db();
            $symbolCode = strtoupper(str_replace('/', '', trim((string)$symbolCode)));
            $now = time();
            $start = $now - ((int)$minutesBehind * 60);
            $end = $now + ((int)$minutesAhead * 60);

            $tables = $pdo->query("SHOW TABLES LIKE 'news_events'")->fetchAll(PDO::FETCH_COLUMN);
            if (empty($tables)) {
                return [];
            }

            /*
            |--------------------------------------------------------------------------
            | Important display rule
            |--------------------------------------------------------------------------
            | calendar source sometimes stores all-day / tentative events as 00:00:00.
            | At 01:00+ Lagos time those rows look "past" and were being filtered out.
            | We therefore include:
            | 1) normal events inside the active look-back/look-ahead window, OR
            | 2) events dated today/tomorrow, so midnight all-day calendar rows still
            |    remain visible to the user for the trading session.
            |--------------------------------------------------------------------------
            */
            $stmt = $pdo->prepare("
                SELECT *
                FROM news_events
                WHERE
                    UPPER(COALESCE(impact_level, impact, 'LOW')) IN ('LOW','MEDIUM','HIGH')
                    AND (
                        event_timestamp BETWEEN :start_ts AND :end_ts
                        OR event_time BETWEEN FROM_UNIXTIME(:start_dt) AND FROM_UNIXTIME(:end_dt)
                        OR DATE(event_time) BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 1 DAY)
                    )
                ORDER BY COALESCE(event_timestamp, UNIX_TIMESTAMP(event_time)) ASC
                LIMIT 150
            ");
            $stmt->execute([
                ':start_ts' => $start,
                ':end_ts' => $end,
                ':start_dt' => $start,
                ':end_dt' => $end,
            ]);

            $events = [];

            foreach (($stmt->fetchAll(PDO::FETCH_ASSOC) ?: []) as $row) {
                $title = trim((string)($row['title'] ?? $row['event'] ?? 'Economic event'));
                $currency = strtoupper(trim((string)($row['currency'] ?? '')));
                $impact = dashboard_news_event_impact($row);
                $ts = dashboard_event_client_timestamp($row);

                if (!$ts) {
                    continue;
                }

                $eventDate = date('Y-m-d', $ts);
                $today = date('Y-m-d');
                $tomorrow = date('Y-m-d', strtotime('+1 day'));
                $isTodayOrTomorrow = in_array($eventDate, [$today, $tomorrow], true);
                $insideWindow = ($ts >= $start && $ts <= $end);

                if (!$insideWindow && !$isTodayOrTomorrow) {
                    continue;
                }

                $candidate = [
                    'title' => $title,
                    'event' => $title,
                    'currency' => $currency,
                    'impact' => $impact,
                    'time' => $row['event_time'] ?? date('Y-m-d H:i', $ts),
                    'event_time' => $row['event_time'] ?? null,
                    'timestamp' => $ts,
                    'event_timestamp' => $ts,
                    'raw_json' => $row['raw_json'] ?? null,
                    'seconds_until' => $ts - $now,
                    'minutes_until' => (int)round(($ts - $now) / 60),
                    'countdown_label' => $ts >= $now ? dashboard_countdown_label($ts - $now) : ($isTodayOrTomorrow ? 'Today' : 'Recently passed'),
                    'actual' => $row['actual'] ?? null,
                    'forecast' => $row['forecast'] ?? null,
                    'previous' => $row['previous'] ?? null,
                    'source' => $row['source'] ?? 'news_events',
                    'impact_level' => $row['impact_level'] ?? $impact,
                    'impact_label' => $row['impact_label'] ?? null,
                    'impact_raw' => $row['impact_raw'] ?? null,
                    'impact_source' => $row['impact_source'] ?? null,
                    'folder_class' => $row['folder_class'] ?? null,
                ];

                $candidate['affects_symbol'] = dashboard_event_affects_symbol($candidate, $symbolCode);

                if ($symbolOnly && empty($candidate['affects_symbol'])) {
                    continue;
                }

                $events[] = $candidate;
            }

            usort($events, function ($a, $b) {
                if (!empty($a['affects_symbol']) !== !empty($b['affects_symbol'])) {
                    return !empty($b['affects_symbol']) <=> !empty($a['affects_symbol']);
                }

                return ($a['timestamp'] ?? 0) <=> ($b['timestamp'] ?? 0);
            });

            return $events;
        } catch (Throwable $e) {
            return [];
        }
    }
}


if (!function_exists('dashboard_db_recent_releases')) {
    function dashboard_db_recent_releases($symbolCode, $hoursBehind = 24)
    {
        try {
            $pdo = db();
            $symbolCode = strtoupper(str_replace('/', '', trim((string)$symbolCode)));
            $now = time();
            $start = $now - ((int)$hoursBehind * 3600);

            $tables = $pdo->query("SHOW TABLES LIKE 'news_events'")->fetchAll(PDO::FETCH_COLUMN);
            if (empty($tables)) {
                return [];
            }

            $stmt = $pdo->prepare("
                SELECT *
                FROM news_events
                WHERE
                    UPPER(COALESCE(impact_level, impact, 'LOW')) IN ('LOW','MEDIUM','HIGH')
                    AND (
                        event_timestamp BETWEEN :start_ts AND :now_ts
                        OR event_time BETWEEN FROM_UNIXTIME(:start_dt) AND FROM_UNIXTIME(:now_dt)
                        OR DATE(event_time) = CURDATE()
                    )
                ORDER BY COALESCE(event_timestamp, UNIX_TIMESTAMP(event_time)) DESC
                LIMIT 120
            ");

            $stmt->execute([
                ':start_ts' => $start,
                ':now_ts' => $now,
                ':start_dt' => $start,
                ':now_dt' => $now,
            ]);

            $events = [];

            foreach (($stmt->fetchAll(PDO::FETCH_ASSOC) ?: []) as $row) {
                $title = trim((string)($row['title'] ?? $row['event'] ?? 'Economic event'));
                $currency = strtoupper(trim((string)($row['currency'] ?? '')));
                $impact = dashboard_news_event_impact($row);

                $candidate = [
                    'title' => $title,
                    'event' => $title,
                    'currency' => $currency,
                    'impact' => $impact,
                    'time' => $row['event_time'] ?? '',
                    'timestamp' => $row['event_timestamp'] ?? null,
                    'event_timestamp' => $row['event_timestamp'] ?? null,
                    'event_time' => $row['event_time'] ?? null,
                    'actual' => $row['actual'] ?? null,
                    'forecast' => $row['forecast'] ?? null,
                    'previous' => $row['previous'] ?? null,
                    'source' => $row['source'] ?? 'news_events',
                    'impact_level' => $row['impact_level'] ?? $impact,
                    'impact_label' => $row['impact_label'] ?? null,
                    'impact_raw' => $row['impact_raw'] ?? null,
                    'impact_source' => $row['impact_source'] ?? null,
                    'folder_class' => $row['folder_class'] ?? null,
                ];

                $displayTs = dashboard_event_client_timestamp($candidate);

                if ($displayTs <= 0 || $displayTs > $now || $displayTs < $start) {
                    continue;
                }

                $candidate['__display_ts'] = $displayTs;
                $candidate['affects_symbol'] = dashboard_event_affects_symbol($candidate, $symbolCode);

                $events[] = $candidate;
            }

            usort($events, function ($a, $b) {
                return ($b['__display_ts'] ?? dashboard_event_client_timestamp($b)) <=> ($a['__display_ts'] ?? dashboard_event_client_timestamp($a));
            });

            return $events;
        } catch (Throwable $e) {
            return [];
        }
    }
}



if (!function_exists('dashboard_upcoming_symbol_news_risk')) {
    function dashboard_upcoming_symbol_news_risk($symbolCode, $minutesAhead = 1440): array
    {
        $now = time();
        $futureLimit = $now + ((int)$minutesAhead * 60);
        $events = function_exists('dashboard_db_news_events')
            ? dashboard_db_news_events($symbolCode, (int)$minutesAhead, 0, true)
            : [];

        $highest = 'LOW';
        $primary = null;

        foreach ($events as $event) {
            if (!is_array($event)) {
                continue;
            }

            $ts = function_exists('dashboard_event_client_timestamp')
                ? (int)($event['__display_ts'] ?? dashboard_event_client_timestamp($event))
                : (int)($event['timestamp'] ?? $event['event_timestamp'] ?? 0);

            if ($ts <= $now || $ts > $futureLimit) {
                continue;
            }

            $hasActual = function_exists('dashboard_event_has_actual_value')
                ? dashboard_event_has_actual_value($event)
                : (!empty($event['actual']) && strtoupper((string)$event['actual']) !== 'NULL');

            if ($hasActual) {
                continue;
            }

            if (empty($event['affects_symbol'])) {
                continue;
            }

            $impact = strtoupper((string)($event['impact_level'] ?? $event['impact'] ?? 'LOW'));
            if (!in_array($impact, ['LOW', 'MEDIUM', 'HIGH'], true)) {
                $impact = function_exists('dashboard_forex_factory_impact')
                    ? dashboard_forex_factory_impact($event)
                    : 'LOW';
            }

            if ($primary === null || dashboard_news_impact_rank_safe($impact) > dashboard_news_impact_rank_safe($highest)) {
                $primary = $event;
                $highest = $impact;
            }

            if ($highest === 'HIGH') {
                break;
            }
        }

        return [
            'risk' => $highest,
            'event' => $primary,
        ];
    }
}


if (!function_exists('dashboard_db_news_risk')) {
    function dashboard_db_news_risk($symbolCode, $minutesAhead = 360, $minutesBehind = 60)
    {
        $events = dashboard_db_news_events($symbolCode, $minutesAhead, $minutesBehind);

        if (empty($events)) {
            return [
                'has_news' => false,
                'has_scheduled_event' => false,
                'risk' => 'LOW',
                'scheduled_risk' => 'LOW',
                'score_penalty' => 0,
                'confidence_adjustment' => 0,
                'title' => '',
                'time' => '',
                'minutes_until' => null,
                'countdown_label' => '',
                'message' => 'No calendar source news found in the local news_events table for this symbol/time window.',
                'events' => [],
                'scheduled_events' => [],
                'source' => 'news_events_direct_empty',
            ];
        }

        $riskEvents = array_values(array_filter($events, static function ($event) {
            return !empty($event['affects_symbol']);
        }));

        $eventsForRisk = !empty($riskEvents) ? $riskEvents : [];

        $highest = 'LOW';
        foreach ($eventsForRisk as $event) {
            $impact = strtoupper((string)($event['impact_level'] ?? $event['impact'] ?? 'LOW'));

            if ($impact === 'HIGH') {
                $highest = 'HIGH';
                break;
            }

            if ($impact === 'MEDIUM') {
                $highest = 'MEDIUM';
            }
        }

        $primary = $riskEvents[0] ?? $events[0];
        $minutes = $primary['minutes_until'] ?? null;
        $penalty = $highest === 'HIGH' ? -10 : ($highest === 'MEDIUM' ? -4 : 0);
        $executionMode = $highest === 'HIGH' ? 'NEWS_CAUTION' : ($highest === 'MEDIUM' ? 'NEWS_REDUCED_RISK' : 'NORMAL');

        if (empty($riskEvents)) {
            $message = 'calendar events are available for global market awareness, but none directly match this symbol in the active window.';
        } elseif ($highest === 'HIGH') {
            $message = 'High-impact calendar event detected for this symbol. Use reduced risk and confirmation.';
        } elseif ($highest === 'MEDIUM') {
            $message = 'Medium-impact calendar event detected for this symbol. Monitor conditions and confirm execution.';
        } else {
            $message = 'Low-impact calendar event detected for this symbol. Displayed for awareness only.';
        }

        return [
            'has_news' => true,
            'has_scheduled_event' => true,
            'risk' => $highest,
            'scheduled_risk' => $highest,
            'advisory_level' => $highest,
            'execution_mode' => $executionMode,
            'score_penalty' => $penalty,
            'confidence_adjustment' => $penalty,
            'trade_allowed' => true,
            'user_allows_news_trading' => true,
            'title' => $primary['title'] ?? 'Economic event',
            'time' => $primary['time'] ?? '',
            'minutes_until' => $minutes,
            'countdown_label' => $primary['countdown_label'] ?? '',
            'message' => $message,
            'events' => $events,
            'scheduled_events' => $events,
            'source' => 'news_events_direct',
        ];
    }
}


if (!function_exists('dashboard_major_forex_news')) {
    function dashboard_major_forex_news($symbolCode, $minutesAhead = 240)
    {
        $events = [];

        try {
            $dbEvents = dashboard_db_news_events((string)$symbolCode, max((int)$minutesAhead, 1440), 360, false);
            if (is_array($dbEvents) && !empty($dbEvents)) {
                $events = array_merge($events, $dbEvents);
            }
        } catch (Throwable $e) {
            // Ignore direct DB source and continue.
        }

        try {
            if (class_exists('EconomicCalendarService') && method_exists('EconomicCalendarService', 'getUpcomingEvents')) {
                $calendarEvents = EconomicCalendarService::getUpcomingEvents((string)$symbolCode, (int)$minutesAhead, 30);
                if (is_array($calendarEvents)) {
                    $events = array_merge($events, $calendarEvents);
                }
            }
        } catch (Throwable $e) {
            // Ignore and continue to other sources.
        }

        try {
            if (class_exists('NewsAggregatorService') && method_exists('NewsAggregatorService', 'getMajorForexEvents')) {
                $aggregatorEvents = NewsAggregatorService::getMajorForexEvents((int)$minutesAhead);
                if (is_array($aggregatorEvents)) {
                    $events = array_merge($events, $aggregatorEvents);
                }
            }
        } catch (Throwable $e) {
            // Ignore and continue.
        }

        return dashboard_normalize_upcoming_news_events($events, (string)$symbolCode, (int)$minutesAhead);
    }
}

if (!function_exists('dashboard_event_seconds_until')) {
    function dashboard_event_seconds_until(array $event)
    {
        $timestamp = $event['timestamp'] ?? null;
        if (!$timestamp && !empty($event['time'])) {
            $timestamp = strtotime((string)$event['time']);
        }
        return $timestamp ? ((int)$timestamp - time()) : 0;
    }
}

if (!function_exists('dashboard_text_contains_safe')) {
    function dashboard_text_contains_safe($haystack, $needle): bool
    {
        $haystack = (string)$haystack;
        $needle = (string)$needle;

        return $needle !== '' && stripos($haystack, $needle) !== false;
    }
}

if (!function_exists('dashboard_news_impact_rank_safe')) {
    function dashboard_news_impact_rank_safe($impact): int
    {
        $impact = strtoupper((string)$impact);

        if ($impact === 'HIGH') {
            return 3;
        }

        if ($impact === 'MEDIUM') {
            return 2;
        }

        return 1;
    }
}


if (!function_exists('dashboard_utc_wall_time_timestamp')) {
    function dashboard_utc_wall_time_timestamp($value): int
    {
        $raw = trim((string)$value);
        if ($raw === '') {
            return 0;
        }

        try {
            $dt = new DateTime($raw, new DateTimeZone('UTC'));
            return $dt->getTimestamp();
        } catch (Throwable $e) {
            $ts = strtotime($raw);
            return $ts ? (int)$ts : 0;
        }
    }
}

if (!function_exists('dashboard_forexfactory_raw_timestamp')) {
    function dashboard_forexfactory_raw_timestamp(array $event): int
    {
        $rawJson = $event['raw_json'] ?? null;
        if (!$rawJson) {
            return 0;
        }

        $raw = is_array($rawJson) ? $rawJson : json_decode((string)$rawJson, true);
        if (!is_array($raw)) {
            return 0;
        }

        $payload = $raw;
        if (isset($raw['raw']) && is_array($raw['raw'])) {
            $payload = $raw['raw'];
        }

        $date = trim((string)($payload['date'] ?? ''));
        $time = trim((string)($payload['time'] ?? ''));

        if ($date === '') {
            return 0;
        }

        if ($time === '' || strtolower($time) === 'all day' || strtolower($time) === 'tentative') {
            $time = '00:00';
        }

        $year = date('Y');
        foreach ([$date . ' ' . $time, $date . ' ' . $year . ' ' . $time, str_replace(['-', '.'], '/', $date) . ' ' . $time] as $candidate) {
            $ts = dashboard_utc_wall_time_timestamp($candidate);
            if ($ts > 0) {
                return $ts;
            }
        }

        return 0;
    }
}

if (!function_exists('dashboard_known_release_timestamp')) {
    function dashboard_known_release_timestamp(array $event): int
    {
        $title = strtoupper((string)($event['title'] ?? $event['event'] ?? ''));
        $currency = strtoupper((string)($event['currency'] ?? ''));

        if ($currency !== 'USD') {
            return 0;
        }

        $eventDate = '';
        if (!empty($event['event_time'])) {
            $eventDate = substr((string)$event['event_time'], 0, 10);
        } elseif (!empty($event['time'])) {
            $eventDate = substr((string)$event['time'], 0, 10);
        }

        if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $eventDate)) {
            $ts = !empty($event['timestamp']) && is_numeric($event['timestamp'])
                ? (int)$event['timestamp']
                : (!empty($event['event_timestamp']) && is_numeric($event['event_timestamp']) ? (int)$event['event_timestamp'] : 0);

            if ($ts > 0) {
                $eventDate = gmdate('Y-m-d', $ts > 20000000000 ? (int)floor($ts / 1000) : $ts);
            }
        }

        if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $eventDate)) {
            return 0;
        }

        $nyTime = null;

        if (
            dashboard_text_contains_safe($title, 'CORE PCE PRICE INDEX')
            || dashboard_text_contains_safe($title, 'PCE PRICE INDEX')
            || dashboard_text_contains_safe($title, 'PERSONAL INCOME')
            || dashboard_text_contains_safe($title, 'PERSONAL SPENDING')
            || dashboard_text_contains_safe($title, 'UNEMPLOYMENT CLAIMS')
            || dashboard_text_contains_safe($title, 'JOBLESS CLAIMS')
            || dashboard_text_contains_safe($title, 'CPI')
            || dashboard_text_contains_safe($title, 'PPI')
            || dashboard_text_contains_safe($title, 'NONFARM')
            || dashboard_text_contains_safe($title, 'NFP')
            || dashboard_text_contains_safe($title, 'GDP')
            || dashboard_text_contains_safe($title, 'RETAIL SALES')
            || dashboard_text_contains_safe($title, 'DURABLE GOODS')
        ) {
            $nyTime = '08:30:00';
        } elseif (
            dashboard_text_contains_safe($title, 'ISM')
            || dashboard_text_contains_safe($title, 'PMI')
            || dashboard_text_contains_safe($title, 'CONSUMER CONFIDENCE')
            || dashboard_text_contains_safe($title, 'JOLTS')
            || dashboard_text_contains_safe($title, 'NEW HOME SALES')
            || dashboard_text_contains_safe($title, 'EXISTING HOME SALES')
            || dashboard_text_contains_safe($title, 'PENDING HOME SALES')
            || dashboard_text_contains_safe($title, 'FACTORY ORDERS')
        ) {
            $nyTime = '10:00:00';
        }

        if ($nyTime === null) {
            return 0;
        }

        try {
            $dt = new DateTime($eventDate . ' ' . $nyTime, new DateTimeZone('America/New_York'));
            return $dt->getTimestamp();
        } catch (Throwable $e) {
            return 0;
        }
    }
}

if (!function_exists('dashboard_event_client_timestamp')) {
    function dashboard_event_client_timestamp(array $event): int
    {
        $knownTs = dashboard_known_release_timestamp($event);
        if ($knownTs > 0) {
            return $knownTs;
        }

        foreach (['event_time', 'time', 'date', 'datetime'] as $key) {
            if (!empty($event[$key])) {
                $ts = dashboard_utc_wall_time_timestamp($event[$key]);
                if ($ts > 0) {
                    return $ts;
                }
            }
        }

        foreach (['timestamp', 'event_timestamp'] as $key) {
            if (!empty($event[$key]) && is_numeric($event[$key])) {
                $ts = (int)$event[$key];
                return $ts > 20000000000 ? (int)floor($ts / 1000) : $ts;
            }
        }

        return dashboard_forexfactory_raw_timestamp($event);
    }
}

if (!function_exists('dashboard_event_server_time_label')) {
    function dashboard_event_server_time_label(int $timestamp): string
    {
        if ($timestamp <= 0) {
            return 'Time pending';
        }

        try {
            $dt = new DateTime('@' . $timestamp);
            $dt->setTimezone(new DateTimeZone('Africa/Lagos'));
            return $dt->format('M j, Y · g:i A') . ' WAT';
        } catch (Throwable $e) {
            return date('M j, Y · g:i A', $timestamp) . ' WAT';
        }
    }
}

if (!function_exists('dashboard_event_has_actual_value')) {
    function dashboard_event_has_actual_value(array $event): bool
    {
        if (!array_key_exists('actual', $event)) {
            return false;
        }

        $actual = $event['actual'];

        if ($actual === null) {
            return false;
        }

        $actual = trim((string)$actual);

        return $actual !== '' && strtoupper($actual) !== 'NULL' && strtoupper($actual) !== 'N/A';
    }
}

if (!function_exists('dashboard_event_countdown_label')) {
    function dashboard_event_countdown_label(int $timestamp, array $event = []): string
    {
        if ($timestamp <= 0) {
            return 'Countdown pending';
        }

        $diff = $timestamp - time();
        $abs = abs($diff);

        $days = intdiv($abs, 86400);
        $hours = intdiv($abs % 86400, 3600);
        $minutes = intdiv($abs % 3600, 60);

        if ($days > 0) {
            $label = $days . 'd ' . $hours . 'h';
        } elseif ($hours > 0) {
            $label = $hours . 'h ' . $minutes . 'm';
        } else {
            $label = max(0, $minutes) . 'm';
        }

        if ($diff >= 0) {
            return 'Starts in ' . $label;
        }

        return dashboard_event_has_actual_value($event)
            ? 'Released ' . $label . ' ago'
            : 'Released — awaiting actual';
    }
}

if (!function_exists('dashboard_event_actual_display')) {
    function dashboard_event_actual_display(array $event, int $timestamp = 0): string
    {
        if (dashboard_event_has_actual_value($event)) {
            return trim((string)$event['actual']);
        }

        if ($timestamp > 0 && $timestamp <= time()) {
            return 'Awaiting update';
        }

        return 'Pending';
    }
}

if (!function_exists('dashboard_calendar_impact_guidance')) {
    function dashboard_calendar_impact_guidance(string $impact): string
    {
        $impact = strtoupper(trim($impact));

        if ($impact === 'HIGH') {
            return 'High expected impact. Wait for spread to settle and require clean confirmation before execution.';
        }

        if ($impact === 'MEDIUM') {
            return 'Medium expected impact. Monitor volatility and avoid entering during the release window.';
        }

        if ($impact === 'LOW') {
            return 'Low expected impact. Displayed for awareness; it should not automatically override the setup.';
        }

        return 'No major scheduled event is currently active.';
    }
}



if (!function_exists('dashboard_news_event_timestamp')) {
    function dashboard_news_event_timestamp(array $event): ?int
    {
        foreach (['timestamp', 'event_timestamp'] as $key) {
            if (!empty($event[$key]) && is_numeric($event[$key])) {
                $ts = (int)$event[$key];
                return $ts > 20000000000 ? (int)floor($ts / 1000) : $ts;
            }
        }

        foreach (['event_time', 'time', 'date', 'datetime', 'published_at', 'created_at'] as $key) {
            if (!empty($event[$key])) {
                $ts = strtotime((string)$event[$key]);
                if ($ts) {
                    return $ts;
                }
            }
        }

        return null;
    }
}

if (!function_exists('dashboard_symbol_currencies')) {
    function dashboard_symbol_currencies(string $symbolCode): array
    {
        $symbolCode = strtoupper(str_replace('/', '', trim($symbolCode)));
        if ($symbolCode === 'XAUUSD') return ['USD', 'XAU'];
        if (strlen($symbolCode) >= 6) return [substr($symbolCode, 0, 3), substr($symbolCode, 3, 3)];
        return ['USD'];
    }
}

if (!function_exists('dashboard_news_event_impact')) {
    function dashboard_news_event_impact(array $event): string
    {
        /*
         * Production rule:
         * If impact_level exists in news_events, trust it. Do not reclassify
         * a calendar source yellow-folder CPI event as HIGH just because the
         * title contains CPI.
         */
        foreach (['impact_level', 'impact'] as $key) {
            if (!empty($event[$key])) {
                $value = strtoupper(trim((string)$event[$key]));

                if (in_array($value, ['LOW', 'MEDIUM', 'HIGH'], true)) {
                    return $value;
                }

                if (dashboard_text_contains_safe($value, 'LOW') || dashboard_text_contains_safe($value, 'YELLOW')) {
                    return 'LOW';
                }

                if (dashboard_text_contains_safe($value, 'MEDIUM') || dashboard_text_contains_safe($value, 'ORANGE') || dashboard_text_contains_safe($value, 'SEMI-RED') || dashboard_text_contains_safe($value, 'SEMI RED')) {
                    return 'MEDIUM';
                }

                if (dashboard_text_contains_safe($value, 'HIGH') || dashboard_text_contains_safe($value, 'RED')) {
                    return 'HIGH';
                }
            }
        }

        $combinedStored = strtoupper(trim(implode(' ', array_filter([
            $event['impact_label'] ?? '',
            $event['impact_raw'] ?? '',
            $event['folder_class'] ?? '',
            $event['impact_source'] ?? '',
        ]))));

        if ($combinedStored !== '') {
            if (dashboard_text_contains_safe($combinedStored, 'LOW IMPACT') || dashboard_text_contains_safe($combinedStored, 'YELLOW')) {
                return 'LOW';
            }

            if (
                dashboard_text_contains_safe($combinedStored, 'MEDIUM IMPACT') ||
                dashboard_text_contains_safe($combinedStored, 'ORANGE') ||
                dashboard_text_contains_safe($combinedStored, 'SEMI-RED') ||
                dashboard_text_contains_safe($combinedStored, 'SEMI RED')
            ) {
                return 'MEDIUM';
            }

            if (dashboard_text_contains_safe($combinedStored, 'HIGH IMPACT') || dashboard_text_contains_safe($combinedStored, 'RED')) {
                return 'HIGH';
            }
        }

        /*
         * Fallback only for older providers that do not provide stored impact.
         */
        $title = strtoupper((string)($event['title'] ?? $event['event'] ?? $event['headline'] ?? ''));

        $high = ['CPI','INFLATION','NFP','NONFARM','FOMC','FED','RATE DECISION','INTEREST RATE','GDP','PCE','POWELL','UNEMPLOYMENT','JOBLESS'];
        $medium = ['PMI','RETAIL SALES','CLAIMS','AUCTION','SPEECH','SPEAKS'];

        foreach ($high as $word) {
            if (dashboard_text_contains_safe($title, $word)) return 'HIGH';
        }
        foreach ($medium as $word) {
            if (dashboard_text_contains_safe($title, $word)) return 'MEDIUM';
        }

        return 'LOW';
    }
}

if (!function_exists('dashboard_event_affects_symbol')) {
    function dashboard_event_affects_symbol(array $event, string $symbolCode): bool
    {
        $currencies = dashboard_symbol_currencies($symbolCode);
        $currency = strtoupper(trim((string)($event['currency'] ?? $event['country'] ?? '')));
        $title = strtoupper((string)($event['title'] ?? $event['event'] ?? $event['headline'] ?? ''));
        $symbolCode = strtoupper(str_replace('/', '', trim((string)$symbolCode)));

        /*
        |--------------------------------------------------------------------------
        | XAUUSD news relevance
        |--------------------------------------------------------------------------
        | Do NOT classify every CPI/PCE/NFP title as XAUUSD-relevant.
        | Example: AUD CPI or JPY CPI should display in Upcoming Events for
        | awareness, but it should not make the XAUUSD hero News risk HIGH.
        |
        | For XAUUSD, scheduled-news risk should come from USD / US / Fed events,
        | not from non-USD calendar rows just because they contain the word CPI.
        |--------------------------------------------------------------------------
        */
        if ($symbolCode === 'XAUUSD') {
            if ($currency === 'USD' || $currency === 'US' || $currency === 'USA') {
                return true;
            }

            foreach (['USD', 'UNITED STATES', 'U.S.', 'US ', 'FED', 'FOMC', 'POWELL', 'TREASURY'] as $word) {
                if (dashboard_text_contains_safe($title, $word)) {
                    return true;
                }
            }

            return false;
        }

        foreach ($currencies as $cur) {
            if ($currency === $cur || dashboard_text_contains_safe($title, $cur)) {
                return true;
            }
        }

        /*
        | For USD pairs, only treat macro keywords as relevant when the event is
        | actually USD/US-based or clearly references the United States/Fed.
        */
        if (in_array('USD', $currencies, true)) {
            $isUsdEvent = (
                $currency === 'USD'
                || $currency === 'US'
                || $currency === 'USA'
                || dashboard_text_contains_safe($title, 'USD')
                || dashboard_text_contains_safe($title, 'UNITED STATES')
                || dashboard_text_contains_safe($title, 'FED')
                || dashboard_text_contains_safe($title, 'FOMC')
                || dashboard_text_contains_safe($title, 'POWELL')
            );

            if (!$isUsdEvent) {
                return false;
            }

            foreach (['CPI','PCE','NFP','NONFARM','JOBLESS','UNEMPLOYMENT','GDP','RETAIL SALES','ISM','PMI','INFLATION','INTEREST RATE'] as $word) {
                if (dashboard_text_contains_safe($title, $word)) {
                    return true;
                }
            }
        }

        return false;
    }
}

if (!function_exists('dashboard_countdown_label')) {
    function dashboard_countdown_label(int $seconds): string
    {
        if ($seconds <= 0) {
            return 'LIVE NOW';
        }

        $minutes = (int)floor($seconds / 60);
        if ($minutes < 60) {
            return 'in ' . $minutes . ' minutes';
        }

        return 'in ' . intdiv($minutes, 60) . 'h ' . ($minutes % 60) . 'm';
    }
}


/*
|--------------------------------------------------------------------------
| calendar source impact display helpers (must be loaded before news normalizer)
|--------------------------------------------------------------------------
| These helpers are intentionally placed before dashboard_normalize_upcoming_news_events()
| because the normalizer can call dashboard_forex_factory_impact_label() while
| dashboard.php is still executing top-to-bottom.
*/
if (!function_exists('dashboard_forex_factory_impact')) {
    function dashboard_forex_factory_impact($event)
    {
        if (!is_array($event)) {
            return 'LOW';
        }

        foreach (['impact_level', 'impact'] as $key) {
            if (!empty($event[$key])) {
                $value = strtoupper(trim((string)$event[$key]));
                if (in_array($value, ['LOW', 'MEDIUM', 'HIGH'], true)) {
                    return $value;
                }
            }
        }

        $combined = strtoupper(trim(implode(' ', array_filter([
            $event['impact_label'] ?? '',
            $event['impact_raw'] ?? '',
            $event['folder_class'] ?? '',
            $event['impact_source'] ?? '',
        ]))));

        if ($combined !== '') {
            if (dashboard_text_contains_safe($combined, 'LOW IMPACT') || dashboard_text_contains_safe($combined, 'YELLOW')) {
                return 'LOW';
            }
            if (dashboard_text_contains_safe($combined, 'MEDIUM IMPACT') || dashboard_text_contains_safe($combined, 'ORANGE') || dashboard_text_contains_safe($combined, 'SEMI-RED') || dashboard_text_contains_safe($combined, 'SEMI RED')) {
                return 'MEDIUM';
            }
            if (dashboard_text_contains_safe($combined, 'HIGH IMPACT') || dashboard_text_contains_safe($combined, 'RED')) {
                return 'HIGH';
            }
        }

        return 'LOW';
    }
}

if (!function_exists('dashboard_forex_factory_impact_label')) {
    function dashboard_forex_factory_impact_label($impact)
    {
        $impact = strtoupper((string)$impact);

        if ($impact === 'HIGH') {
            return 'High Impact Expected';
        }

        if ($impact === 'MEDIUM') {
            return 'Medium Impact Expected';
        }

        return 'Low Impact Expected';
    }
}

if (!function_exists('dashboard_normalize_upcoming_news_events')) {
    function dashboard_normalize_upcoming_news_events(array $events, string $symbolCode, int $minutesAhead = 240): array
    {
        $now = time();
        $end = $now + ($minutesAhead * 60);
        $normalized = [];
        $seen = [];

        foreach ($events as $event) {
            if (!is_array($event)) continue;

            $title = trim((string)($event['title'] ?? $event['event'] ?? $event['headline'] ?? ''));
            if ($title === '') continue;

            $ts = dashboard_news_event_timestamp($event);
            if (!$ts || $ts > $end) continue;

            $recentThreshold = $now - 1800;
            $eventDate = date('Y-m-d', $ts);
            $isTodayOrTomorrow = in_array($eventDate, [date('Y-m-d'), date('Y-m-d', strtotime('+1 day'))], true);

            if ($ts < $recentThreshold && !$isTodayOrTomorrow) continue;

            $impact = dashboard_news_event_impact($event);
            if (!in_array($impact, ['LOW', 'MEDIUM', 'HIGH'], true)) continue;

            if (!dashboard_event_affects_symbol($event, $symbolCode)) continue;

            $key = md5(strtoupper($title) . '|' . $ts . '|' . strtoupper((string)($event['currency'] ?? $event['country'] ?? '')));
            if (isset($seen[$key])) continue;
            $seen[$key] = true;

            $seconds = max(0, $ts - $now);
            $event['title'] = $title;
            $event['event'] = $title;
            $event['impact'] = $impact;
            $event['impact_level'] = $event['impact_level'] ?? $impact;
            $event['impact_label'] = $event['impact_label'] ?? dashboard_forex_factory_impact_label($impact);
            $event['timestamp'] = $ts;
            $event['time'] = date('Y-m-d H:i', $ts);
            $event['seconds_until'] = $seconds;
            $event['minutes_until'] = (int)floor($seconds / 60);
            $event['countdown_label'] = dashboard_countdown_label($seconds);
            $normalized[] = $event;
        }

        usort($normalized, fn($a, $b) => ($a['timestamp'] ?? 0) <=> ($b['timestamp'] ?? 0));
        return $normalized;
    }
}

if (!function_exists('dashboard_sanitize_news_risk')) {
    function dashboard_sanitize_news_risk(array $risk, string $symbolCode, int $minutesAhead = 240): array
    {
        $rawEvents = [];
        foreach (['scheduled_events', 'events', 'macro_events'] as $key) {
            if (!empty($risk[$key]) && is_array($risk[$key])) {
                $rawEvents = array_merge($rawEvents, $risk[$key]);
            }
        }

        $upcoming = dashboard_normalize_upcoming_news_events($rawEvents, $symbolCode, $minutesAhead);

        if (empty($upcoming)) {
            $risk['has_news'] = false;
            $risk['has_scheduled_event'] = false;
            $risk['risk'] = 'LOW';
            $risk['scheduled_risk'] = 'LOW';
            $risk['score_penalty'] = 0;
            $risk['title'] = '';
            $risk['time'] = '';
            $risk['minutes_until'] = null;
            $risk['countdown_label'] = '';
            $risk['message'] = 'No calendar source news detected in the next ' . $minutesAhead . ' minutes.';
            $risk['events'] = [];
            $risk['scheduled_events'] = [];
            return $risk;
        }

        $highest = 'LOW';
        foreach ($upcoming as $event) {
            $impact = strtoupper((string)($event['impact_level'] ?? $event['impact'] ?? 'LOW'));

            if ($impact === 'HIGH') {
                $highest = 'HIGH';
                break;
            }

            if ($impact === 'MEDIUM') {
                $highest = 'MEDIUM';
            }
        }

        $primary = $upcoming[0];
        $risk['has_news'] = true;
        $risk['has_scheduled_event'] = true;
        $risk['risk'] = $highest;
        $risk['scheduled_risk'] = $highest;
        $risk['score_penalty'] = $highest === 'HIGH' ? 4 : ($highest === 'MEDIUM' ? 2 : 0);
        $risk['title'] = $primary['title'] ?? 'Upcoming economic event';
        $risk['time'] = $primary['time'] ?? '';
        $risk['minutes_until'] = $primary['minutes_until'] ?? null;
        $risk['countdown_label'] = $primary['countdown_label'] ?? '';
        $risk['message'] = $highest === 'LOW'
            ? 'Low-impact scheduled economic event: ' . ($primary['title'] ?? 'Upcoming event') . '.'
            : $highest . ' scheduled economic event: ' . ($primary['title'] ?? 'Upcoming event') . '.';
        $risk['events'] = $upcoming;
        $risk['scheduled_events'] = $upcoming;
        $risk['source'] = trim((string)($risk['source'] ?? '')) ?: 'dashboard_normalized_calendar';
        return $risk;
    }
}

if (!function_exists('dashboard_sanitize_geo_risk')) {
    function dashboard_sanitize_geo_risk(array $geo): array
    {
        $events = [];
        foreach (($geo['events'] ?? []) as $event) {
            if (!is_array($event)) continue;
            $title = trim((string)($event['title'] ?? $event['headline'] ?? $event['event'] ?? ''));
            if ($title === '') continue;

            $ts = dashboard_news_event_timestamp($event);
            if ($ts && $ts < (time() - 21600)) continue; // ignore headlines older than 6 hours

            $events[] = $event;
        }

        if (empty($events)) {
            return [
                'risk' => 'LOW',
                'impact' => 'Neutral',
                'score_penalty' => 0,
                'message' => 'No major geopolitical risk detected currently.',
                'sentiment' => 0,
                'events' => [],
                'source' => $geo['source'] ?? 'dashboard_sanitized',
            ];
        }

        $rawRisk = strtoupper((string)($geo['risk'] ?? $geo['status'] ?? ''));
        $risk = in_array($rawRisk, ['HIGH', 'MEDIUM', 'LOW'], true) ? $rawRisk : 'MEDIUM';

        $geo['risk'] = $risk;
        $geo['message'] = $geo['message'] ?? 'Relevant geopolitical headlines detected. Use normal risk controls.';
        $geo['events'] = $events;
        return $geo;
    }
}

if (!function_exists('setup_rank_grade')) {
    function setup_rank_grade($score)
    {
        $score = (float)($score ?? 0);

        if ($score >= 12) return 'A+';
        if ($score >= 10) return 'A';
        if ($score >= 8) return 'B+';
        if ($score >= 6) return 'B';
        if ($score >= 4) return 'C+';

        return 'C';
    }
}

if (!function_exists('setup_rank_score_10')) {
    function setup_rank_score_10($score)
    {
        $score = (float)($score ?? 0);
        if ($score > 10) {
            $score = ($score / 15) * 10;
        }

        return number_format(max(0, min(10, $score)), 1);
    }
}

if (!function_exists('lifecycle_badge_class')) {
    function lifecycle_badge_class($stage)
    {
        $stage = strtoupper((string)$stage);

        if (dashboard_text_contains_safe($stage, 'READY') || dashboard_text_contains_safe($stage, 'TRIGGER')) {
            return 'bg-emerald-50 text-emerald-700 border-emerald-200';
        }

        if (dashboard_text_contains_safe($stage, 'ARMED') || dashboard_text_contains_safe($stage, 'BUILDING')) {
            return 'bg-amber-50 text-amber-700 border-amber-200';
        }

        if (dashboard_text_contains_safe($stage, 'INVALID')) {
            return 'bg-red-50 text-red-700 border-red-200';
        }

        if (dashboard_text_contains_safe($stage, 'REDUCED')) {
            return 'bg-blue-50 text-blue-700 border-blue-200';
        }

        return 'bg-slate-50 text-slate-700 border-slate-200';
    }
}

if (!function_exists('lifecycle_bar_class')) {
    function lifecycle_bar_class($stage)
    {
        $stage = strtoupper((string)$stage);

        if (dashboard_text_contains_safe($stage, 'READY') || dashboard_text_contains_safe($stage, 'TRIGGER')) {
            return 'bg-emerald-600';
        }

        if (dashboard_text_contains_safe($stage, 'ARMED') || dashboard_text_contains_safe($stage, 'BUILDING')) {
            return 'bg-amber-500';
        }

        if (dashboard_text_contains_safe($stage, 'INVALID')) {
            return 'bg-red-600';
        }

        if (dashboard_text_contains_safe($stage, 'REDUCED')) {
            return 'bg-blue-600';
        }

        return 'bg-slate-500';
    }
}

if (!function_exists('institutional_numeric')) {
    function institutional_numeric($value, $fallback = null)
    {
        return is_numeric($value) ? (float)$value : $fallback;
    }
}

if (!function_exists('institutional_zone_midpoint')) {
    function institutional_zone_midpoint($low, $high)
    {
        if (!is_numeric($low) || !is_numeric($high)) return null;
        return ((float)$low + (float)$high) / 2;
    }
}

if (!function_exists('institutional_zone_direction_matches')) {
    function institutional_zone_direction_matches($side, array $zone)
    {
        $side = strtoupper((string)$side);
        $text = strtoupper(
            ($zone['direction'] ?? '') . ' ' .
            ($zone['type'] ?? '') . ' ' .
            ($zone['source'] ?? '') . ' ' .
            ($zone['label'] ?? '')
        );

        if ($side === 'BUY') {
            return dashboard_text_contains_safe($text, 'BUY') || dashboard_text_contains_safe($text, 'BULLISH') || dashboard_text_contains_safe($text, 'DEMAND');
        }

        if ($side === 'SELL') {
            return dashboard_text_contains_safe($text, 'SELL') || dashboard_text_contains_safe($text, 'BEARISH') || dashboard_text_contains_safe($text, 'SUPPLY');
        }

        return false;
    }
}

if (!function_exists('institutional_zone_location_ok')) {
    function institutional_zone_location_ok($side, $midpoint, $equilibrium)
    {
        if (!is_numeric($midpoint) || !is_numeric($equilibrium)) return true;

        $side = strtoupper((string)$side);
        if ($side === 'BUY') return (float)$midpoint <= (float)$equilibrium;
        if ($side === 'SELL') return (float)$midpoint >= (float)$equilibrium;

        return true;
    }
}

if (!function_exists('institutional_collect_side_zones')) {
    function institutional_collect_side_zones($side, array $orderBlocks, array $fvgZones, $equilibrium, $currentPrice = null, $strictLocation = true)
    {
        $side = strtoupper((string)$side);
        $zones = [];
        $currentPrice = is_numeric($currentPrice) ? (float)$currentPrice : null;
        $equilibrium = is_numeric($equilibrium) ? (float)$equilibrium : null;

        $addZone = function (array $zone) use (&$zones, $side, $currentPrice, $equilibrium, $strictLocation) {
            $low = institutional_numeric($zone['low'] ?? $zone['zone_low'] ?? null);
            $high = institutional_numeric($zone['high'] ?? $zone['zone_high'] ?? null);

            if ($low === null || $high === null) return;

            $low = min($low, $high);
            $high = max($low, $high);
            $mid = institutional_zone_midpoint($low, $high);

            /*
            |--------------------------------------------------------------------------
            | Strict institutional location filter
            |--------------------------------------------------------------------------
            | BUY scenario must use demand/FVG below equilibrium (discount).
            | SELL scenario must use supply/FVG above equilibrium (premium).
            |--------------------------------------------------------------------------
            */
            $locationOk = institutional_zone_location_ok($side, $mid, $equilibrium);
            if ($strictLocation && !$locationOk) {
                return;
            }

            $containsCurrent = $currentPrice !== null && $currentPrice >= $low && $currentPrice <= $high;

            /*
            |--------------------------------------------------------------------------
            | Fresh pullback filter
            |--------------------------------------------------------------------------
            | BUY: zone should be below current price, unless price is already inside it.
            | SELL: zone should be above current price, unless price is already inside it.
            |--------------------------------------------------------------------------
            */
            if ($currentPrice !== null && !$containsCurrent) {
                if ($side === 'BUY' && $high >= $currentPrice) return;
                if ($side === 'SELL' && $low <= $currentPrice) return;
            }

            $zone['low'] = $low;
            $zone['high'] = $high;
            $zone['midpoint'] = $mid;
            $zone['contains_current'] = $containsCurrent;
            $zone['location_ok'] = $locationOk;
            $zone['pullback_valid'] = $currentPrice === null || $containsCurrent || ($side === 'BUY' ? $high < $currentPrice : $low > $currentPrice);
            $zone['distance_to_price'] = $currentPrice !== null ? institutional_scenario_distance($currentPrice, $low, $high) : null;

            $zones[] = $zone;
        };

        foreach ($orderBlocks as $ob) {
            if (!is_array($ob)) continue;
            if (!institutional_zone_direction_matches($side, $ob)) continue;

            $low = institutional_numeric($ob['zone_low'] ?? $ob['low'] ?? null);
            $high = institutional_numeric($ob['zone_high'] ?? $ob['high'] ?? null);
            if ($low === null || $high === null) continue;

            $addZone([
                'kind' => $side === 'BUY' ? 'Demand Order Block' : 'Supply Order Block',
                'source' => 'order_block',
                'type' => $ob['type'] ?? ($side === 'BUY' ? 'BULLISH_OB' : 'BEARISH_OB'),
                'low' => $low,
                'high' => $high,
                'quality' => (float)($ob['quality'] ?? 3),
                'mitigated' => !empty($ob['mitigated']),
                'raw' => $ob,
            ]);
        }

        foreach ($fvgZones as $fvg) {
            if (!is_array($fvg)) continue;
            if (!institutional_zone_direction_matches($side, $fvg)) continue;

            $low = institutional_numeric($fvg['low'] ?? $fvg['zone_low'] ?? null);
            $high = institutional_numeric($fvg['high'] ?? $fvg['zone_high'] ?? null);
            if ($low === null || $high === null) continue;

            $addZone([
                'kind' => $side === 'BUY' ? 'Bullish FVG / Demand Rebalance' : 'Bearish FVG / Supply Rebalance',
                'source' => 'fvg',
                'type' => $fvg['type'] ?? ($side === 'BUY' ? 'BULLISH_FVG' : 'BEARISH_FVG'),
                'low' => $low,
                'high' => $high,
                'quality' => (float)($fvg['quality'] ?? 2.5),
                'mitigated' => !empty($fvg['filled']),
                'raw' => $fvg,
            ]);
        }

        usort($zones, function ($a, $b) {
            if (($a['contains_current'] ?? false) !== ($b['contains_current'] ?? false)) {
                return !empty($b['contains_current']) <=> !empty($a['contains_current']);
            }

            $da = $a['distance_to_price'] ?? 999999;
            $db = $b['distance_to_price'] ?? 999999;
            if ($da != $db) return $da <=> $db;

            return ($b['quality'] ?? 0) <=> ($a['quality'] ?? 0);
        });

        return $zones;
    }
}

if (!function_exists('institutional_scenario_distance')) {
    function institutional_scenario_distance($price, $low, $high)
    {
        if (!is_numeric($price) || !is_numeric($low) || !is_numeric($high)) return null;

        $price = (float)$price;
        $a = (float)$low;
        $b = (float)$high;
        $low = min($a, $b);
        $high = max($a, $b);

        if ($price >= $low && $price <= $high) return 0.0;
        return $price < $low ? ($low - $price) : ($price - $high);
    }
}

if (!function_exists('institutional_dynamic_sl_for_scenario')) {
    function institutional_dynamic_sl_for_scenario($side, $entry, $zoneLow, $zoneHigh, $baseSlDistance, $atr, $volatility, $symbolCode = '')
    {
        $entry = institutional_numeric($entry);
        $riskDistance = scenario_risk_price_distance($baseSlDistance, $symbolCode);

        if ($entry === null || $riskDistance === null || $riskDistance <= 0) {
            return [
                'distance' => $riskDistance ?: abs((float)$baseSlDistance),
                'price' => null,
                'reason' => 'Dynamic stop could not be calculated because the scenario entry or risk distance is incomplete.',
            ];
        }

        $side = strtoupper((string)$side);

        /*
        |--------------------------------------------------------------------------
        | Corrected scenario SL logic
        |--------------------------------------------------------------------------
        | The scenario planner must use the SAME dynamic SL distance displayed
        | in the Execution Quality section. It must not shrink the stop to a
        | zone-width/ATR buffer. For XAUUSD, 304 pips = 30.40 price distance.
        |--------------------------------------------------------------------------
        */
        $sl = $side === 'SELL'
            ? ($entry + $riskDistance)
            : ($entry - $riskDistance);

        return [
            'distance' => $riskDistance,
            'price' => $sl,
            'reason' => 'Stop is calculated from the scenario entry using the dynamic SL distance shown in Execution Quality.',
        ];
    }
}

if (!function_exists('institutional_build_side_scenario')) {
    function institutional_build_side_scenario(
        $side,
        $contextBias,
        $executionBias,
        $currentPrice,
        array $orderBlocks,
        array $fvgZones,
        $equilibrium,
        $pdLabel,
        $rangePosition,
        $baseSlDistance,
        $atr,
        $volatility,
        array $tpMultipliers,
        $symbolCode,
        $decimals,
        $confirmationStage,
        $confirmationCount,
        $partialConfirmationCount
    ) {
        $side = strtoupper((string)$side);
        $contextBias = strtoupper((string)$contextBias);
        $executionBias = strtoupper((string)$executionBias);
        $pdLabelUpper = strtoupper((string)$pdLabel);
        $stage = strtoupper((string)$confirmationStage);

        $zones = institutional_collect_side_zones($side, $orderBlocks, $fvgZones, $equilibrium, $currentPrice, true);
        $zone = $zones[0] ?? null;

        $htfAligned = $contextBias === $side;
        $ltfAligned = $executionBias === $side;
        $locationIdeal = $side === 'BUY' ? dashboard_text_contains_safe($pdLabelUpper, 'DISCOUNT') : dashboard_text_contains_safe($pdLabelUpper, 'PREMIUM');

        $score = 0;
        $reasons = [];

        if ($htfAligned) { $score += 25; $reasons[] = 'HTF supports ' . $side; }
        else { $reasons[] = 'HTF does not support ' . $side; }

        if ($ltfAligned) { $score += 20; $reasons[] = 'Execution timeframe supports ' . $side; }
        else { $reasons[] = 'Execution timeframe does not support ' . $side . ' yet'; }

        if ($locationIdeal) { $score += 25; $reasons[] = $side === 'BUY' ? 'Current market is in discount context' : 'Current market is in premium context'; }
        else { $reasons[] = $side === 'BUY' ? 'Buy scenario requires a discount demand zone' : 'Sell scenario requires a premium supply zone'; }

        $entry = null; $entryLow = null; $entryHigh = null; $sl = null; $tp1 = null; $tp2 = null; $tp3 = null;
        $distance = null; $riskDistance = null; $slReason = '';

        if ($zone) {
            $score += 25;
            $score += min(10, (float)($zone['quality'] ?? 0) * 1.5);
            $reasons[] = $zone['kind'] . ' detected in correct side of the dealing range';
            $reasons[] = !empty($zone['contains_current']) ? 'Price is currently inside this institutional zone' : 'Entry requires pullback into the zone';

            $entryLow = $zone['low'];
            $entryHigh = $zone['high'];
            $entry = scenario_entry_anchor($side, $entryLow, $entryHigh, $currentPrice, $confirmationStage);
            $distance = institutional_scenario_distance($currentPrice, $entryLow, $entryHigh);

            if ($entry !== null) {
                $slData = institutional_dynamic_sl_for_scenario($side, $entry, $entryLow, $entryHigh, $baseSlDistance, $atr, $volatility, $symbolCode);
                $sl = $slData['price'];
                $riskDistance = $slData['distance'];
                $slReason = $slData['reason'];

                $tp1r = (float)($tpMultipliers['tp1'] ?? 1);
                $tp2r = (float)($tpMultipliers['tp2'] ?? 2);
                $tp3r = (float)($tpMultipliers['tp3'] ?? 3);

                if ($side === 'BUY') {
                    $tp1 = $entry + ($riskDistance * $tp1r);
                    $tp2 = $entry + ($riskDistance * $tp2r);
                    $tp3 = $entry + ($riskDistance * $tp3r);
                } else {
                    $tp1 = $entry - ($riskDistance * $tp1r);
                    $tp2 = $entry - ($riskDistance * $tp2r);
                    $tp3 = $entry - ($riskDistance * $tp3r);
                }
            } else {
                $slReason = scenario_entry_note($side, null, $currentPrice, $entryLow, $entryHigh, $confirmationStage);
            }
        } else {
            $reasons[] = $side === 'BUY'
                ? 'No qualified demand zone in discount found below current price'
                : 'No qualified supply zone in premium found above current price';
        }

        $score += min(10, ((int)$confirmationCount * 3) + ((int)$partialConfirmationCount * 1.5));
        $score = max(0, min(100, $score));

        $freshValid = scenario_is_fresh_entry_valid($side, $entry, $currentPrice);

        if (!$zone) {
            $status = 'No Qualified Zone';
            $instruction = $side === 'BUY'
                ? 'No valid demand block in discount is available below current price. Wait for a real discount-zone pullback.'
                : 'No valid supply block in premium is available above current price. Wait for a real premium-zone pullback.';
            $tone = 'slate';
            $score = min($score, 35);
        } elseif (!$freshValid && $stage !== 'EXECUTION_CONFIRMED') {
            $status = 'Zone Already Tapped';
            $instruction = scenario_entry_note($side, null, $currentPrice, $entryLow, $entryHigh, $stage);
            $tone = 'amber';
            $score = min($score, 55);
        } elseif ($htfAligned && $ltfAligned && $locationIdeal && $stage === 'EXECUTION_CONFIRMED') {
            $status = 'Execution Ready';
            $instruction = $side . ' setup is valid only from the highlighted institutional zone. Do not chase current price.';
            $tone = 'emerald';
        } elseif ($htfAligned && $zone) {
            $status = 'Preferred Watchlist';
            $instruction = $side . ' setup has a valid institutional zone. Wait for price to return to the zone and confirm execution.';
            $tone = 'blue';
        } elseif (!$htfAligned && $ltfAligned) {
            $status = 'Countertrend - Avoid';
            $instruction = 'Do not force this ' . $side . ' setup because it does not align with the higher timeframe.';
            $tone = 'red';
            $score = min($score, 45);
        } else {
            $status = 'Waiting for Alignment';
            $instruction = $side . ' setup is not active until HTF, execution timeframe, location, and confirmation agree.';
            $tone = 'slate';
            $score = min($score, 50);
        }

        return [
            'side' => $side,
            'status' => $status,
            'instruction' => $instruction,
            'tone' => $tone,
            'score' => round($score, 1),
            'zone' => $zone,
            'entry_low' => $entryLow,
            'entry_high' => $entryHigh,
            'entry' => $entry,
            'stop_loss' => $sl,
            'risk_distance' => $riskDistance,
            'sl_reason' => $slReason,
            'tp1' => $tp1,
            'tp2' => $tp2,
            'tp3' => $tp3,
            'distance' => $distance,
            'entry_note' => scenario_entry_note($side, $entry, $currentPrice, $entryLow, $entryHigh, $confirmationStage),
            'fresh_entry_valid' => $freshValid,
            'reasons' => array_slice($reasons, 0, 7),
            'htf_aligned' => $htfAligned,
            'ltf_aligned' => $ltfAligned,
            'location_ideal' => $locationIdeal,
        ];
    }
}

if (!function_exists('institutional_detect_liquidity_sweep_fallback')) {
    function institutional_detect_liquidity_sweep_fallback(array $candles, $side, array $liquidity = [])
    {
        $side = strtoupper((string)$side);
        if (count($candles) < 8) {
            return ['label' => 'Liquidity Sweep Pending', 'message' => 'Not enough candle data to confirm liquidity sweep.', 'confirmed' => false, 'partial' => false];
        }

        $recent = array_slice($candles, -12);
        $last = end($recent);
        $previous = array_slice($recent, 0, -1);
        $prevHigh = max(array_map(fn($c) => (float)($c['high_price'] ?? 0), $previous));
        $prevLow = min(array_map(fn($c) => (float)($c['low_price'] ?? 0), $previous));

        $lastHigh = (float)($last['high_price'] ?? 0);
        $lastLow = (float)($last['low_price'] ?? 0);
        $lastClose = (float)($last['close_price'] ?? 0);

        if ($side === 'SELL') {
            $buySide = institutional_numeric($liquidity['buy_side'] ?? ($liquidity['nearest_buy_side_liquidity'] ?? null), $prevHigh);
            if (($lastHigh > $prevHigh || $lastHigh >= $buySide) && $lastClose < $prevHigh) {
                return ['label' => 'Buy-side Liquidity Swept', 'message' => 'Recent candle swept buy-side liquidity and closed back below it.', 'confirmed' => true, 'partial' => false];
            }
            if ($lastHigh > $prevHigh || $lastHigh >= $buySide) {
                return ['label' => 'Buy-side Liquidity Tapped', 'message' => 'Buy-side liquidity has been tapped. Wait for bearish close back below the level.', 'confirmed' => false, 'partial' => true];
            }
        }

        if ($side === 'BUY') {
            $sellSide = institutional_numeric($liquidity['sell_side'] ?? ($liquidity['nearest_sell_side_liquidity'] ?? null), $prevLow);
            if (($lastLow < $prevLow || $lastLow <= $sellSide) && $lastClose > $prevLow) {
                return ['label' => 'Sell-side Liquidity Swept', 'message' => 'Recent candle swept sell-side liquidity and closed back above it.', 'confirmed' => true, 'partial' => false];
            }
            if ($lastLow < $prevLow || $lastLow <= $sellSide) {
                return ['label' => 'Sell-side Liquidity Tapped', 'message' => 'Sell-side liquidity has been tapped. Wait for bullish close back above the level.', 'confirmed' => false, 'partial' => true];
            }
        }

        return ['label' => 'Liquidity Sweep Pending', 'message' => 'No fresh liquidity sweep confirmation detected yet.', 'confirmed' => false, 'partial' => false];
    }
}

if (!function_exists('scenario_tone_classes')) {
    function scenario_tone_classes($tone)
    {
        $tone = (string)$tone;
        if ($tone === 'emerald') {
            return 'border-emerald-200 bg-emerald-50 text-emerald-900';
        }

        if ($tone === 'blue') {
            return 'border-blue-200 bg-blue-50 text-blue-900';
        }

        if ($tone === 'amber') {
            return 'border-amber-200 bg-amber-50 text-amber-900';
        }

        if ($tone === 'red') {
            return 'border-red-200 bg-red-50 text-red-900';
        }

        return 'border-slate-200 bg-slate-50 text-slate-900';
    }
}

if (!function_exists('institutional_direction_conflict')) {
    function institutional_direction_conflict($htfBias, $ltfBias)
    {
        $htf = strtoupper((string)$htfBias);
        $ltf = strtoupper((string)$ltfBias);

        return in_array($htf, ['BUY', 'SELL'], true)
            && in_array($ltf, ['BUY', 'SELL'], true)
            && $htf !== $ltf;
    }
}

if (!function_exists('institutional_primary_action')) {
    function institutional_primary_action($htfBias, $ltfBias, $confirmationStage, $locationClass = '')
    {
        if (institutional_direction_conflict($htfBias, $ltfBias)) {
            return 'WAIT';
        }

        $stage = strtoupper((string)$confirmationStage);
        if ($stage !== 'EXECUTION_CONFIRMED') {
            return 'WAIT';
        }

        $ltf = strtoupper((string)$ltfBias);
        return in_array($ltf, ['BUY', 'SELL'], true) ? $ltf : 'WAIT';
    }
}

if (!function_exists('institutional_conflict_guidance')) {
    function institutional_conflict_guidance($htfBias, $ltfBias, $rawLocation)
    {
        $htf = strtoupper((string)$htfBias);
        $ltf = strtoupper((string)$ltfBias);
        $loc = strtoupper((string)$rawLocation);

        if (!institutional_direction_conflict($htf, $ltf)) {
            return '';
        }

        if ($htf === 'SELL' && $ltf === 'BUY' && dashboard_text_contains_safe($loc, 'PREMIUM')) {
            return 'HTF is SELL while the execution timeframe is BUY, and price is in premium. Do not buy at a premium level and do not sell without bearish confirmation. Wait for LTF bearish shift or a discount pullback.';
        }

        if ($htf === 'BUY' && $ltf === 'SELL' && dashboard_text_contains_safe($loc, 'DISCOUNT')) {
            return 'HTF is BUY while the execution timeframe is SELL, and price is in discount. Do not sell into discount and do not buy without bullish confirmation. Wait for LTF bullish shift or a premium pullback.';
        }

        return 'HTF and execution-timeframe bias are conflicting. Do not force a trade. Wait for directional alignment or a confirmed continuation model.';
    }
}

if (!function_exists('institutional_readiness_final')) {
    function institutional_readiness_final($currentLabel, $isConflict, $confirmationStage)
    {
        if ($isConflict) {
            return 'Directional Conflict';
        }

        if (strtoupper((string)$confirmationStage) !== 'EXECUTION_CONFIRMED') {
            return 'Watchlist';
        }

        return $currentLabel ?: 'Execution Ready';
    }
}

if (!function_exists('refined_location_label')) {
    function refined_location_label($bias, $rawLocation, $position, $currentLabel)
    {
        $bias = strtoupper((string)$bias);
        $raw = strtoupper((string)$rawLocation);
        $position = $position === null ? null : (float)$position;
        $currentLabel = trim((string)$currentLabel);

        if ($bias === 'SELL' && dashboard_text_contains_safe($raw, 'PREMIUM')) {
            if ($position !== null && $position >= 75) return 'High-Quality Sell Location';
            return 'Favorable Sell Location';
        }

        if ($bias === 'BUY' && dashboard_text_contains_safe($raw, 'DISCOUNT')) {
            if ($position !== null && $position <= 25) return 'High-Quality Buy Location';
            return 'Favorable Buy Location';
        }

        if ($bias === 'BUY' && dashboard_text_contains_safe($raw, 'PREMIUM')) {
            return dashboard_text_contains_safe($raw, 'EXTREME') ? 'Late Buy Location' : 'Premium Buy - Reduced RR';
        }

        if ($bias === 'SELL' && dashboard_text_contains_safe($raw, 'DISCOUNT')) {
            return dashboard_text_contains_safe($raw, 'EXTREME') ? 'Late Sell Location' : 'Discount Sell - Reduced RR';
        }

        return $currentLabel ?: 'Neutral Location';
    }
}

if (!function_exists('refined_location_score')) {
    function refined_location_score($bias, $rawLocation, $position, $score)
    {
        $bias = strtoupper((string)$bias);
        $raw = strtoupper((string)$rawLocation);
        $position = $position === null ? null : (float)$position;
        $score = (float)$score;

        if ($bias === 'SELL' && dashboard_text_contains_safe($raw, 'PREMIUM')) {
            return ($position !== null && $position >= 75) ? max($score, 85) : min(max($score, 70), 78);
        }

        if ($bias === 'BUY' && dashboard_text_contains_safe($raw, 'DISCOUNT')) {
            return ($position !== null && $position <= 25) ? max($score, 85) : min(max($score, 70), 78);
        }

        if ($bias === 'BUY' && dashboard_text_contains_safe($raw, 'PREMIUM')) {
            return dashboard_text_contains_safe($raw, 'EXTREME') ? min($score, 20) : min($score, 35);
        }

        if ($bias === 'SELL' && dashboard_text_contains_safe($raw, 'DISCOUNT')) {
            return dashboard_text_contains_safe($raw, 'EXTREME') ? min($score, 20) : min($score, 35);
        }

        return $score;
    }
}

if (!function_exists('confirmation_text')) {
    function confirmation_text($confirmed, $partial)
    {
        $confirmed = (int)$confirmed;
        $partial = (int)$partial;

        $text = $confirmed . '/4 checks';
        if ($partial > 0) {
            $text .= ' + ' . $partial . ' partial';
        }

        return $text;
    }
}

if (!function_exists('ranking_score_display')) {
    function ranking_score_display($score, $confirmationCount, $partialCount)
    {
        $score = (float)$score;
        $confirmationCount = (int)$confirmationCount;
        $partialCount = (int)$partialCount;

        if ($confirmationCount === 0 && $partialCount === 0) {
            $score = min($score, 5.8);
        } elseif ($confirmationCount === 0 && $partialCount > 0) {
            $score = min($score, 6.4);
        } elseif ($confirmationCount === 1) {
            $score = min($score, 7.0);
        } elseif ($confirmationCount === 2) {
            $score = min($score, 7.6);
        } else {
            $score = min($score, 8.6);
        }

        return number_format(max(0, $score), 1);
    }
}

if (!function_exists('readiness_display_label')) {
    function readiness_display_label($readiness, $confirmationStage, $confirmationCount, $partialCount)
    {
        $readiness = trim((string)$readiness);
        $stage = strtoupper((string)$confirmationStage);
        $confirmationCount = (int)$confirmationCount;
        $partialCount = (int)$partialCount;

        if ($stage !== 'EXECUTION_CONFIRMED') {
            if ($confirmationCount === 0 && $partialCount === 0) return 'Watchlist';
            return 'Developing Setup';
        }

        return $readiness ?: 'Execution Ready';
    }
}

if (!function_exists('institutional_score_cap_value')) {
    function institutional_score_cap_value($confirmationCount, $partialCount, $stage, $status, $locationClass)
    {
        $confirmationCount = (int)$confirmationCount;
        $partialCount = (int)$partialCount;
        $stage = strtoupper((string)$stage);
        $status = strtoupper((string)$status);
        $locationClass = strtoupper((string)$locationClass);

        if (in_array($status, ['INVALID', 'MISSED_ENTRY', 'NO_VALID_SETUP', 'NO_DATA', 'NO DATA'], true)) {
            return 3.0;
        }

        if ($confirmationCount === 0 && $partialCount === 0) {
            $cap = 5.5;
        } elseif ($confirmationCount === 0 && $partialCount > 0) {
            $cap = 6.5;
        } elseif ($confirmationCount === 1) {
            $cap = 7.0;
        } elseif ($confirmationCount === 2) {
            $cap = 7.8;
        } else {
            $cap = 9.4;
        }

        if ($stage !== 'EXECUTION_CONFIRMED') {
            $cap = min($cap, 8.2);
        }

        if ($locationClass === 'CAUTION') {
            $cap = min($cap, 5.8);
        } elseif ($locationClass === 'REDUCED_RR') {
            $cap = min($cap, 7.2);
        }

        return $cap;
    }
}

if (!function_exists('institutional_confidence_label')) {
    function institutional_confidence_label($score, $probability, $confirmationCount, $partialCount)
    {
        $score = (float)$score;
        $probability = (float)$probability;
        $confirmationCount = (int)$confirmationCount;
        $partialCount = (int)$partialCount;

        if ($score >= 8.5 && $confirmationCount >= 3 && $probability >= 75) return 'VERY HIGH';
        if ($score >= 7.2 && $confirmationCount >= 2 && $probability >= 65) return 'HIGH';
        if ($score >= 5.5 || $partialCount > 0) return 'MEDIUM';
        if ($score >= 4.0) return 'LOW';
        return 'VERY LOW';
    }
}

if (!function_exists('institutional_readiness_from_confirmation')) {
    function institutional_readiness_from_confirmation($score, $confirmationCount, $partialCount, $stage, $status)
    {
        $score = (float)$score;
        $confirmationCount = (int)$confirmationCount;
        $partialCount = (int)$partialCount;
        $stage = strtoupper((string)$stage);
        $status = strtoupper((string)$status);

        if (in_array($status, ['INVALID', 'MISSED_ENTRY', 'NO_VALID_SETUP', 'NO_DATA', 'NO DATA'], true)) return 'Waitlist';
        if ($stage === 'EXECUTION_CONFIRMED' && $score >= 8.5) return 'Watchlist';
        if ($confirmationCount >= 2 && $score >= 7.0) return 'B+ Setup';
        if ($confirmationCount >= 1 || $partialCount > 0) return 'Developing Setup';
        return 'Watchlist';
    }
}

if (!function_exists('contextual_fvg_text')) {
    function contextual_fvg_text($bias, $fvgType)
    {
        $bias = strtoupper((string)$bias);
        $fvgType = strtoupper((string)$fvgType);

        if ($bias === 'SELL' && dashboard_text_contains_safe($fvgType, 'BULLISH')) {
            return 'Opposing FVG: use only as a rebalance/pullback context until bearish confirmation appears.';
        }

        if ($bias === 'BUY' && dashboard_text_contains_safe($fvgType, 'BEARISH')) {
            return 'Opposing FVG: use only as a rebalance/pullback context until bullish confirmation appears.';
        }

        if ($fvgType) {
            return 'Aligned FVG: supports the active execution bias if mitigation confirms.';
        }

        return 'No FVG context available.';
    }
}

if (!function_exists('probability_class')) {
    function probability_class($score)
    {
        $score = (float)($score ?? 0);
        if ($score >= 75) return 'bg-emerald-50 text-emerald-700 border-emerald-200';
        if ($score >= 55) return 'bg-blue-50 text-blue-700 border-blue-200';
        if ($score >= 35) return 'bg-amber-50 text-amber-700 border-amber-200';
        return 'bg-red-50 text-red-700 border-red-200';
    }
}

if (!function_exists('setup_rank_grade_refined')) {
    function setup_rank_grade_refined($score)
    {
        $score = (float)($score ?? 0);
        if ($score >= 8.8) return 'A';
        if ($score >= 7.5) return 'B+';
        if ($score >= 6.2) return 'B';
        if ($score >= 4.8) return 'C+';
        return 'C';
    }
}

if (!function_exists('canonical_zone_interaction_label')) {
    function canonical_zone_interaction_label($mitigationState, $fallback = 'Developing setup')
    {
        if (is_array($mitigationState) && !empty($mitigationState['label'])) {
            return (string)$mitigationState['label'];
        }

        return (string)$fallback;
    }
}

if (!function_exists('confirmation_stage_class')) {
    function confirmation_stage_class($stage)
    {
        $stage = strtoupper((string)$stage);

        if ($stage === 'EXECUTION_CONFIRMED') {
            return 'bg-emerald-50 text-emerald-700 border-emerald-200';
        }

        if ($stage === 'PARTIAL_CONFIRMATION') {
            return 'bg-blue-50 text-blue-700 border-blue-200';
        }

        if ($stage === 'EARLY_CONFIRMATION') {
            return 'bg-amber-50 text-amber-700 border-amber-200';
        }

        return 'bg-slate-50 text-slate-700 border-slate-200';
    }
}

if (!function_exists('location_quality_class')) {
    function location_quality_class($score)
    {
        $score = (float)($score ?? 0);

        if ($score >= 80) {
            return 'bg-emerald-50 text-emerald-700 border-emerald-200';
        }

        if ($score >= 55) {
            return 'bg-blue-50 text-blue-700 border-blue-200';
        }

        if ($score >= 35) {
            return 'bg-amber-50 text-amber-700 border-amber-200';
        }

        return 'bg-red-50 text-red-700 border-red-200';
    }
}

if (!function_exists('execution_mode_label')) {
    function execution_mode_label($value)
    {
        $value = strtoupper((string)$value);

        $map = [
            'REDUCED_RR' => 'Reduced RR',
            'LOCATION_CAUTION' => 'Location Caution',
            'REDUCED_RISK' => 'Reduced Risk',
            'NEWS_HIGH_CAUTION' => 'High-Impact News Caution',
            'NEWS_CAUTION' => 'News Caution',
            'NEWS_REDUCED_RISK' => 'News Reduced Risk',
            'POST_NEWS_VOLATILITY' => 'Post-News Volatility',
            'NORMAL' => 'Normal',
        ];

        return $map[$value] ?? ucwords(strtolower(str_replace('_', ' ', $value)));
    }
}

if (!function_exists('institutional_readiness_label')) {
    function institutional_readiness_label($tradePlan, $confluenceScore)
    {
        $status = strtoupper((string)($tradePlan['status'] ?? ''));
        $mode = strtoupper((string)($tradePlan['execution_mode'] ?? ''));
        $locClass = strtoupper((string)($tradePlan['location_execution_class'] ?? ''));
        $score = (float)$confluenceScore;

        if (in_array($status, ['INVALID', 'MISSED_ENTRY', 'NO_VALID_SETUP'], true)) {
            return 'Waitlist';
        }

        if ($mode === 'LOCATION_CAUTION' || $locClass === 'CAUTION') {
            return 'Location Caution';
        }

        if ($mode === 'REDUCED_RR' || $locClass === 'REDUCED_RR') {
            return 'Reduced RR';
        }

        if ($score >= 8.5) return 'A Setup';
        if ($score >= 7.0) return 'A Setup';
        if ($score >= 5.5) return 'B Setup';
        if ($score >= 4.0) return 'Developing Setup';

        return 'Watchlist';
    }
}

if (!function_exists('clean_dashboard_text')) {
    function clean_dashboard_text($value, $fallback = 'N/A')
    {
        $text = trim((string)($value ?? ''));

        if ($text === '') {
            return $fallback;
        }

        $badPhrases = [
            'Multi-source geopolitical headlines detected and scored.',
            'multi-source geopolitical headlines detected and scored.',
            'Geopolitical developments may increase market volatility and execution risk.',
        ];

        foreach ($badPhrases as $bad) {
            $text = str_replace($bad, 'Use reduced size until volatility stabilizes. Wait for confirmation before execution.', $text);
        }

        $text = str_replace('Geopolitical Risk High', 'Geopolitical Risk High', $text);

        return $text;
    }
}

if (!function_exists('clean_status_label')) {
    function clean_status_label($value)
    {
        $label = trim((string)($value ?? 'N/A'));

        $map = [
            'Geopolitical Risk High' => 'Geopolitical Risk Elevated',
            'GEOPOLITICAL_CAUTION' => 'Geopolitical Risk Elevated',
            'GEOPOLITICAL_RISK_ELEVATED' => 'Geopolitical Risk Elevated',
            'HIGH_IMPACT_NEWS_CAUTION' => 'News Caution',
            'NEWS_CAUTION' => 'News Caution',
            'WEAK SETUP' => 'Caution',
            'VERY LOW' => 'Low Confidence',
            'STRONG SETUP' => 'High Quality',
        ];

        return $map[$label] ?? $label;
    }
}

if (!function_exists('geo_risk_guidance_text')) {
    function geo_risk_guidance_text($risk, $impact = '')
    {
        $risk = strtoupper((string)$risk);
        $impact = trim((string)$impact);

        if ($risk === 'HIGH') {
            return $impact && strtolower($impact) !== 'neutral'
                ? $impact . '. Reduce risk and wait for cleaner confirmation.'
                : 'Use reduced size until volatility stabilizes. Wait for confirmation before execution.';
        }

        if ($risk === 'MEDIUM') {
            return $impact && strtolower($impact) !== 'neutral'
                ? $impact . '. Use caution and reduce exposure.'
                : 'Moderate geopolitical risk is present. Use caution and reduce exposure.';
        }

        if ($risk === 'LOW') {
            return 'No major geopolitical risk is currently affecting execution.';
        }

        return 'Geopolitical risk could not be fully verified. Use normal risk controls.';
    }
}


if (!function_exists('dashboard_should_show_execution_guidance')) {
    function dashboard_should_show_execution_guidance($newsRiskLevel, $geoDisplayLevel, $executionMode)
    {
        $newsRiskLevel = strtoupper(trim((string)$newsRiskLevel));
        $geoDisplayLevel = strtoupper(trim((string)$geoDisplayLevel));
        $executionMode = strtoupper(trim((string)$executionMode));

        $newsRank = ['LOW' => 1, 'MEDIUM' => 2, 'HIGH' => 3, 'EXTREME' => 4, 'CRITICAL' => 4];
        $newsActive = ($newsRank[$newsRiskLevel] ?? 1) >= 2;
        $geoActive = in_array($geoDisplayLevel, ['HIGH', 'EXTREME', 'CRITICAL'], true);
        $modeActive = $executionMode !== '' && $executionMode !== 'NORMAL';

        return $newsActive || $geoActive || $modeActive;
    }
}

if (!function_exists('dashboard_macro_context_should_show')) {
    function dashboard_macro_context_should_show($macroSupports, $macroMessage = '')
    {
        $support = strtoupper(trim((string)$macroSupports));
        $message = strtolower(trim((string)$macroMessage));

        if (in_array($support, ['BUY', 'SELL', 'BULLISH', 'BEARISH', 'RISK_ON', 'RISK_OFF'], true)) {
            return true;
        }

        if ($message !== '' && !dashboard_text_contains_safe($message, 'no strong macro bias') && !dashboard_text_contains_safe($message, 'neutral')) {
            return true;
        }

        return false;
    }
}

if (!function_exists('dashboard_execution_guidance_message')) {
    function dashboard_execution_guidance_message($newsRiskLevel, $geoDisplayLevel, $executionMode, $fallback = '')
    {
        $newsRiskLevel = strtoupper(trim((string)$newsRiskLevel));
        $geoDisplayLevel = strtoupper(trim((string)$geoDisplayLevel));
        $executionMode = strtoupper(trim((string)$executionMode));
        $fallback = trim((string)$fallback);

        if (in_array($newsRiskLevel, ['HIGH', 'EXTREME', 'CRITICAL'], true)) {
            return 'High-impact news risk is active. Use reduced size, wait for confirmation, and avoid chasing fast candles.';
        }

        if ($newsRiskLevel === 'MEDIUM') {
            return 'Medium-impact news risk is active. Keep position size conservative and confirm direction before entry.';
        }

        if (in_array($geoDisplayLevel, ['HIGH', 'EXTREME', 'CRITICAL'], true)) {
            return 'Major geopolitical risk is active. Reduce exposure and wait for cleaner confirmation.';
        }

        if ($executionMode !== '' && $executionMode !== 'NORMAL') {
            return execution_mode_label($executionMode) . ' is active. Use stricter confirmation before execution.';
        }

        return $fallback !== '' ? $fallback : 'Use normal risk controls.';
    }
}


if (!function_exists('dashboard_clean_risk_level')) {
    function dashboard_clean_risk_level($risk, $eventsCount = 0)
    {
        $risk = strtoupper(trim((string)$risk));

        if ($eventsCount <= 0) {
            return 'LOW';
        }

        if (in_array($risk, ['HIGH', 'MEDIUM', 'LOW'], true)) {
            return $risk;
        }

        return 'LOW';
    }
}

if (!function_exists('dashboard_news_summary')) {
    function dashboard_news_summary(array $events = [])
    {
        if (empty($events)) {
            return 'No major scheduled forex news detected in the next hour.';
        }

        $first = $events[0];

        $title = trim((string)($first['title'] ?? $first['event'] ?? 'Major Forex Event'));
        $currency = trim((string)($first['currency'] ?? ''));
        $time = trim((string)($first['time'] ?? ''));

        $parts = [];

        if ($currency !== '') {
            $parts[] = $currency;
        }

        $parts[] = $title;

        if ($time !== '') {
            $parts[] = date('g:i A', strtotime($time));
        }

        return implode(' • ', $parts);
    }
}

if (!function_exists('safe_text')) {
    function safe_text($value, $fallback = 'N/A')
    {
        if ($value === null || $value === '') {
            return $fallback;
        }

        return e((string)$value);
    }
}

if (!function_exists('zone_status')) {
    function zone_status($price, $low, $high, $direction)
    {
        if ($price === null || $low === null || $high === null) {
            return 'Unknown';
        }

        $price = (float)$price;
        $low = (float)$low;
        $high = (float)$high;

        if ($low > $high) {
            [$low, $high] = [$high, $low];
        }

        if ($price >= $low && $price <= $high) {
            return 'Price is inside the institutional zone';
        }

        if ($direction === 'BUY') {
            return $price > $high
                ? 'Price is above the zone - wait for mitigation/pullback'
                : 'Price is below the zone - setup may be invalid or early';
        }

        if ($direction === 'SELL') {
            return $price < $low
                ? 'Price is below the zone - wait for mitigation/pullback'
                : 'Price is above the zone - setup may be invalid or early';
        }

        return 'Price is outside the zone';
    }
}

if (!function_exists('recent_high')) {
    function recent_high(array $candles)
    {
        if (empty($candles)) {
            return null;
        }
        $recent = array_slice($candles, -60);
        return max(array_map(fn($c) => (float)$c['high_price'], $recent));
    }
}

if (!function_exists('recent_low')) {
    function recent_low(array $candles)
    {
        if (empty($candles)) {
            return null;
        }
        $recent = array_slice($candles, -60);
        return min(array_map(fn($c) => (float)$c['low_price'], $recent));
    }
}

if (!function_exists('premium_discount_state')) {
    function premium_discount_state($price, $high, $low)
    {
        if ($price === null || $high === null || $low === null || $high <= $low) {
            return [
                'label' => 'UNKNOWN',
                'equilibrium' => null,
                'position' => null,
            ];
        }

        $equilibrium = ($high + $low) / 2;
        $position = (($price - $low) / ($high - $low)) * 100;
        $position = max(0, min(100, $position));

        if ($position >= 75) {
            $label = 'EXTREME PREMIUM';
        } elseif ($position >= 55) {
            $label = 'PREMIUM';
        } elseif ($position <= 25) {
            $label = 'EXTREME DISCOUNT';
        } elseif ($position <= 45) {
            $label = 'DISCOUNT';
        } else {
            $label = 'EQUILIBRIUM';
        }

        return [
            'label' => $label,
            'equilibrium' => $equilibrium,
            'position' => round($position, 2),
        ];
    }
}

if (!function_exists('scenario_entry_anchor')) {
    function scenario_entry_anchor($side, $low, $high, $currentPrice, $confirmationStage)
    {
        if (!is_numeric($low) || !is_numeric($high)) return null;

        $low = min((float)$low, (float)$high);
        $high = max((float)$low, (float)$high);
        $range = max(0.00000001, $high - $low);
        $currentPrice = is_numeric($currentPrice) ? (float)$currentPrice : null;
        $side = strtoupper((string)$side);
        $stage = strtoupper((string)$confirmationStage);

        /*
        |--------------------------------------------------------------------------
        | Institutional entry anchor
        |--------------------------------------------------------------------------
        | We do NOT use current market price as entry. For a SELL, the entry anchor
        | is the premium side of the supply zone. For a BUY, the entry anchor is the
        | discount side of the demand zone.
        |--------------------------------------------------------------------------
        */

        $entry = $side === 'SELL'
            ? ($high - ($range * 0.25))
            : ($low + ($range * 0.25));

        if ($currentPrice !== null && $stage !== 'EXECUTION_CONFIRMED') {
            if ($side === 'SELL' && $entry <= $currentPrice) {
                // Price already traded through the preferred sell entry area.
                // Do not pretend this is a fresh limit entry.
                return null;
            }

            if ($side === 'BUY' && $entry >= $currentPrice) {
                // Price already traded through the preferred buy entry area.
                // Do not pretend this is a fresh limit entry.
                return null;
            }
        }

        return $entry;
    }
}

if (!function_exists('scenario_entry_note')) {
    function scenario_entry_note($side, $entry, $currentPrice, $zoneLow, $zoneHigh, $confirmationStage)
    {
        $side = strtoupper((string)$side);
        $stage = strtoupper((string)$confirmationStage);
        $currentPrice = is_numeric($currentPrice) ? (float)$currentPrice : null;
        $low = is_numeric($zoneLow) ? (float)$zoneLow : null;
        $high = is_numeric($zoneHigh) ? (float)$zoneHigh : null;

        if ($entry === null) {
            if ($low !== null && $high !== null && $currentPrice !== null && $currentPrice >= min($low, $high) && $currentPrice <= max($low, $high)) {
                return 'Price is already inside the zone. Do not chase. Wait for candle confirmation before entry.';
            }

            return 'No fresh limit entry. Wait for price to return to a qualified zone.';
        }

        return $side === 'SELL'
            ? 'Sell entry is planned from the upper part of the supply zone.'
            : 'Buy entry is planned from the lower part of the demand zone.';
    }
}

if (!function_exists('scenario_entry_distance_display')) {
    function scenario_entry_distance_display($side, $entry, $currentPrice, $symbolCode, $decimals)
    {
        if ($entry === null || !is_numeric($entry) || !is_numeric($currentPrice)) {
            return 'Waiting for valid entry trigger';
        }

        $entry = (float)$entry;
        $currentPrice = (float)$currentPrice;
        $side = strtoupper((string)$side);

        if ($side === 'SELL' && $entry < $currentPrice) {
            return 'Entry is below current price - invalid for fresh sell limit';
        }

        if ($side === 'BUY' && $entry > $currentPrice) {
            return 'Entry is above current price - invalid for fresh buy limit';
        }

        $distance = abs($entry - $currentPrice);
        return trade_distance_label($distance, $symbolCode, $decimals) . ' from current price';
    }
}

if (!function_exists('scenario_is_fresh_entry_valid')) {
    function scenario_is_fresh_entry_valid($side, $entry, $currentPrice)
    {
        if ($entry === null || !is_numeric($entry) || !is_numeric($currentPrice)) return false;
        $side = strtoupper((string)$side);
        $entry = (float)$entry;
        $currentPrice = (float)$currentPrice;

        if ($side === 'SELL') return $entry > $currentPrice;
        if ($side === 'BUY') return $entry < $currentPrice;

        return false;
    }
}

if (!function_exists('recent_news_fallback_from_db')) {
    function recent_news_fallback_from_db($symbolCode, $minutesBack = 360, $minutesAhead = 360)
    {
        try {
            $pdo = db();
            $tables = $pdo->query("SHOW TABLES")->fetchAll(PDO::FETCH_COLUMN);
            $candidateTables = array_values(array_filter($tables, function ($t) {
                $name = strtolower((string)$t);
                return dashboard_text_contains_safe($name, 'calendar') || dashboard_text_contains_safe($name, 'news') || dashboard_text_contains_safe($name, 'event');
            }));

            $events = [];
            $currencies = [];
            $symbolCode = strtoupper(str_replace('/', '', (string)$symbolCode));

            if ($symbolCode === 'XAUUSD') {
                $currencies = ['USD'];
            } elseif (strlen($symbolCode) >= 6) {
                $currencies = [substr($symbolCode, 0, 3), substr($symbolCode, 3, 3)];
            }

            foreach ($candidateTables as $table) {
                $cols = $pdo->query("SHOW COLUMNS FROM `$table`")->fetchAll(PDO::FETCH_COLUMN);
                $lower = array_map('strtolower', $cols);

                $titleCol = null;
                foreach (['title', 'event', 'name', 'headline'] as $c) {
                    $idx = array_search($c, $lower, true);
                    if ($idx !== false) { $titleCol = $cols[$idx]; break; }
                }

                $timeCol = null;
                foreach (['time', 'event_time', 'date', 'datetime', 'published_at', 'created_at'] as $c) {
                    $idx = array_search($c, $lower, true);
                    if ($idx !== false) { $timeCol = $cols[$idx]; break; }
                }

                $impactCol = null;
                foreach (['impact', 'importance', 'priority', 'risk'] as $c) {
                    $idx = array_search($c, $lower, true);
                    if ($idx !== false) { $impactCol = $cols[$idx]; break; }
                }

                $currencyCol = null;
                foreach (['currency', 'country', 'symbol'] as $c) {
                    $idx = array_search($c, $lower, true);
                    if ($idx !== false) { $currencyCol = $cols[$idx]; break; }
                }

                if (!$titleCol || !$timeCol) continue;

                $sql = "SELECT * FROM `$table` WHERE `$timeCol` BETWEEN DATE_SUB(NOW(), INTERVAL :back MINUTE) AND DATE_ADD(NOW(), INTERVAL :ahead MINUTE) ORDER BY `$timeCol` ASC LIMIT 20";
                $stmt = $pdo->prepare($sql);
                $stmt->execute([':back' => (int)$minutesBack, ':ahead' => (int)$minutesAhead]);

                foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) {
                    $title = (string)($row[$titleCol] ?? '');
                    $impact = strtoupper((string)($impactCol ? ($row[$impactCol] ?? '') : ''));
                    $currency = strtoupper((string)($currencyCol ? ($row[$currencyCol] ?? '') : ''));

                    $titleUpper = strtoupper($title);
                    $isHigh = dashboard_text_contains_safe($impact, 'HIGH') || dashboard_text_contains_safe($impact, '3') || dashboard_text_contains_safe($titleUpper, 'CPI') || dashboard_text_contains_safe($titleUpper, 'FOMC') || dashboard_text_contains_safe($titleUpper, 'NFP') || dashboard_text_contains_safe($titleUpper, 'NONFARM') || dashboard_text_contains_safe($titleUpper, 'INTEREST RATE') || dashboard_text_contains_safe($titleUpper, 'FED');

                    $affects = empty($currencies);
                    foreach ($currencies as $cur) {
                        if ($currency === $cur || dashboard_text_contains_safe($titleUpper, $cur) || ($cur === 'USD' && (dashboard_text_contains_safe($currency, 'UNITED STATES') || dashboard_text_contains_safe($titleUpper, 'FED') || dashboard_text_contains_safe($titleUpper, 'CPI')))) {
                            $affects = true;
                            break;
                        }
                    }

                    if (!$isHigh || !$affects) continue;

                    $events[] = [
                        'title' => $title ?: 'High-impact economic event',
                        'time' => (string)($row[$timeCol] ?? ''),
                        'impact' => 'High',
                        'currency' => $currency,
                        'source' => $table,
                    ];
                }
            }

            if (!$events) {
                return null;
            }

            $primary = $events[0];
            $ts = strtotime($primary['time'] ?? '');
            $minutesUntil = $ts ? (int)round(($ts - time()) / 60) : null;
            $countdown = '';

            if ($minutesUntil !== null) {
                if ($minutesUntil >= 0) {
                    $countdown = $minutesUntil < 60 ? 'in ' . $minutesUntil . ' minutes' : 'in ' . intdiv($minutesUntil, 60) . 'h ' . ($minutesUntil % 60) . 'm';
                } else {
                    $ago = abs($minutesUntil);
                    $countdown = $ago < 60 ? $ago . ' minutes ago' : intdiv($ago, 60) . 'h ' . ($ago % 60) . 'm ago';
                }
            }

            return [
                'has_news' => true,
                'has_scheduled_event' => true,
                'risk' => 'HIGH',
                'scheduled_risk' => 'HIGH',
                'title' => $primary['title'] ?? 'High-impact economic event',
                'time' => $primary['time'] ?? '',
                'minutes_until' => $minutesUntil,
                'countdown_label' => $countdown,
                'message' => 'High-impact event detected from local calendar/news table.',
                'events' => $events,
                'scheduled_events' => $events,
                'source' => 'local_calendar_table_fallback',
            ];
        } catch (Throwable $e) {
            return null;
        }
    }
}

if (!function_exists('news_window_sentence')) {
    function news_window_sentence(array $newsRisk): string
    {
        $title = trim((string)($newsRisk['title'] ?? 'High-impact event'));
        $minutes = $newsRisk['minutes_until'] ?? null;

        if ($minutes === null || $minutes === '') {
            return $title ? $title : 'High-impact news risk detected.';
        }

        $minutes = (int)$minutes;

        if ($minutes >= 0) {
            if ($minutes <= 90) {
                return ($title ?: 'High-impact event') . ' is due ' . ($minutes < 60 ? 'in ' . $minutes . ' minutes' : 'in ' . intdiv($minutes, 60) . 'h ' . ($minutes % 60) . 'm') . '. Avoid new execution until the event clears.';
            }

            return ($title ?: 'High-impact event') . ' is scheduled later today. Keep it on watch, but do not block the setup yet.';
        }

        $ago = abs($minutes);
        if ($ago <= 120) {
            return ($title ?: 'High-impact event') . ' was released ' . ($ago < 60 ? $ago . ' minutes ago' : intdiv($ago, 60) . 'h ' . ($ago % 60) . 'm ago') . '. Wait for volatility to stabilize before execution.';
        }

        return ($title ?: 'High-impact event') . ' happened earlier. Treat it as background context unless volatility is still elevated.';
    }
}

if (!function_exists('inject_speech_risk_from_headlines')) {
    function inject_speech_risk_from_headlines(array $risk, string $symbolCode): array
    {
        $events = $risk['events'] ?? $risk['macro_events'] ?? $risk['scheduled_events'] ?? [];
        if (!is_array($events)) return $risk;

        $keywords = ['TRUMP', 'POWELL', 'FED CHAIR', 'FOMC MEMBER', 'ECB PRESIDENT', 'LAGARDE', 'BAILEY', 'BOJ', 'SPEECH', 'SPEAKS', 'TESTIFIES', 'PRESS CONFERENCE'];
        $symbolCode = strtoupper($symbolCode);
        $best = null;

        foreach ($events as $event) {
            if (!is_array($event)) continue;
            $title = strtoupper((string)($event['title'] ?? $event['event'] ?? $event['headline'] ?? ''));
            if ($title === '') continue;

            $match = false;
            foreach ($keywords as $kw) {
                if (dashboard_text_contains_safe($title, $kw)) {
                    $match = true;
                    break;
                }
            }

            if (!$match) continue;

            $ts = $event['timestamp'] ?? null;
            if (!$ts && !empty($event['time'])) $ts = strtotime((string)$event['time']);
            if (!$ts && !empty($event['published_at'])) $ts = strtotime((string)$event['published_at']);
            $minutesUntil = $ts ? (int)round(($ts - time()) / 60) : null;

            // Use speeches coming soon or just released as high-impact awareness.
            if ($minutesUntil === null || ($minutesUntil >= -120 && $minutesUntil <= 180)) {
                $best = $event;
                $best['minutes_until'] = $minutesUntil;
                break;
            }
        }

        if (!$best) return $risk;

        $title = $best['title'] ?? $best['event'] ?? $best['headline'] ?? 'Important speech / press conference';
        $minutesUntil = $best['minutes_until'] ?? null;
        $countdown = '';
        if ($minutesUntil !== null) {
            if ($minutesUntil >= 0) {
                $countdown = $minutesUntil < 60 ? 'in ' . $minutesUntil . ' minutes' : 'in ' . intdiv($minutesUntil, 60) . 'h ' . ($minutesUntil % 60) . 'm';
            } else {
                $ago = abs($minutesUntil);
                $countdown = $ago < 60 ? $ago . ' minutes ago' : intdiv($ago, 60) . 'h ' . ($ago % 60) . 'm ago';
            }
        }

        return array_merge($risk, [
            'has_news' => true,
            'has_scheduled_event' => true,
            'risk' => 'HIGH',
            'scheduled_risk' => 'HIGH',
            'title' => $title,
            'time' => $best['time'] ?? $best['published_at'] ?? ($risk['time'] ?? ''),
            'minutes_until' => $minutesUntil,
            'countdown_label' => $countdown,
            'message' => 'Speech / press conference risk detected.',
            'events' => array_slice(array_merge([$best], $events), 0, 10),
            'scheduled_events' => array_slice(array_merge([$best], $risk['scheduled_events'] ?? []), 0, 10),
            'source' => trim((string)($risk['source'] ?? '')) . '+speech_detection',
        ]);
    }
}

$userId = $_SESSION['user_id'] ?? null;
$userAlertPreferences = dashboard_user_alert_preferences($userId);

$symbols = SystemConfigService::getActiveSymbols(false, $userId);
if (!$symbols) {
    $symbols = SystemConfigService::getActiveSymbols(false);
}

if (!$symbols) {
    die('No active symbols found. Please add symbols from the admin settings.');
}

$symbolCode = $userRequestedAnalysis ? ($_POST['symbol'] ?? null) : null;
$symbol = null;

if (empty($symbol) || !$userRequestedAnalysis) {
    $symbol = '';
}

foreach ($symbols as $s) {
    if (($s['symbol'] ?? '') === $symbolCode) {
        $symbol = $s;
        break;
    }
}

if (!$symbol && $userRequestedAnalysis) {
    $symbol = $symbols[0];
}

if (!$symbol && !$userRequestedAnalysis) {
    $symbol = $symbols[0];
}

$symbolGroups = [];
foreach ($symbols as $symbolRow) {
    $assetType = strtoupper(trim((string)($symbolRow['asset_type'] ?? 'OTHER')));
    if ($assetType === '') {
        $assetType = 'OTHER';
    }
    $symbolGroups[$assetType][] = $symbolRow;
}

$selectedAssetType = strtoupper(trim((string)($_GET['asset_type'] ?? ($symbol['asset_type'] ?? ''))));
if ($selectedAssetType === '' || !isset($symbolGroups[$selectedAssetType])) {
    $selectedAssetType = strtoupper(trim((string)($symbol['asset_type'] ?? array_key_first($symbolGroups))));
}

$symbolCodeResolvedForMeta = (string)($symbol['symbol'] ?? '');
$symbol['decimals'] = dashboard_symbol_decimals_safe($symbolCodeResolvedForMeta, isset($symbol['decimals']) ? (int)$symbol['decimals'] : 2);
$symbol['pip_size'] = isset($symbol['pip_size']) && (float)$symbol['pip_size'] > 0 ? (float)$symbol['pip_size'] : dashboard_symbol_pip_size_safe($symbolCodeResolvedForMeta);
$symbol['contract_size'] = isset($symbol['contract_size']) && (float)$symbol['contract_size'] > 0 ? (float)$symbol['contract_size'] : dashboard_symbol_contract_size_safe($symbolCodeResolvedForMeta, 0);
$symbol['min_lot'] = isset($symbol['min_lot']) && (float)$symbol['min_lot'] > 0 ? (float)$symbol['min_lot'] : dashboard_symbol_min_lot_safe($symbolCodeResolvedForMeta);
$symbol['lot_step'] = isset($symbol['lot_step']) && (float)$symbol['lot_step'] > 0 ? (float)$symbol['lot_step'] : dashboard_symbol_lot_step_safe($symbolCodeResolvedForMeta);

$timeframes = SystemConfigService::getTimeframes();
if (!$timeframes) {
    die('No active timeframes found. Please add timeframes from the admin settings.');
}

$timeframe = $userRequestedAnalysis ? ($_POST['timeframe'] ?? null) : null;
$validTimeframe = false;

foreach ($timeframes as $tf) {
    if (($tf['code'] ?? '') === $timeframe) {
        $validTimeframe = true;
        break;
    }
}

if (!$validTimeframe && $userRequestedAnalysis) {
    $timeframe = $timeframes[0]['code'];
}

if (!$validTimeframe && !$userRequestedAnalysis) {
    $timeframe = '';
}

$tradingStyles = class_exists('TradingStyleService') ? TradingStyleService::getActiveStyles() : [];
$selectedStyle = $userRequestedAnalysis ? ($_POST['style'] ?? ($tradingStyles[0]['style_key'] ?? 'intraday')) : ($tradingStyles[0]['style_key'] ?? 'intraday');
$selectedStyleData = class_exists('TradingStyleService') ? TradingStyleService::getStyle($selectedStyle) : ['style_key' => 'intraday', 'style_name' => 'Intraday'];
$selectedStyle = $selectedStyleData['style_key'] ?? $selectedStyle;


if (!function_exists('dashboard_user_landing_alerts')) {
    function dashboard_user_landing_alerts($userId): array
    {
        $result = [
            'total' => 0,
            'active' => 0,
            'sent' => 0,
            'paused' => 0,
            'ready' => 0,
            'review' => 0,
            'rows' => [],
        ];

        if (!$userId) {
            return $result;
        }

        try {
            $tables = db()->query("SHOW TABLES LIKE 'institutional_alerts'")->fetchAll(PDO::FETCH_COLUMN);
            if (empty($tables)) {
                return $result;
            }

            $stmt = db()->prepare("SELECT COUNT(*) FROM institutional_alerts WHERE user_id = ?");
            $stmt->execute([$userId]);
            $result['total'] = (int)$stmt->fetchColumn();

            $stmt = db()->prepare("SELECT COUNT(*) FROM institutional_alerts WHERE user_id = ? AND is_active = 1 AND triggered_at IS NULL AND (expired_at IS NULL OR expired_at = '')");
            $stmt->execute([$userId]);
            $result['active'] = (int)$stmt->fetchColumn();

            $stmt = db()->prepare("SELECT COUNT(*) FROM institutional_alerts WHERE user_id = ? AND (triggered_at IS NOT NULL OR final_sent_at IS NOT NULL)");
            $stmt->execute([$userId]);
            $result['sent'] = (int)$stmt->fetchColumn();

            $stmt = db()->prepare("SELECT COUNT(*) FROM institutional_alerts WHERE user_id = ? AND is_active = 0 AND triggered_at IS NULL");
            $stmt->execute([$userId]);
            $result['paused'] = (int)$stmt->fetchColumn();

            $stmt = db()->prepare("SELECT COUNT(*) FROM institutional_alerts WHERE user_id = ? AND lifecycle_stage = 'READY_TO_EXECUTE' AND (triggered_at IS NULL OR triggered_at = '')");
            $stmt->execute([$userId]);
            $result['ready'] = (int)$stmt->fetchColumn();

            $stmt = db()->prepare("SELECT COUNT(*) FROM institutional_alerts WHERE user_id = ? AND (last_execution_checklist_status = 'FAILED' OR last_block_reason IS NOT NULL)");
            $stmt->execute([$userId]);
            $result['review'] = (int)$stmt->fetchColumn();

            $selectColumns = [
                'id', 'symbol', 'side', 'timeframe', 'style', 'lifecycle_stage', 'is_active',
                'created_at', 'tracking_started_at', 'last_checked_at',
                'stage1_sent_at', 'stage2_sent_at', 'stage3_sent_at', 'final_sent_at', 'triggered_at',
                'last_execution_checklist_status', 'last_execution_checklist_reason', 'last_block_reason',
                'entry_price', 'stop_loss', 'tp1'
            ];

            /*
            |--------------------------------------------------------------------------
            | Optional live/current price columns
            |--------------------------------------------------------------------------
            | Some deployments store the latest tracked price as current_price while
            | older tables may use last_price/latest_price/market_price. Build this
            | list dynamically so the idle dashboard never crashes if a column is
            | missing, but still displays the same precision shown in My Alerts when
            | the value exists.
            |--------------------------------------------------------------------------
            */
            try {
                $columnStmt = db()->query("SHOW COLUMNS FROM institutional_alerts");
                $availableColumns = array_map('strtolower', $columnStmt->fetchAll(PDO::FETCH_COLUMN) ?: []);

                foreach (['current_price', 'last_price', 'latest_price', 'market_price', 'baseline_current_price', 'last_market_snapshot'] as $optionalColumn) {
                    if (in_array(strtolower($optionalColumn), $availableColumns, true)) {
                        $selectColumns[] = $optionalColumn;
                    }
                }
            } catch (Throwable $e) {
                // Keep the required base columns only.
            }

            $safeSelect = implode(', ', array_map(static function ($column) {
                return '`' . str_replace('`', '', (string)$column) . '`';
            }, array_values(array_unique($selectColumns))));

            $stmt = db()->prepare("
                SELECT {$safeSelect}
                FROM institutional_alerts
                WHERE user_id = ?
                ORDER BY created_at DESC, id DESC
                LIMIT 6
            ");
            $stmt->execute([$userId]);
            $result['rows'] = $stmt->fetchAll(PDO::FETCH_ASSOC) ?: [];
        } catch (Throwable $e) {
            return $result;
        }

        return $result;
    }
}

if (!function_exists('dashboard_landing_time_label')) {
    function dashboard_landing_time_label($value): string
    {
        $value = trim((string)($value ?? ''));
        if ($value === '' || $value === '0000-00-00 00:00:00') {
            return 'N/A';
        }

        try {
            /* dashboard/user-facing landing should show Nigeria time */
            $dt = new DateTime($value);
            $dt->setTimezone(new DateTimeZone('Africa/Lagos'));
            return $dt->format('M j, Y · g:i A') . ' WAT';
        } catch (Throwable $e) {
            return $value;
        }
    }
}


if (!function_exists('dashboard_landing_price_decimals')) {
    function dashboard_landing_price_decimals($symbolCode, $value = null): int
    {
        $symbolCode = strtoupper(str_replace(['/', '-', '_', ' '], '', trim((string)$symbolCode)));

        try {
            $config = function_exists('dashboard_symbol_config_safe') ? dashboard_symbol_config_safe($symbolCode) : [];
            $configuredDecimals = (int)($config['decimals'] ?? 0);
            if ($configuredDecimals > 0) {
                return max(0, min(8, $configuredDecimals));
            }
        } catch (Throwable $e) {
        }

        try {
            if (class_exists('RiskService') && method_exists('RiskService', 'priceDecimals')) {
                $serviceDecimals = (int)RiskService::priceDecimals($symbolCode);
                if ($serviceDecimals > 0) {
                    return max(0, min(8, $serviceDecimals));
                }
            }
        } catch (Throwable $e) {
        }

        if ($symbolCode === 'XAUUSD') return 2;
        if ($symbolCode === 'XAGUSD') return 3;
        if (strpos($symbolCode, 'JPY') !== false) return 3;

        /*
        |--------------------------------------------------------------------------
        | Forex precision
        |--------------------------------------------------------------------------
        | Idle dashboard cards must match My Alerts. EURUSD/GBPUSD style symbols
        | should show 5 decimals, not the generic 2-decimal formatting that made
        | 1.16481 appear as 1.16.
        |--------------------------------------------------------------------------
        */
        if (preg_match('/^[A-Z]{6}$/', $symbolCode)) {
            return 5;
        }

        $assetType = function_exists('dashboard_symbol_asset_type_safe')
            ? dashboard_symbol_asset_type_safe($symbolCode)
            : '';

        if ($assetType === 'CRYPTO') {
            return is_numeric($value) && abs((float)$value) < 10 ? 4 : 2;
        }

        return is_numeric($value) && abs((float)$value) < 10 ? 5 : 2;
    }
}

if (!function_exists('dashboard_landing_price_label')) {
    function dashboard_landing_price_label(array $row, string $column): string
    {
        if (!array_key_exists($column, $row) || $row[$column] === null || $row[$column] === '' || !is_numeric($row[$column])) {
            return 'N/A';
        }

        $symbol = (string)($row['symbol'] ?? '');
        $decimals = dashboard_landing_price_decimals($symbol, $row[$column]);

        return number_format((float)$row[$column], $decimals, '.', '');
    }
}

if (!function_exists('dashboard_landing_cached_quote_value')) {
    function dashboard_landing_cached_quote_value(string $symbol)
    {
        static $quoteCache = [];

        $symbol = strtoupper(str_replace(['/', '-', '_', ' '], '', trim($symbol)));
        if ($symbol === '') {
            return null;
        }

        if (array_key_exists($symbol, $quoteCache)) {
            return $quoteCache[$symbol];
        }

        $quoteCache[$symbol] = null;

        try {
            $tables = db()->query("SHOW TABLES LIKE 'market_quotes'")->fetchAll(PDO::FETCH_COLUMN);
            if (empty($tables)) {
                return null;
            }

            $stmt = db()->prepare("\n                SELECT price\n                FROM market_quotes\n                WHERE UPPER(REPLACE(REPLACE(REPLACE(REPLACE(symbol, '/', ''), '-', ''), '_', ''), ' ', '')) = ?\n                  AND price IS NOT NULL\n                ORDER BY COALESCE(quote_time, created_at) DESC, id DESC\n                LIMIT 1\n            ");
            $stmt->execute([$symbol]);
            $price = $stmt->fetchColumn();

            if ($price !== false && $price !== null && $price !== '' && is_numeric($price)) {
                $quoteCache[$symbol] = (float)$price;
            }
        } catch (Throwable $e) {
            $quoteCache[$symbol] = null;
        }

        return $quoteCache[$symbol];
    }
}

if (!function_exists('dashboard_landing_snapshot_price_value')) {
    function dashboard_landing_snapshot_price_value($snapshot)
    {
        if ($snapshot === null || $snapshot === '') {
            return null;
        }

        $data = is_array($snapshot) ? $snapshot : json_decode((string)$snapshot, true);
        if (!is_array($data)) {
            return null;
        }

        foreach (['current_price', 'live_stage_price', 'live_crypto_price', 'candle_price', 'latest_price', 'last_price', 'market_price', 'price'] as $key) {
            if (array_key_exists($key, $data) && $data[$key] !== null && $data[$key] !== '' && is_numeric($data[$key])) {
                return (float)$data[$key];
            }
        }

        foreach ($data as $value) {
            if (is_array($value)) {
                $nested = dashboard_landing_snapshot_price_value($value);
                if ($nested !== null) {
                    return $nested;
                }
            }
        }

        return null;
    }
}

if (!function_exists('dashboard_landing_current_price_value')) {
    function dashboard_landing_current_price_value(array $row)
    {
        /*
        | First use saved price columns if the alert table has them.
        | Then fall back to last_market_snapshot written by AlertService.
        | Finally use the latest saved market_quotes row. This remains DB-only
        | and does not make a live market-data/API call on the idle dashboard.
        */
        foreach (['current_price', 'last_price', 'latest_price', 'market_price', 'baseline_current_price'] as $column) {
            if (array_key_exists($column, $row) && $row[$column] !== null && $row[$column] !== '' && is_numeric($row[$column])) {
                return (float)$row[$column];
            }
        }

        if (array_key_exists('last_market_snapshot', $row)) {
            $snapshotPrice = dashboard_landing_snapshot_price_value($row['last_market_snapshot']);
            if ($snapshotPrice !== null) {
                return $snapshotPrice;
            }
        }

        return dashboard_landing_cached_quote_value((string)($row['symbol'] ?? ''));
    }
}

if (!function_exists('dashboard_landing_current_price_label')) {
    function dashboard_landing_current_price_label(array $row): string
    {
        $value = dashboard_landing_current_price_value($row);
        if ($value === null) {
            return 'N/A';
        }

        $symbol = (string)($row['symbol'] ?? '');
        $decimals = dashboard_landing_price_decimals($symbol, $value);

        return number_format((float)$value, $decimals, '.', '');
    }
}


if (!function_exists('dashboard_landing_stage_label')) {
    function dashboard_landing_stage_label($stage): string
    {
        $stage = strtoupper((string)$stage);
        $map = [
            'TRACKING' => 'Monitoring',
            'ZONE_APPROACHED' => 'Stage 1: Zone Reached',
            'LIQUIDITY_SWEPT' => 'Stage 2: Liquidity Swept',
            'STRUCTURE_CONFIRMED' => 'Stage 3: Structure Confirmed',
            'READY_TO_EXECUTE' => 'Final Setup Ready',
            'ALERT_SENT' => 'Alert Sent',
            'INVALIDATED' => 'Invalidated',
            'EXPIRED' => 'Expired',
        ];
        return $map[$stage] ?? ucwords(strtolower(str_replace('_', ' ', $stage)));
    }
}

if (!function_exists('dashboard_landing_badge_class')) {
    function dashboard_landing_badge_class($value): string
    {
        $v = strtoupper((string)$value);
        if (in_array($v, ['BUY', 'READY_TO_EXECUTE', 'ALERT_SENT', 'SENT'], true)) return 'bg-emerald-50 text-emerald-700 border-emerald-200';
        if (in_array($v, ['SELL', 'FAILED', 'INVALIDATED', 'EXPIRED'], true)) return 'bg-rose-50 text-rose-700 border-rose-200';
        if (in_array($v, ['ZONE_APPROACHED', 'LIQUIDITY_SWEPT', 'STRUCTURE_CONFIRMED'], true)) return 'bg-amber-50 text-amber-700 border-amber-200';
        return 'bg-blue-50 text-blue-700 border-blue-200';
    }
}

if (!function_exists('dashboard_landing_clean_condition_text')) {
    function dashboard_landing_clean_condition_text(string $text): string
    {
        $text = trim($text);
        if ($text === '') {
            return '';
        }

        $map = [
            'Risk/reward' => 'risk/reward review',
            'Fresh displacement candle' => 'a fresh displacement candle',
            'BOS/CHoCH' => 'BOS/CHoCH confirmation',
            'Candle close confirmation' => 'closed-candle confirmation',
            'Setup validity' => 'setup-validity confirmation',
            'Liquidity sweep' => 'liquidity sweep',
            'Location' => 'location quality',
            'Session' => 'session context',
            'News' => 'news context',
        ];

        foreach ($map as $from => $to) {
            $text = str_ireplace($from, $to, $text);
        }

        $text = preg_replace('/\s+/', ' ', $text);
        return trim((string)$text);
    }
}

if (!function_exists('dashboard_landing_status_message')) {
    function dashboard_landing_status_message(array $row): string
    {
        $stage = strtoupper((string)($row['lifecycle_stage'] ?? 'TRACKING'));

        /*
         * User-facing stage language must stay consistent with My Alerts:
         * Tracking: waiting for the setup zone.
         * Stage 1: zone approached/touched; wait for liquidity sweep.
         * Stage 2: liquidity swept; wait for closed confirm-TF BOS/CHoCH + displacement.
         * Stage 3: structure confirmed; wait for final readiness and risk advisory.
         * Final: setup ready/delivered; RR is advisory and not part of the user-facing readiness status.
         */
        if (!empty($row['triggered_at']) || !empty($row['final_sent_at']) || $stage === 'ALERT_SENT') {
            return 'Final setup delivered. Review the entry plan, current news context and risk/reward guidance before taking action.';
        }

        if ($stage === 'READY_TO_EXECUTE') {
            return 'Final setup ready. Required readiness checks are complete. Review risk/reward guidance and position sizing before execution.';
        }

        if ($stage === 'STRUCTURE_CONFIRMED') {
            return 'Stage 3 complete: BOS/CHoCH and displacement confirmed on the confirmation timeframe. Waiting for final readiness and risk/reward guidance.';
        }

        if ($stage === 'LIQUIDITY_SWEPT') {
            return 'Stage 2 complete: liquidity has been swept. Waiting for a closed confirmation candle with BOS/CHoCH and displacement.';
        }

        if ($stage === 'ZONE_APPROACHED') {
            return 'Stage 1 complete: price has approached or touched the tracked zone. Waiting for liquidity sweep.';
        }

        if ($stage === 'INVALIDATED') {
            return 'Setup invalidated. Open the full alert history before tracking a new setup.';
        }

        if ($stage === 'EXPIRED') {
            return 'Setup expired. Run a fresh scan when you want a new market view.';
        }

        return 'Monitoring active: waiting for price to approach or touch the tracked institutional zone.';
    }
}


/*
|--------------------------------------------------------------------------
| Idle Dashboard Mode
|--------------------------------------------------------------------------
| Stop here until user explicitly clicks Analyze.
| This prevents API credits from being consumed on login or refresh.
|--------------------------------------------------------------------------
*/
if (!$userRequestedAnalysis) {
    $landingAlerts = dashboard_user_landing_alerts($userId);
    $recentAlerts = $landingAlerts['rows'] ?? [];
    ?>
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Institutional Trading Dashboard</title>
        <style>
            *{box-sizing:border-box}
            :root{
                --bg:#eef3f8;
                --panel:#ffffff;
                --ink:#0f172a;
                --muted:#64748b;
                --line:#dde7f1;
                --soft:#f8fafc;
                --blue:#2563eb;
                --navy:#07111f;
                --green:#047857;
                --red:#b91c1c;
                --amber:#b45309;
                --shadow:0 22px 55px rgba(15,23,42,.075);
            }
            body{margin:0;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:linear-gradient(180deg,#eef3f8 0%,#f8fafc 100%);color:var(--ink);}
            .page{max-width:1180px;margin:0 auto;padding:30px 18px 70px;}
            .hero{overflow:hidden;border:1px solid var(--line);border-radius:28px;background:var(--panel);box-shadow:var(--shadow);}
            .hero-inner{display:grid;grid-template-columns:minmax(0,1.45fr) 390px;gap:28px;align-items:stretch;background:radial-gradient(circle at 15% 20%,rgba(37,99,235,.30),transparent 30%),linear-gradient(135deg,#020617 0%,#0f172a 48%,#172554 100%);padding:34px;color:#fff;}
            .eyebrow{display:inline-flex;align-items:center;gap:8px;padding:8px 11px;border:1px solid rgba(191,219,254,.28);border-radius:999px;background:rgba(255,255,255,.08);color:#bfdbfe;font-size:11px;font-weight:800;letter-spacing:.16em;text-transform:uppercase;}
            .hero h1{margin:16px 0 0;font-size:clamp(34px,5vw,58px);line-height:.95;font-weight:850;letter-spacing:-.06em;}
            .hero-copy{margin:16px 0 0;max-width:760px;color:#dbeafe;font-size:17px;line-height:1.75;}
            .hero-points{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-top:26px;max-width:820px;}
            .point{border:1px solid rgba(255,255,255,.14);background:rgba(255,255,255,.07);border-radius:18px;padding:14px;}
            .point b{display:block;color:#fff;font-size:13px}.point span{display:block;color:#cbd5e1;margin-top:4px;font-size:12px;line-height:1.55;}
            .hero-panel{align-self:stretch;border:1px solid rgba(255,255,255,.15);border-radius:24px;background:rgba(255,255,255,.10);backdrop-filter:blur(12px);padding:22px;display:flex;flex-direction:column;justify-content:space-between;gap:18px;}
            .hero-panel h2{margin:0;font-size:18px;font-weight:800;letter-spacing:-.03em}.hero-panel p{margin:8px 0 0;color:#dbeafe;line-height:1.65;font-size:13.5px}
            .hero-actions{display:grid;grid-template-columns:1fr;gap:10px}.btn{display:inline-flex;align-items:center;justify-content:center;min-height:46px;border-radius:15px;padding:0 17px;border:0;text-decoration:none;font-size:14px;font-weight:800;cursor:pointer;transition:.18s ease}.btn:hover{transform:translateY(-1px)}.btn-primary{background:var(--blue);color:#fff;box-shadow:0 14px 26px rgba(37,99,235,.28)}.btn-monitor{background:#eff6ff;color:#1d4ed8;box-shadow:none;border:1px solid #bfdbfe;border-radius:999px;min-height:40px;padding:0 16px;font-size:12.5px;font-weight:750;letter-spacing:0;white-space:nowrap}.btn-monitor:hover{background:#dbeafe;color:#1e40af}.btn-dark{background:#0b1220;color:#fff}.btn-light{background:rgba(255,255,255,.12);color:#fff;border:1px solid rgba(255,255,255,.18)}.btn-soft{background:#eff6ff;color:#1d4ed8}.btn-danger{background:#fee2e2;color:#991b1b}.btn-sm{min-height:38px;border-radius:13px;padding:0 13px;font-size:12.5px}
            .stats-wrap{padding:22px 28px 28px;background:#fff;}
            .stats{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:14px}.stat{border:1px solid var(--line);border-radius:18px;background:linear-gradient(180deg,#fff,#f8fafc);padding:18px;text-align:center}.stat span{display:block;color:var(--muted);font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.1em}.stat strong{display:block;margin-top:7px;font-size:28px;line-height:1;font-weight:850;letter-spacing:-.04em}.stat .blue{color:#1d4ed8}.stat .green{color:#047857}.stat .amber{color:#b45309}.stat .red{color:#be123c}
            .main-grid{display:grid;grid-template-columns:minmax(0,1.2fr) 420px;gap:22px;margin-top:24px;align-items:start}.card{background:#fff;border:1px solid var(--line);border-radius:26px;box-shadow:var(--shadow);overflow:hidden}.card-head{padding:24px 26px;border-bottom:1px solid var(--line);display:flex;align-items:flex-start;justify-content:space-between;gap:16px}.card-head h2{margin:0;font-size:24px;font-weight:850;letter-spacing:-.045em}.card-head p{margin:6px 0 0;color:var(--muted);line-height:1.6;font-size:14px}.header-actions{display:flex;gap:10px;flex-wrap:wrap;align-items:center;justify-content:flex-end}.content{padding:22px 24px}.setup-list{display:grid;grid-template-columns:1fr;gap:14px}.setup{border:1px solid var(--line);border-radius:20px;background:linear-gradient(180deg,#fff,#f9fbfd);padding:18px}.setup-top{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.setup-name{font-size:18px;font-weight:850;letter-spacing:-.035em}.setup-meta{margin-top:5px;color:var(--muted);font-size:13px;font-weight:650;line-height:1.55}.badge{display:inline-flex;align-items:center;justify-content:center;min-height:26px;border-radius:999px;padding:0 10px;border:1px solid;font-size:11.5px;font-weight:800;white-space:nowrap}.metrics{display:grid;grid-template-columns:repeat(auto-fit,minmax(128px,1fr));gap:10px;margin-top:14px}.metric{background:#fff;border:1px solid #e5edf6;border-radius:15px;padding:11px 12px}.metric span{display:block;color:#64748b;font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.08em}.metric strong{display:block;margin-top:4px;font-size:15px;font-weight:800}.reason{margin-top:13px;color:#475569;font-size:13px;line-height:1.6;font-weight:650}.empty{border:1px dashed #cbd5e1;border-radius:20px;background:#f8fafc;padding:34px;text-align:center}.empty h3{margin:0;font-size:20px;font-weight:850}.empty p{margin:9px auto 0;max-width:520px;color:var(--muted);line-height:1.7}.form-grid{display:grid;grid-template-columns:1fr;gap:16px}.label{display:block}.label span{display:block;margin-bottom:8px;color:#64748b;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.1em}.field{width:100%;height:50px;border:1px solid #cbd5e1;border-radius:15px;background:#fff;padding:0 14px;color:#0f172a;font-size:15px;font-weight:700;outline:none}.field:focus{border-color:#2563eb;box-shadow:0 0 0 4px rgba(37,99,235,.12)}.footer-row{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-top:18px;padding-top:18px;border-top:1px solid #e2e8f0;color:#64748b;font-size:12.5px;font-weight:700}.quick-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin-top:22px}.quick{border:1px solid var(--line);border-radius:18px;background:#fff;padding:17px}.quick b{display:block;font-size:14px}.quick span{display:block;margin-top:4px;color:var(--muted);font-size:12.5px;line-height:1.55}
            @media(max-width:980px){.hero-inner{grid-template-columns:1fr;padding:28px}.hero-points{grid-template-columns:1fr}.main-grid{grid-template-columns:1fr}.stats{grid-template-columns:repeat(2,minmax(0,1fr))}.card-head{flex-direction:column}.header-actions{width:100%}.header-actions .btn{flex:1}.hero-panel{max-width:unset}.metrics{grid-template-columns:repeat(3,minmax(0,1fr))}}
            @media(max-width:560px){.page{padding:18px 12px 50px}.hero-inner{padding:22px}.stats-wrap{padding:18px}.stats{grid-template-columns:1fr}.content{padding:18px}.card-head{padding:20px}.setup-top{flex-direction:column}.metrics{grid-template-columns:1fr}.quick-grid{grid-template-columns:1fr}.hero h1{font-size:36px}}
        </style>
    </head>
    <body>
        <main class="page">
            <section class="hero">
                <div class="hero-inner">
                    <div>
                        <div class="eyebrow">Institutional Trading Assistant</div>
                        <h1>Institutional setup<br>monitor</h1>
                        <p class="hero-copy">Monitor active and delivered setups, follow stage progress and launch a new institutional scan when you want a fresh view of a symbol or timeframe.</p>
                        <div class="hero-points">
                            <div class="point"><b>Setup overview</b><span>Review active monitored setups, delivery status and the latest tracked price context.</span></div>
                            <div class="point"><b>Stage workflow</b><span>Follow zone reach, liquidity sweep, structure confirmation and final setup delivery.</span></div>
                            <div class="point"><b>New scan</b><span>Start a new analysis when you want the platform to evaluate a fresh trading opportunity.</span></div>
                        </div>
                    </div>
                    <aside class="hero-panel">
                        <div>
                            <h2>Continue monitoring or start a new scan</h2>
                            <p>Review your monitored setups, open the alert monitor, or run a fresh market scan when you want a new institutional view.</p>
                        </div>
                        <div class="hero-actions">
                            <a class="btn btn-primary" href="my_alerts.php">Open Alert Monitor</a>
                            <a class="btn btn-light" href="#analyze-market">Run New Scan</a>
                        </div>
                    </aside>
                </div>
                <div class="stats-wrap">
                    <div class="stats">
                        <div class="stat"><span>Total Setups</span><strong><?= (int)$landingAlerts['total'] ?></strong></div>
                        <div class="stat"><span>Active</span><strong class="blue"><?= (int)$landingAlerts['active'] ?></strong></div>
                        <div class="stat"><span>Ready</span><strong class="green"><?= (int)($landingAlerts['ready'] ?? 0) ?></strong></div>
                        <div class="stat"><span>Delivered</span><strong class="green"><?= (int)$landingAlerts['sent'] ?></strong></div>
                        <div class="stat"><span>Paused</span><strong class="amber"><?= (int)$landingAlerts['paused'] ?></strong></div>
                    </div>
                </div>
            </section>

            <section class="main-grid">
                <div class="card">
                    <div class="card-head">
                        <div>
                            <h2>Tracked setups</h2>
                            <p>Review your most recent monitored setups, current tracked price, delivery status and next confirmation step.</p>
                        </div>
                        <div class="header-actions">
                            <a class="btn btn-monitor btn-sm" href="my_alerts.php">Open monitor →</a>
                        </div>
                    </div>
                    <div class="content">
                        <?php if (empty($recentAlerts)): ?>
                            <div class="empty">
                                <h3>No tracked setups yet</h3>
                                <p>Run a new institutional scan, then track a qualified setup when you want the platform to monitor its confirmation stages.</p>
                            </div>
                        <?php else: ?>
                            <div class="setup-list">
                                <?php foreach ($recentAlerts as $alertRow): ?>
                                    <?php
                                        $stage = (string)($alertRow['lifecycle_stage'] ?? 'TRACKING');
                                        $side = strtoupper((string)($alertRow['side'] ?? ''));
                                        $statusReason = dashboard_landing_status_message($alertRow);
                                    ?>
                                    <article class="setup">
                                        <div class="setup-top">
                                            <div>
                                                <div class="setup-name"><?= e($alertRow['symbol'] ?? 'N/A') ?> <?= e($side) ?></div>
                                                <div class="setup-meta">
                                                    <?= e($alertRow['style'] ?? 'style') ?> · TF <?= e($alertRow['timeframe'] ?? 'N/A') ?> · Created <?= e(dashboard_landing_time_label($alertRow['tracking_started_at'] ?? ($alertRow['created_at'] ?? ''))) ?>
                                                </div>
                                            </div>
                                            <div class="header-actions">
                                                <span class="badge <?= e(dashboard_landing_badge_class($side)) ?>"><?= e($side ?: 'N/A') ?></span>
                                                <span class="badge <?= e(dashboard_landing_badge_class($stage)) ?>"><?= e(dashboard_landing_stage_label($stage)) ?></span>
                                            </div>
                                        </div>
                                        <div class="metrics">
                                            <div class="metric"><span>Current</span><strong><?= e(dashboard_landing_current_price_label($alertRow)) ?></strong></div>
                                            <div class="metric"><span>Entry</span><strong><?= e(dashboard_landing_price_label($alertRow, 'entry_price')) ?></strong></div>
                                            <div class="metric"><span>Stop Loss</span><strong><?= e(dashboard_landing_price_label($alertRow, 'stop_loss')) ?></strong></div>
                                            <div class="metric"><span>TP1</span><strong><?= e(dashboard_landing_price_label($alertRow, 'tp1')) ?></strong></div>
                                        </div>
                                        <div class="reason"><?= e($statusReason) ?></div>
                                    </article>
                                <?php endforeach; ?>
                            </div>
                            <div class="footer-row">
                                <span>Showing latest <?= (int)count($recentAlerts) ?> setup(s)</span>
                                <a class="btn btn-soft btn-sm" href="my_alerts.php">Open full alert history</a>
                            </div>
                        <?php endif; ?>
                    </div>
                </div>

                <aside class="card" id="analyze-market">
                    <div class="card-head">
                        <div>
                            <h2>New market scan</h2>
                            <p>Select a symbol and timeframe to build a fresh institutional read, review market structure and prepare a new setup view.</p>
                        </div>
                    </div>
                    <div class="content">
                        <form method="post" action="dashboard.php" class="form-grid">
                            <input type="hidden" name="analyze" value="1">

                            <label class="label">
                                <span>Symbol</span>
                                <select name="symbol" required class="field">
                                    <option value="">Choose symbol</option>
                                    <?php foreach ($symbolGroups as $groupName => $groupSymbols): ?>
                                        <optgroup label="<?= e($groupName) ?>">
                                            <?php foreach ($groupSymbols as $row): ?>
                                                <option value="<?= e($row['symbol'] ?? '') ?>"><?= e($row['symbol'] ?? '') ?><?= !empty($row['name']) ? ' - ' . e($row['name']) : '' ?></option>
                                            <?php endforeach; ?>
                                        </optgroup>
                                    <?php endforeach; ?>
                                </select>
                            </label>

                            <label class="label">
                                <span>Timeframe</span>
                                <select name="timeframe" required class="field">
                                    <option value="">Choose timeframe</option>
                                    <?php foreach ($timeframes as $tf): ?>
                                        <?php $tfCode = (string)($tf['code'] ?? ''); ?>
                                        <option value="<?= e($tfCode) ?>"><?= e($tfCode) ?></option>
                                    <?php endforeach; ?>
                                </select>
                            </label>

                            <label class="label">
                                <span>Trading Style</span>
                                <select name="style" class="field">
                                    <?php foreach ($tradingStyles as $style): ?>
                                        <option value="<?= e($style['style_key'] ?? '') ?>"><?= e($style['style_name'] ?? $style['style_key'] ?? 'Style') ?></option>
                                    <?php endforeach; ?>
                                </select>
                            </label>

                            <button type="submit" class="btn btn-primary" style="width:100%;">Run New Scan</button>
                        </form>


                        <div class="quick-grid">
                            <div class="quick"><b>What the scan reviews</b><span>Directional bias, liquidity, institutional zones, confirmation timeframe, news context and execution plan.</span></div>
                            <div class="quick"><b>After the scan</b><span>Track a qualified setup to monitor zone reach, liquidity sweep, structure confirmation and final setup delivery.</span></div>
                        </div>
                    </div>
                </aside>
            </section>
        </main>
        <script>if(window.history&&window.history.replaceState){window.history.replaceState(null,'','dashboard.php');}</script>
    </body>
    </html>
    <?php
    exit;
}

/*
|--------------------------------------------------------------------------
| Live Quote Safe Defaults
|--------------------------------------------------------------------------
| These defaults prevent undefined variable warnings before the dashboard
| decides whether to use a live crypto quote or the latest saved candle close.
|--------------------------------------------------------------------------
*/
$liveQuote = [
    'success' => false,
    'provider' => null,
    'price' => null,
    'source' => null,
    'timestamp' => null,
    'error' => '',
];

$livePriceDisplay = null;
$livePriceSource = 'Latest saved candle close';
$livePriceTimestamp = null;
$livePriceWarning = '';

$apiInterval = SystemConfigService::getApiInterval($timeframe);
$fetchStatus = 'Using saved market data.';
$lastUpdated = null;

if ($freshMarketDataRequested && $apiInterval) {
    /*
    | Production credit guard:
    | Analyze should request a live quote, not silently burn TwelveData credits
    | with candle refreshes. Candle syncing belongs to cron/sync_candles.php.
    | Crypto candles are supplied by Binance cron. Forex/metals use saved candles
    | plus a guarded live quote on Analyze.
    */
    $selectedAssetForGuard = strtoupper((string)($symbol['asset_type'] ?? ''));
    $allowPaidDashboardCandleRefresh = function_exists('dashboard_text_contains_safe')
        ? false
        : false;

    if (class_exists('ApiCreditGuard')) {
        $allowPaidDashboardCandleRefresh = ApiCreditGuard::boolSetting('dashboard_allow_paid_candle_refresh', false);
    }

    if ($allowPaidDashboardCandleRefresh && class_exists('ApiCreditGuard')) {
        $guard = ApiCreditGuard::shouldSyncCandles($symbol['symbol'], 'twelvedata', $selectedAssetForGuard ?: ApiCreditGuard::guessAssetType($symbol['symbol'], $symbol), 'dashboard', true);
        if (!empty($guard['allowed'])) {
            $cacheKey = 'market_fetch_' . $symbol['symbol'] . '_' . $timeframe;
            if (!isset($_SESSION[$cacheKey]) || (time() - $_SESSION[$cacheKey]) > 300) {
                $_SESSION[$cacheKey] = time();
                ob_start();
                $freshData = MarketDataService::fetchCandles($symbol['symbol'], $apiInterval, 2);
                ob_end_clean();
                if ($freshData && is_array($freshData)) {
                    foreach ($freshData as $candle) {
                        if (empty($candle['datetime'])) continue;
                        CandleService::insertCandle($symbol['symbol'], $timeframe, $candle['datetime'], $candle['open'], $candle['high'], $candle['low'], $candle['close'], $candle['volume'] ?? null);
                    }
                    $fetchStatus = 'Market data checked.';
                } else {
                    $fetchStatus = 'Using last saved market data.';
                }
            }
        } else {
            $fetchStatus = 'API guard: candle refresh skipped. Live quote requested separately.';
        }
    } else {
        $fetchStatus = 'API guard: no candle API call on Analyze. Live quote requested separately.';
    }
}

$candles = CandleService::getCandles($symbol['symbol'], $timeframe, 200);

/*
|--------------------------------------------------------------------------
| Live Price Selection
|--------------------------------------------------------------------------
| Crypto symbols use QuoteService/Binance Vision when available.
| Non-crypto symbols keep the existing saved-candle flow to avoid displaying
| paid-provider/TwelveData errors on the dashboard.
|--------------------------------------------------------------------------
*/
$assetTypeForLiveQuote = strtoupper(trim((string)($symbol['asset_type'] ?? '')));

if ($freshMarketDataRequested && class_exists('QuoteService')) {
    try {
        $liveQuote = QuoteService::getLivePrice($symbol['symbol'] ?? '');

        if (!empty($liveQuote['success']) && isset($liveQuote['price']) && is_numeric($liveQuote['price'])) {
            $livePriceDisplay = (float)$liveQuote['price'];
            $livePriceSource = (($liveQuote['provider'] ?? '') === 'binance_vision')
                ? 'Binance Vision Live'
                : 'Live market quote';
            $livePriceTimestamp = $liveQuote['timestamp'] ?? date('Y-m-d H:i:s');
        }
    } catch (Throwable $e) {
        $livePriceWarning = 'Live quote unavailable. Using saved candle close.';
    }
}

if (($livePriceDisplay === null || !is_numeric($livePriceDisplay)) && !empty($candles)) {
    $lastCandleForLivePrice = end($candles);
    $livePriceDisplay = isset($lastCandleForLivePrice['close_price'])
        ? (float)$lastCandleForLivePrice['close_price']
        : null;
    $livePriceSource = 'Latest saved candle close';
    $livePriceTimestamp = $lastCandleForLivePrice['open_time'] ?? $lastUpdated ?? null;
}

if ($livePriceDisplay === null || !is_numeric($livePriceDisplay)) {
    $livePriceDisplay = 0;
    $livePriceSource = 'No price available';
}

if ($livePriceDisplay === null && !empty($candles)) {
    $lastCandle = end($candles);
    $livePriceDisplay = (float)($lastCandle['close_price'] ?? 0);
}

$stmt = db()->prepare('
    SELECT MAX(open_time)
    FROM candles
    WHERE symbol = ? AND timeframe = ?
');
$stmt->execute([$symbol['symbol'], $timeframe]);
$lastUpdated = $stmt->fetchColumn();

/*
|-------------------------------------------------------------------------- | Phase 1B: Multi-Source Macro + News + Geopolitical Risk Intelligence
|-------------------------------------------------------------------------- | Uses aggregator services first, then falls back to direct FMP-based services.
*/

$preNewsRisk = [
    'has_news' => false,
    'risk' => 'LOW',
    'score_penalty' => 0,
    'title' => '',
    'time' => '',
    'message' => 'No high-impact macro/news risk detected.',
    'events' => [],
    'source' => 'fallback',
];

if (class_exists('NewsRiskService') && method_exists('NewsRiskService', 'check')) {
    try {
        /*
        |--------------------------------------------------------------------------
        | DB-first advisory news engine
        |--------------------------------------------------------------------------
        | This must be the primary news source now. It reads from news_events,
        | applies XAUUSD-sensitive weighting, includes medium-impact/speech risk,
        | and returns advisory data without stopping setup generation.
        |--------------------------------------------------------------------------
        */
        $preNewsRisk = NewsRiskService::check($symbol['symbol'], 240, $userId);
    } catch (Throwable $e) {
        $preNewsRisk['message'] = 'News engine unavailable.';
    }
} elseif (class_exists('NewsAggregatorService') && method_exists('NewsAggregatorService', 'check')) {
    try {
        $preNewsRisk = NewsAggregatorService::check($symbol['symbol'], 90);
    } catch (Throwable $e) {
        $preNewsRisk['message'] = 'Multi-source news aggregator failed. Using fallback risk status.';
    }
} elseif (class_exists('NewsService') && method_exists('NewsService', 'checkHighImpactNews')) {
    try {
        $preNewsRisk = NewsService::checkHighImpactNews($symbol['symbol'], 90);
    } catch (Throwable $e) {
        $preNewsRisk['message'] = 'News feed failed. Using clear news status.';
    }
}

/*
|--------------------------------------------------------------------------
| Direct High-Impact News Safety Check
|--------------------------------------------------------------------------
| The aggregator is preferred, but this direct calendar pass prevents the
| dashboard from missing high-impact news released recently or due soon.
|--------------------------------------------------------------------------
*/

$directCalendarRisk = null;

if (class_exists('EconomicCalendarService') && method_exists('EconomicCalendarService', 'check')) {
    try {
        $directCalendarRisk = EconomicCalendarService::check($symbol['symbol'], 240, 240);
    } catch (Throwable $e) {
        $directCalendarRisk = null;
    }
}

if (is_array($directCalendarRisk) && !empty($directCalendarRisk['has_news'])) {
    $directRank = ['LOW' => 1, 'MEDIUM' => 2, 'HIGH' => 3, 'EXTREME' => 4];
    $currentRisk = strtoupper((string)($preNewsRisk['risk'] ?? 'LOW'));
    $directRisk = strtoupper((string)($directCalendarRisk['risk'] ?? 'LOW'));

    if (($directRank[$directRisk] ?? 0) >= ($directRank[$currentRisk] ?? 0)) {
        $primaryEvent = $directCalendarRisk['events'][0] ?? [];
        $eventTime = $directCalendarRisk['time'] ?? ($primaryEvent['time'] ?? '');
        $timestamp = $primaryEvent['timestamp'] ?? ($eventTime ? strtotime((string)$eventTime) : null);
        $minutesUntil = $timestamp ? (int)round(($timestamp - time()) / 60) : null;

        $countdown = '';
        if ($minutesUntil !== null) {
            if ($minutesUntil >= 0) {
                $countdown = $minutesUntil < 60 ? 'in ' . $minutesUntil . ' minutes' : 'in ' . floor($minutesUntil / 60) . 'h ' . ($minutesUntil % 60) . 'm';
            } else {
                $countdown = abs($minutesUntil) < 60 ? abs($minutesUntil) . ' minutes ago' : floor(abs($minutesUntil) / 60) . 'h ' . (abs($minutesUntil) % 60) . 'm ago';
            }
        }

        $preNewsRisk = array_merge($preNewsRisk, [
            'has_news' => true,
            'has_scheduled_event' => true,
            'risk' => $directRisk,
            'scheduled_risk' => $directRisk,
            'score_penalty' => max((int)($preNewsRisk['score_penalty'] ?? 0), (int)($directCalendarRisk['score_penalty'] ?? 0)),
            'title' => $directCalendarRisk['title'] ?? ($primaryEvent['title'] ?? 'High-impact economic event'),
            'time' => $eventTime,
            'minutes_until' => $minutesUntil,
            'countdown_label' => $countdown,
            'message' => $directCalendarRisk['message'] ?? 'High-impact economic calendar risk detected.',
            'events' => array_slice(array_merge($directCalendarRisk['events'] ?? [], $preNewsRisk['events'] ?? []), 0, 10),
            'scheduled_events' => $directCalendarRisk['events'] ?? [],
            'source' => 'direct_economic_calendar+safety_check',
        ]);
    }
}

/*
|--------------------------------------------------------------------------
| Direct High-Impact News Safety Check v2
|--------------------------------------------------------------------------
| Wider window: 6 hours behind and 6 hours ahead. This catches high-impact
| releases that just happened and events approaching soon.
|--------------------------------------------------------------------------
*/

if (class_exists('EconomicCalendarService') && method_exists('EconomicCalendarService', 'check')) {
    try {
        $calendarV2 = EconomicCalendarService::check($symbol['symbol'], 360, 360);

        if (is_array($calendarV2) && !empty($calendarV2['events'])) {
            $rank = ['LOW' => 1, 'MEDIUM' => 2, 'HIGH' => 3, 'EXTREME' => 4];
            $calendarRisk = strtoupper((string)($calendarV2['risk'] ?? 'LOW'));
            $currentRisk = strtoupper((string)($preNewsRisk['risk'] ?? 'LOW'));

            if (($rank[$calendarRisk] ?? 0) >= ($rank[$currentRisk] ?? 0)) {
                $primary = $calendarV2['events'][0] ?? [];
                $eventTime = $calendarV2['time'] ?? ($primary['time'] ?? '');
                $timestamp = $primary['timestamp'] ?? ($eventTime ? strtotime((string)$eventTime) : null);
                $minutesUntil = $timestamp ? (int)round(($timestamp - time()) / 60) : null;
                $countdown = '';

                if ($minutesUntil !== null) {
                    if ($minutesUntil >= 0) {
                        $countdown = $minutesUntil < 60 ? 'in ' . $minutesUntil . ' minutes' : 'in ' . intdiv($minutesUntil, 60) . 'h ' . ($minutesUntil % 60) . 'm';
                    } else {
                        $ago = abs($minutesUntil);
                        $countdown = $ago < 60 ? $ago . ' minutes ago' : intdiv($ago, 60) . 'h ' . ($ago % 60) . 'm ago';
                    }
                }

                $preNewsRisk = array_merge($preNewsRisk, [
                    'has_news' => true,
                    'has_scheduled_event' => true,
                    'risk' => $calendarRisk,
                    'scheduled_risk' => $calendarRisk,
                    'title' => $calendarV2['title'] ?? ($primary['title'] ?? $primary['event'] ?? 'High-impact economic event'),
                    'time' => $eventTime,
                    'minutes_until' => $minutesUntil,
                    'countdown_label' => $countdown,
                    'message' => $calendarV2['message'] ?? 'High-impact calendar risk detected.',
                    'events' => array_slice(array_merge($calendarV2['events'] ?? [], $preNewsRisk['events'] ?? []), 0, 10),
                    'scheduled_events' => $calendarV2['events'] ?? [],
                    'source' => 'economic_calendar_safety_v2',
                ]);
            }
        }
    } catch (Throwable $e) {
        // Never break dashboard because of news feed.
    }
}

$localNewsFallback = recent_news_fallback_from_db($symbol['symbol'], 360, 360);
if (is_array($localNewsFallback) && !empty($localNewsFallback['has_news'])) {
    $rank = ['LOW' => 1, 'MEDIUM' => 2, 'HIGH' => 3, 'EXTREME' => 4];
    $localRisk = strtoupper((string)($localNewsFallback['risk'] ?? 'LOW'));
    $currentRisk = strtoupper((string)($preNewsRisk['risk'] ?? 'LOW'));

    if (($rank[$localRisk] ?? 0) >= ($rank[$currentRisk] ?? 0)) {
        $preNewsRisk = array_merge($preNewsRisk, $localNewsFallback);
    }
}

$preNewsRisk = inject_speech_risk_from_headlines($preNewsRisk, $symbol['symbol']);

/*
|--------------------------------------------------------------------------
| Final Dashboard News Normalization
|--------------------------------------------------------------------------
| This controls what the dashboard visibly displays. It prevents stale
| headlines or generic provider responses from showing permanent HIGH risk,
| while still detecting real upcoming medium/high-impact calendar events.
*/
$preNewsRisk = dashboard_sanitize_news_risk($preNewsRisk, $symbol['symbol'], 360);

/*
|--------------------------------------------------------------------------
| Re-apply advisory news payload after dashboard normalization
|--------------------------------------------------------------------------
| dashboard_sanitize_news_risk() cleans event display, but the new
| NewsRiskService also returns advisory fields such as confidence_adjustment,
| execution_mode and trade_allowed. Preserve those fields for the signal UI.
|--------------------------------------------------------------------------
*/
if (class_exists('NewsRiskService') && method_exists('NewsRiskService', 'check')) {
    try {
        $advisoryNewsRisk = NewsRiskService::check($symbol['symbol'], 240, $userId);
        if (is_array($advisoryNewsRisk) && !empty($advisoryNewsRisk['has_news'])) {
            $advisoryEvents = $advisoryNewsRisk['events'] ?? [];
            $preNewsRisk = array_merge($preNewsRisk, $advisoryNewsRisk);
            $preNewsRisk['scheduled_events'] = $advisoryEvents;
            $preNewsRisk['has_scheduled_event'] = !empty($advisoryEvents);
            $preNewsRisk['scheduled_risk'] = $advisoryNewsRisk['risk'] ?? ($preNewsRisk['scheduled_risk'] ?? 'LOW');
        }
    } catch (Throwable $e) {
        // Keep sanitized fallback.
    }
}


/*
|--------------------------------------------------------------------------
| Final Direct news_events Table Override
|--------------------------------------------------------------------------
| This prevents the dashboard from showing "no upcoming news" when the cron
| has populated news_events but a service layer or old sanitizer missed it.
|--------------------------------------------------------------------------
*/
try {
    $directDbNewsRisk = dashboard_db_news_risk($symbol['symbol'], 1440, 360);
    if (is_array($directDbNewsRisk) && !empty($directDbNewsRisk['has_news'])) {
        $rank = ['LOW' => 1, 'MEDIUM' => 2, 'HIGH' => 3, 'EXTREME' => 4, 'CRITICAL' => 4];
        $dbRisk = strtoupper((string)($directDbNewsRisk['risk'] ?? 'LOW'));
        $currentRisk = strtoupper((string)($preNewsRisk['risk'] ?? 'LOW'));

        if (empty($preNewsRisk['has_news']) || (($rank[$dbRisk] ?? 0) >= ($rank[$currentRisk] ?? 0))) {
            $preNewsRisk = array_merge($preNewsRisk, $directDbNewsRisk);
        } else {
            $preNewsRisk['events'] = array_slice(array_merge($preNewsRisk['events'] ?? [], $directDbNewsRisk['events'] ?? []), 0, 10);
            $preNewsRisk['scheduled_events'] = array_slice(array_merge($preNewsRisk['scheduled_events'] ?? [], $directDbNewsRisk['scheduled_events'] ?? []), 0, 10);
            $preNewsRisk['has_news'] = true;
            $preNewsRisk['has_scheduled_event'] = true;
        }
    }
} catch (Throwable $e) {
    // Direct DB override must never break dashboard rendering.
}

$upcomingForexNews60 = $preNewsRisk['scheduled_events'] ?? dashboard_major_forex_news($symbol['symbol'], 360);

$preGeo = [
    'risk' => 'UNKNOWN',
    'impact' => 'Neutral',
    'score_penalty' => 0,
    'message' => 'Geopolitical intelligence engine unavailable.',
    'sentiment' => 0,
    'events' => [],
    'source' => 'fallback',
];

if (class_exists('GeoPoliticalAggregator') && method_exists('GeoPoliticalAggregator', 'analyze')) {
    try {
        $preGeo = GeoPoliticalAggregator::analyze($symbol['symbol']);
    } catch (Throwable $e) {
        $preGeo['message'] = 'Geopolitical intelligence is temporarily unavailable. Using fallback risk status.';
    }
} elseif (class_exists('GeoRiskService') && method_exists('GeoRiskService', 'analyze')) {
    try {
        $preGeo = GeoRiskService::analyze($symbol['symbol']);
    } catch (Throwable $e) {
        $preGeo['message'] = 'Geopolitical engine unavailable.';
    }
} elseif (class_exists('GeoPoliticalService') && method_exists('GeoPoliticalService', 'analyze')) {
    try {
        $preGeo = GeoPoliticalService::analyze($symbol['symbol']);
    } catch (Throwable $e) {
        $preGeo['message'] = 'Geopolitical feed failed. Using neutral risk.';
    }
}

$preGeo = dashboard_sanitize_geo_risk(is_array($preGeo) ? $preGeo : []);

$market = null;

if (class_exists('MarketStructureService') && method_exists('MarketStructureService', 'analyze')) {
    try {
        $market = MarketStructureService::analyze($symbol['symbol'], $timeframe, $selectedStyle, $preNewsRisk, $preGeo);
    } catch (Throwable $e) {
        $market = null;
    }
}

if (is_array($market) && !empty($market)) {
    $currentPrice = $market['current_price'] ?? null;
    $bias = $market['execution_bias'] ?? ($market['bias'] ?? 'WAIT');
    $contextBias = $market['context_bias'] ?? ($market['higher_timeframe_bias'] ?? 'NEUTRAL');
    $contextTimeframe = $market['context_timeframe'] ?? (class_exists('MultiTimeframeService') ? MultiTimeframeService::getHigherTimeframe($timeframe) : 'HTF');
    $structureEvents = $market['structure_events'] ?? ($market['events'] ?? []);
    $latestEvent = $market['latest_event'] ?? (!empty($structureEvents) ? end($structureEvents) : null);
    $liquidity = $market['liquidity'] ?? [];
    $orderBlocks = $market['order_blocks'] ?? [];
    $activeOrderBlock = $market['active_order_block'] ?? null;
    $latestOrderBlock = $market['latest_order_block'] ?? (!empty($orderBlocks) ? end($orderBlocks) : null);
    $fvgZones = $market['fvg_zones'] ?? [];
    $activeFVG = $market['active_fvg'] ?? null;
    $latestFvg = $market['latest_fvg'] ?? (!empty($fvgZones) ? end($fvgZones) : null);
    $tradePlan = $market['trade_plan'] ?? [];
    $decision = $market['decision'] ?? [];
    $atr = $market['atr'] ?? 0;
    $volatility = $market['volatility'] ?? 'UNKNOWN';
    $rangePosition = $market['range_position'] ?? null;
    $dealingRange = $market['dealing_range'] ?? [];
    $rangeHigh = $dealingRange['range_high'] ?? ($market['range_high'] ?? recent_high($candles));
    $rangeLow = $dealingRange['range_low'] ?? ($market['range_low'] ?? recent_low($candles));
    $equilibrium = $dealingRange['equilibrium'] ?? ($market['equilibrium'] ?? null);
    $pdLabel = $market['price_location'] ?? ($market['array'] ?? 'UNKNOWN');
    $institutionalNarrative = $market['narrative'] ?? '';
} else {
    $swings = SmcAnalyzer::detectSwings($candles);
    $structureEvents = SmcAnalyzer::detectStructureEvents($swings);
    $bias = SmcAnalyzer::getBias($structureEvents);
    $contextBias = 'NEUTRAL';
    $contextTimeframe = class_exists('MultiTimeframeService') ? MultiTimeframeService::getHigherTimeframe($timeframe) : 'HTF';
    $liquidity = SmcAnalyzer::getLiquidityTargets($swings);
    $orderBlocks = SmcAnalyzer::detectOrderBlocks($candles, $structureEvents);
    $activeOrderBlock = SmcAnalyzer::getActiveOrderBlock($orderBlocks, $bias);
    $latestOrderBlock = !empty($orderBlocks) ? end($orderBlocks) : null;
    $fvgZones = SmcAnalyzer::detectFVG($candles);
    $activeFVG = SmcAnalyzer::getActiveFVG($fvgZones, $bias);
    $latestFvg = !empty($fvgZones) ? end($fvgZones) : null;
    $latestEvent = !empty($structureEvents) ? end($structureEvents) : null;

    $tradePlan = SmcAnalyzer::buildTradePlan(
        $candles,
        $bias,
        $liquidity,
        $activeOrderBlock,
        $activeFVG,
        $symbol,
        $selectedStyleData
    );
    $tradePlan = SmcAnalyzer::enhanceTradePlan($tradePlan);

    $currentPrice = $tradePlan['current_price'] ?? null;
    if ($currentPrice === null && !empty($candles)) {
        $lastCandle = end($candles);
        $currentPrice = (float)$lastCandle['close_price'];
    }

    $rangeHigh = recent_high($candles);
    $rangeLow = recent_low($candles);
    $pd = premium_discount_state($currentPrice, $rangeHigh, $rangeLow);
    $pdLabel = $pd['label'];
    $equilibrium = $pd['equilibrium'];
    $rangePosition = $pd['position'];
    $atr = class_exists('VolatilityService') ? VolatilityService::calculateATR($candles) : 0;
    $volatility = class_exists('VolatilityService') ? VolatilityService::classifyVolatility($symbol['symbol'], $atr) : 'UNKNOWN';
    $decision = [];
    $institutionalNarrative = '';
}

if ($currentPrice === null && !empty($candles)) {
    $lastCandle = end($candles);
    $currentPrice = (float)$lastCandle['close_price'];
}

/*
|--------------------------------------------------------------------------
| Live Quote Layer
|--------------------------------------------------------------------------
| Historical analysis still uses candles from the database, but execution
| distance, current price display, zone proximity, and alert context should
| use the freshest available quote. Crypto uses Binance Vision through
| QuoteService. Forex/metals use TwelveData quote when available, otherwise
| latest candle close is used as fallback.
|--------------------------------------------------------------------------
*/

$liveQuote = [
    'success' => true,
    'provider' => 'database',
    'source' => 'latest_candle_close',
    'price' => $currentPrice,
    'timestamp' => $lastUpdated,
    'error' => '',
];

$liveQuotePrice = null;
$liveQuoteSourceLabel = 'Latest saved candle close';
$liveQuoteWarning = '';

$selectedAssetTypeForQuote = strtoupper(trim((string)($symbol['asset_type'] ?? '')));
$isCryptoSymbolForQuote = $selectedAssetTypeForQuote === 'CRYPTO';

if (!$isCryptoSymbolForQuote) {
    $symbolCodeForQuote = strtoupper((string)($symbol['symbol'] ?? ''));
    foreach (['BTC', 'ETH', 'SOL', 'BNB', 'XRP', 'DOGE', 'ADA', 'AVAX', 'DOT', 'LTC', 'LINK'] as $cryptoCode) {
        if (strpos($symbolCodeForQuote, $cryptoCode) !== false) {
            $isCryptoSymbolForQuote = true;
            break;
        }
    }
}

/*
|--------------------------------------------------------------------------
| Live Quote Decision
|--------------------------------------------------------------------------
| Crypto uses QuoteService/Binance Vision for real-time ticker.
| Forex/metals/indices intentionally keep using the latest saved candle close
| from your existing fetch_market.php/candle workflow. This prevents raw
| TwelveData API messages from appearing in the dashboard and preserves the
| original forex/metal behavior.
|--------------------------------------------------------------------------
*/

$quoteShouldRefresh = $freshMarketDataRequested || ($analysisViewRequest && $isCryptoSymbolForQuote);

if ($quoteShouldRefresh) {
    try {
        $quoteResult = null;

        if ($isCryptoSymbolForQuote && function_exists('dashboard_crypto_live_quote_safe')) {
            /* Crypto quotes are free/public Binance calls and should not fall back to stale candles while the analysis page is open. */
            $quoteResult = dashboard_crypto_live_quote_safe($symbol['symbol']);
        } elseif (class_exists('QuoteService') && method_exists('QuoteService', 'getLivePrice')) {
            $quoteResult = QuoteService::getLivePrice($symbol['symbol']);
        }

        if (
            is_array($quoteResult)
            && !empty($quoteResult['success'])
            && isset($quoteResult['price'])
            && is_numeric($quoteResult['price'])
        ) {
            $liveQuote = array_merge($liveQuote, $quoteResult);
            $liveQuotePrice = (float)$quoteResult['price'];
            $currentPrice = $liveQuotePrice;
            $liveQuoteSourceLabel = dashboard_quote_source_label($quoteResult);
            $livePriceDisplay = $currentPrice;
            $livePriceSource = $liveQuoteSourceLabel;
            $livePriceTimestamp = $quoteResult['timestamp'] ?? gmdate('Y-m-d H:i:s');

            if (isset($tradePlan) && is_array($tradePlan)) {
                $tradePlan['current_price'] = $currentPrice;
            }

            if (isset($market) && is_array($market)) {
                $market['current_price'] = $currentPrice;
                $market['live_quote'] = $quoteResult;
            }
        } elseif ($isCryptoSymbolForQuote) {
            $liveQuoteWarning = 'Live Binance quote unavailable. Displaying latest saved candle close.';
        }
    } catch (Throwable $e) {
        $liveQuoteWarning = $isCryptoSymbolForQuote ? 'Live Binance quote failed. Displaying latest saved candle close.' : '';
    }
}

$liquidityBuySide = $liquidity['buy_side'] ?? null;
$liquiditySellSide = $liquidity['sell_side'] ?? null;

if ($liquidityBuySide === null && !empty($liquidity['draw_on_liquidity']['target']) && ($liquidity['draw_on_liquidity']['side'] ?? '') === 'BUY SIDE') {
    $liquidityBuySide = $liquidity['draw_on_liquidity']['target'];
}

if ($liquiditySellSide === null && !empty($liquidity['draw_on_liquidity']['target']) && ($liquidity['draw_on_liquidity']['side'] ?? '') === 'SELL SIDE') {
    $liquiditySellSide = $liquidity['draw_on_liquidity']['target'];
}

$activeObZoneStatus = $activeOrderBlock
    ? zone_status(
        $currentPrice,
        $activeOrderBlock['zone_low'] ?? null,
        $activeOrderBlock['zone_high'] ?? null,
        $activeOrderBlock['direction'] ?? ''
    )
    : 'No active order block detected.';

$activeFvgZoneStatus = $activeFVG
    ? zone_status(
        $currentPrice,
        $activeFVG['low'] ?? null,
        $activeFVG['high'] ?? null,
        $activeFVG['direction'] ?? ''
    )
    : 'No active FVG detected.';

$geo = $preGeo;

$newsRisk = $preNewsRisk;

if (class_exists('RiskScoringEngine') && method_exists('RiskScoringEngine', 'apply')) {
    try {
        $tradePlan = RiskScoringEngine::apply($tradePlan, $newsRisk, $geo);
    } catch (Throwable $e) {
        // Risk scoring fallback should never break dashboard rendering.
    }
}

$confluence = [
    'score' => $tradePlan['score'] ?? 0,
    'decision' => $decision['label'] ?? 'UNAVAILABLE',
    'confidence' => $decision['confidence'] ?? ($tradePlan['quality'] ?? 'UNKNOWN'),
];

if (class_exists('ConfluenceService') && method_exists('ConfluenceService', 'calculate')) {
    try {
        $confluence = ConfluenceService::calculate($tradePlan, $newsRisk, $geo);
    } catch (Throwable $e) {
        // Keep fallback confluence.
    }
}

if (($geo['risk'] ?? '') === 'HIGH' && in_array(($tradePlan['status'] ?? ''), ['READY', 'EXECUTION_READY', 'SELECTIVE_EXECUTION'], true)) {
    $tradePlan['geo_warning'] = true;
    $tradePlan['risk_guidance'] = geo_risk_guidance_text($geo['risk'] ?? 'UNKNOWN', $geo['impact'] ?? '');
    // Keep the trade plan visible. Geopolitical risk should warn/reduce risk, not erase the setup.
}

/*
|--------------------------------------------------------------------------
| Early Location Context
|--------------------------------------------------------------------------
| This must be declared before final reason / hero decision logic uses it.
| It also protects the dashboard from undefined variable notices when the
| service does not return location_quality fields.
|--------------------------------------------------------------------------
*/

$earlyLocationQuality = $tradePlan['location_quality'] ?? ($market['location_quality'] ?? []);
$earlyLocationQualityScore = (float)($tradePlan['location_quality_score'] ?? ($earlyLocationQuality['score'] ?? 50));
$earlyLocationQualityLabel = $tradePlan['location_quality_label'] ?? ($earlyLocationQuality['label'] ?? 'Neutral Location');
$earlyLocationQualityState = $tradePlan['location_quality_state'] ?? ($earlyLocationQuality['state'] ?? 'NEUTRAL');
$earlyLocationGuidance = $tradePlan['location_guidance'] ?? ($earlyLocationQuality['guidance'] ?? '');
$earlyLocationExecutionClass = $tradePlan['location_execution_class'] ?? ($earlyLocationQuality['execution_class'] ?? 'WATCHLIST');

$priceLocationRaw = strtoupper((string)($pdLabel ?? 'UNKNOWN'));
$biasRaw = strtoupper((string)($bias ?? ($tradePlan['direction'] ?? 'WAIT')));
$contextBiasRaw = strtoupper((string)($contextBias ?? 'NEUTRAL'));
$isBuyInPremium = $biasRaw === 'BUY' && dashboard_text_contains_safe($priceLocationRaw, 'PREMIUM');
$isSellInDiscount = $biasRaw === 'SELL' && dashboard_text_contains_safe($priceLocationRaw, 'DISCOUNT');

if ($isBuyInPremium) {
    $earlyLocationQualityScore = dashboard_text_contains_safe($priceLocationRaw, 'EXTREME') ? min($earlyLocationQualityScore, 20) : min($earlyLocationQualityScore, 35);
    $earlyLocationQualityLabel = dashboard_text_contains_safe($priceLocationRaw, 'EXTREME') ? 'Late Buy Location' : 'Premium Buy - Reduced RR';
    $earlyLocationQualityState = dashboard_text_contains_safe($priceLocationRaw, 'EXTREME') ? 'LATE_BUY_LOCATION' : 'MOMENTUM_BUY_REDUCED_RR';
    $earlyLocationExecutionClass = dashboard_text_contains_safe($priceLocationRaw, 'EXTREME') ? 'CAUTION' : 'REDUCED_RR';
    $earlyLocationGuidance = dashboard_text_contains_safe($priceLocationRaw, 'EXTREME')
        ? 'BUY setup is late because price is in extreme premium. Wait for discount pullback or confirmed breakout continuation.'
        : 'BUY setup is above equilibrium. Treat as momentum continuation only and use reduced RR.';
}

if ($isSellInDiscount) {
    $earlyLocationQualityScore = dashboard_text_contains_safe($priceLocationRaw, 'EXTREME') ? min($earlyLocationQualityScore, 20) : min($earlyLocationQualityScore, 35);
    $earlyLocationQualityLabel = dashboard_text_contains_safe($priceLocationRaw, 'EXTREME') ? 'Late Sell Location' : 'Discount Sell - Reduced RR';
    $earlyLocationQualityState = dashboard_text_contains_safe($priceLocationRaw, 'EXTREME') ? 'LATE_SELL_LOCATION' : 'MOMENTUM_SELL_REDUCED_RR';
    $earlyLocationExecutionClass = dashboard_text_contains_safe($priceLocationRaw, 'EXTREME') ? 'CAUTION' : 'REDUCED_RR';
    $earlyLocationGuidance = dashboard_text_contains_safe($priceLocationRaw, 'EXTREME')
        ? 'SELL setup is late because price is in extreme discount. Wait for premium pullback or confirmed breakdown continuation.'
        : 'SELL setup is below equilibrium. Treat as momentum continuation only and use reduced RR.';
}

$tradePlan['location_quality_score'] = $earlyLocationQualityScore;
$tradePlan['location_quality_label'] = $earlyLocationQualityLabel;
$tradePlan['location_quality_state'] = $earlyLocationQualityState;
$tradePlan['location_guidance'] = $earlyLocationGuidance;
$tradePlan['location_execution_class'] = $earlyLocationExecutionClass;

if (in_array(strtoupper((string)$earlyLocationExecutionClass), ['REDUCED_RR', 'CAUTION'], true)) {
    $tradePlan['display_status'] = strtoupper((string)$earlyLocationExecutionClass) === 'CAUTION'
        ? 'Location Caution'
        : 'Reduced RR';

    $tradePlan['execution_mode'] = strtoupper((string)$earlyLocationExecutionClass) === 'CAUTION'
        ? 'LOCATION_CAUTION'
        : 'REDUCED_RR';

    if (!empty($earlyLocationGuidance)) {
        $tradePlan['execution_guidance'] = $earlyLocationGuidance;
    }
}

$earlyConfirmationStage = $tradePlan['confirmation_stage'] ?? (($tradePlan['phase3']['stage'] ?? ($market['phase3']['stage'] ?? 'CONFIRMATION_PENDING')));
$earlyConfirmationCount = (int)($tradePlan['confirmation_count'] ?? (($tradePlan['phase3']['confirmation_count'] ?? ($market['phase3']['confirmation_count'] ?? 0))));
$earlyPartialConfirmationCount = (int)($tradePlan['partial_confirmation_count'] ?? (($tradePlan['phase3']['partial_count'] ?? ($market['phase3']['partial_count'] ?? 0))));
$earlyRawConfluenceScore = (float)($confluence['score'] ?? ($tradePlan['score'] ?? 0));
$earlyInstitutionalCap = institutional_score_cap_value(
    $earlyConfirmationCount,
    $earlyPartialConfirmationCount,
    $earlyConfirmationStage,
    $tradePlan['status'] ?? '',
    $earlyLocationExecutionClass
);
$earlyDisplayConfluenceScore = round(min($earlyRawConfluenceScore, (float)$earlyInstitutionalCap), 1);

$finalAction = $decision['action'] ?? 'WAIT';
$finalReason = $decision['summary'] ?? ($tradePlan['message'] ?? 'Wait for clearer institutional confirmation.');
$finalSubText = !empty($institutionalNarrative)
    ? $institutionalNarrative
    : ($tradePlan['guidance'] ?? 'Wait for institutional confirmation.');

if (($tradePlan['status'] ?? '') === 'NEWS_CAUTION') {
    $finalSubText = $macroExecutionGuidance;
} elseif (trim(strip_tags((string)$finalSubText)) === trim(strip_tags((string)$finalReason))) {
    $finalSubText = 'Monitor liquidity sweep, displacement confirmation, and execution-zone reaction before entry.';
}

if (in_array(strtoupper((string)$earlyLocationExecutionClass), ['REDUCED_RR', 'CAUTION'], true)) {
    if (!empty($earlyLocationGuidance)) {
        $finalReason = $earlyLocationGuidance;
    }

    if (strtoupper((string)$earlyLocationExecutionClass) === 'REDUCED_RR') {
        $decisionTone = 'caution';
    }
}

if ($earlyRawConfluenceScore > $earlyDisplayConfluenceScore && $earlyConfirmationStage !== 'EXECUTION_CONFIRMED') {
    $finalReason = 'Setup is structurally valid, but execution confirmation is incomplete. Wait for mitigation, displacement, liquidity sweep, or candle trigger before execution.';
}

$finalReason = clean_dashboard_text($finalReason, 'Wait for clearer institutional confirmation.');
$finalSubText = clean_dashboard_text($finalSubText, 'Monitor liquidity sweep, displacement confirmation, and execution-zone reaction before entry.');

$decisionTone = $decision['tone'] ?? 'neutral';
$status = $tradePlan['status'] ?? 'STANDBY';

if (in_array(strtoupper((string)$earlyLocationExecutionClass), ['REDUCED_RR', 'CAUTION'], true)) {
    $decisionTone = 'caution';
}

if (in_array($status, ['AVOID TRADE', 'NO DATA', 'NO_DATA', 'INVALID', 'SKIP'], true)) {
    $finalAction = 'WAIT';
    $decisionTone = in_array($status, ['INVALID', 'SKIP'], true) ? 'danger' : 'caution';
} elseif (in_array($status, ['EXECUTION_READY', 'READY', 'SELECTIVE_EXECUTION'], true)) {
    $finalAction = $tradePlan['direction'] ?? $finalAction;
    if ($finalAction === 'BUY') {
        $decisionTone = 'bullish';
    } elseif ($finalAction === 'SELL') {
        $decisionTone = 'bearish';
    }
} elseif (in_array($status, ['NEWS_CAUTION', 'NEWS_CAUTION', 'GEOPOLITICAL_RISK_ELEVATED', 'LOCATION_CAUTION', 'REDUCED_RR', 'DIRECTIONAL_CONFLICT'], true)) {
    $finalAction = 'WAIT';
    $decisionTone = 'caution';
} elseif (in_array($status, ['PULLBACK_NEEDED', 'WAIT_FOR_MITIGATION', 'WAIT_FOR_CONFIRMATION', 'WATCHLIST', 'WAIT_FOR_RETRACEMENT', 'WATCH', 'STANDBY', 'SELECTIVE_EXECUTION', 'NO_VALID_SETUP'], true)) {
    $finalAction = 'WAIT';
    $decisionTone = $status === 'SELECTIVE_EXECUTION' ? 'caution' : 'neutral';
}

if ($decisionTone === 'bullish') {
    $finalBg = 'from-emerald-950 via-emerald-800 to-emerald-600';
    $finalBadge = 'bg-emerald-100 text-emerald-800';
} elseif ($decisionTone === 'bearish') {
    $finalBg = 'from-rose-950 via-red-800 to-red-700';
    $finalBadge = 'bg-red-100 text-red-800';
} elseif ($decisionTone === 'danger') {
    $finalBg = 'from-red-950 via-red-900 to-orange-700';
    $finalBadge = 'bg-red-100 text-red-800';
} elseif ($decisionTone === 'caution') {
    $finalBg = 'from-amber-950 via-amber-800 to-orange-600';
    $finalBadge = 'bg-amber-100 text-amber-800';
} else {
    $finalBg = 'from-slate-950 via-blue-950 to-slate-800';
    $finalBadge = 'bg-blue-100 text-blue-800';
}

$directionalBiasDisplay = $tradePlan['bias_context'] ?? $bias ?? ($tradePlan['direction'] ?? 'WAIT');
if (!in_array($directionalBiasDisplay, ['BUY', 'SELL', 'WAIT'], true)) {
    $directionalBiasDisplay = $tradePlan['direction'] ?? 'WAIT';
}
$executionStateDisplay = $tradePlan['display_status'] ?? ($tradePlan['status'] ?? 'WAIT');
$executionStateDisplay = clean_status_label($executionStateDisplay);
$currentDistanceFromEntry = (isset($tradePlan['entry_price'], $currentPrice)) ? abs((float)$currentPrice - (float)$tradePlan['entry_price']) : null;
$slDistanceForDisplay = isset($tradePlan['sl_distance']) ? (float)$tradePlan['sl_distance'] : (isset($tradePlan['entry_price'], $tradePlan['stop_loss']) ? abs((float)$tradePlan['entry_price'] - (float)$tradePlan['stop_loss']) : null);
$qualityLabel = trim((string)($confluence['decision'] ?? 'UNAVAILABLE'));
$confidenceLabel = trim((string)($confluence['confidence'] ?? 'UNKNOWN'));

$newsRiskLevel = strtoupper((string)($newsRisk['risk'] ?? 'LOW'));
$scheduledRiskLevel = strtoupper((string)($newsRisk['scheduled_risk'] ?? $newsRisk['risk'] ?? 'LOW'));
$macroRiskLevel = strtoupper((string)($newsRisk['macro_risk'] ?? 'LOW'));
$geoRiskLevel = strtoupper((string)($geo['risk'] ?? 'LOW'));

$newsEventsForDisplay = [];
if (!empty($newsRisk['scheduled_events']) && is_array($newsRisk['scheduled_events'])) {
    $newsEventsForDisplay = $newsRisk['scheduled_events'];
} elseif (!empty($newsRisk['events']) && is_array($newsRisk['events'])) {
    $newsEventsForDisplay = $newsRisk['events'];
}

$hasScheduledEvent = !empty($newsRisk['has_scheduled_event']) || !empty($newsEventsForDisplay);
$scheduledEventTitle = $hasScheduledEvent ? (string)($newsRisk['title'] ?? 'Medium/high-impact event') : '';
$scheduledEventTime = $hasScheduledEvent ? (string)($newsRisk['time'] ?? '') : '';
$scheduledCountdown = $hasScheduledEvent ? (string)($newsRisk['countdown_label'] ?? '') : '';

$macroBiasText = (string)($newsRisk['macro_summary'] ?? 'No strong macro bias detected.');
$macroSupports = strtoupper((string)($newsRisk['macro_supports'] ?? 'NEUTRAL'));

$geoRiskSummary = (string)($geo['message'] ?? geo_risk_guidance_text($geoRiskLevel, $geo['impact'] ?? ''));

$primaryNewsEvent = is_array($newsEventsForDisplay) && !empty($newsEventsForDisplay) ? $newsEventsForDisplay[0] : [];
$primaryNewsTitle = $scheduledEventTitle ?: ($primaryNewsEvent['title'] ?? $primaryNewsEvent['event'] ?? '');
$primaryNewsTime = $scheduledEventTime ?: ($primaryNewsEvent['time'] ?? '');
$newsDisplayMessage = $hasScheduledEvent
    ? (string)($newsRisk['message'] ?? news_window_sentence($newsRisk))
    : 'No medium/high-impact forex news detected in the next 6 hours.';

$newsCautionEnabled = !empty($userAlertPreferences['avoid_news_trades']);
$tradeHighImpactNews = !empty($userAlertPreferences['trade_high_impact_news']);
$tradeMediumImpactNews = !empty($userAlertPreferences['trade_medium_impact_news']);
$newsTradeAllowed = (bool)($newsRisk['trade_allowed'] ?? true);
$newsConfidenceAdjustment = (int)($newsRisk['confidence_adjustment'] ?? $newsRisk['score_penalty'] ?? 0);

$newsPreferenceLabel = $newsCautionEnabled
    ? 'News caution is ON: setups still generate, but the dashboard will mark caution if your news-trading preference is not met.'
    : 'News caution is OFF: setups are generated normally while still displaying news risk advisories.';

// Upcoming scheduled news is awareness/risk adaptation only; it must not invalidate the setup.
$macroPenalty = $newsConfidenceAdjustment + (int)($geo['score_penalty'] ?? 0);

$macroExecutionGuidance = 'No scheduled medium/high-impact event is currently affecting execution risk.';
if ($hasScheduledEvent && in_array($scheduledRiskLevel, ['HIGH', 'EXTREME', 'CRITICAL'], true)) {
    $macroExecutionGuidance = (string)($newsRisk['message'] ?? news_window_sentence($newsRisk));
} elseif ($hasScheduledEvent) {
    $macroExecutionGuidance = (string)($newsRisk['message'] ?? news_window_sentence($newsRisk));
} elseif ($geoRiskLevel === 'HIGH') {
    $macroExecutionGuidance = 'Use reduced size until volatility stabilizes. Wait for confirmation before execution.';
} elseif ($macroRiskLevel === 'HIGH' || $macroRiskLevel === 'MEDIUM') {
    $macroExecutionGuidance = 'Macro headlines are active. Use them as directional context, not as an automatic trade blocker.';
}

$marketStateLabel = $tradePlan['display_status'] ?? ($tradePlan['status'] ?? 'WAIT');
$locationLabel = $pdLabel . ($rangePosition !== null ? ' (' . number_format((float)$rangePosition, 1) . '%)' : '');

if ($biasRaw === 'SELL' && dashboard_text_contains_safe($priceLocationRaw, 'PREMIUM')) {
    $locationLabel .= ' - favorable sell location';
}

if ($biasRaw === 'BUY' && dashboard_text_contains_safe($priceLocationRaw, 'DISCOUNT')) {
    $locationLabel .= ' - favorable buy location';
}

$confirmationLabel = $confidenceLabel ?: ($tradePlan['quality'] ?? 'UNKNOWN');
$readinessLabel = $qualityLabel ?: ($confluence['decision'] ?? 'UNAVAILABLE');

$phase2ExecutionMode = $tradePlan['execution_mode'] ?? ($market['execution_mode']['mode'] ?? 'NORMAL');
$phase2ExecutionGuidance = $tradePlan['execution_guidance'] ?? ($market['execution_mode']['guidance'] ?? 'Follow normal confirmation rules.');
$setupLifecycleStage = $tradePlan['lifecycle_stage'] ?? ($market['setup_lifecycle']['stage'] ?? 'FORMING');
$setupLifecycleGuidance = $tradePlan['lifecycle_guidance'] ?? ($market['setup_lifecycle']['guidance'] ?? 'Setup is forming.');
$setupLifecycleProgress = (int)($tradePlan['lifecycle_progress'] ?? ($market['setup_lifecycle']['progress'] ?? 20));
$sessionName = $tradePlan['session_name'] ?? ($market['session_intelligence']['name'] ?? 'Session');
$sessionGuidance = $market['session_intelligence']['guidance'] ?? 'Session data is not available yet.';
$symbolModel = $tradePlan['symbol_model'] ?? ($market['symbol_personality']['name'] ?? 'Symbol Model');
$symbolModelGuidance = $market['symbol_personality']['guidance'] ?? 'Symbol personality data is not available yet.';
$alignmentStrength = (int)($tradePlan['alignment_strength'] ?? ($market['alignment_strength'] ?? 0));
$rankedSetups = $market['ranked_setups'] ?? [];

$phase3 = $tradePlan['phase3'] ?? ($market['phase3'] ?? []);
$confirmationStage = $tradePlan['confirmation_stage'] ?? ($phase3['stage'] ?? 'CONFIRMATION_PENDING');
$confirmationReadiness = $tradePlan['confirmation_readiness'] ?? ($phase3['readiness'] ?? 'Wait for confirmation');
$confirmationGuidance = $tradePlan['confirmation_guidance'] ?? ($phase3['guidance'] ?? 'Execution confirmation is pending.');
$confirmationCount = (int)($tradePlan['confirmation_count'] ?? ($phase3['confirmation_count'] ?? 0));

$mitigationState = $phase3['mitigation'] ?? [];
$displacementState = $phase3['displacement'] ?? [];
$liquiditySweepState = $phase3['liquidity_sweep'] ?? [];
$candleConfirmationState = $phase3['candle_confirmation'] ?? [];

$partialConfirmationCount = (int)($tradePlan['partial_confirmation_count'] ?? ($phase3['partial_count'] ?? 0));
$triggerState = $tradePlan['trigger_state'] ?? ($phase3['trigger_state'] ?? 'PENDING_TRIGGER');
$invalidationState = $tradePlan['invalidation_state'] ?? ($phase3['invalidation_state'] ?? 'ACTIVE');
$probabilityScore = (float)($tradePlan['probability_score'] ?? 0);
$locationQuality = $tradePlan['location_quality'] ?? ($market['location_quality'] ?? []);
$locationQualityScore = $earlyLocationQualityScore;
$locationQualityLabel = $earlyLocationQualityLabel;
$locationQualityState = $earlyLocationQualityState;
$locationGuidance = $earlyLocationGuidance ?: 'Location quality is neutral. Wait for confirmation.';
$locationExecutionClass = $earlyLocationExecutionClass;
$phase2ExecutionModeDisplay = execution_mode_label($phase2ExecutionMode);

/*
|--------------------------------------------------------------------------
| Final Institutional Display Calibration
|--------------------------------------------------------------------------
| Prevents the dashboard from showing A+/10 or VERY HIGH confidence when
| the setup has not passed execution confirmation.
|--------------------------------------------------------------------------
*/

$rawConfluenceScore = (float)($confluence['score'] ?? ($tradePlan['score'] ?? 0));
$institutionalCap = $tradePlan['institutional_score_cap'] ?? institutional_score_cap_value(
    $confirmationCount,
    $partialConfirmationCount,
    $confirmationStage,
    $tradePlan['status'] ?? '',
    $locationExecutionClass
);

$displayConfluenceScore = round(min($rawConfluenceScore, (float)$institutionalCap), 1);

/*
|--------------------------------------------------------------------------
| News-aware confidence adaptation
|--------------------------------------------------------------------------
| The setup is still generated, but high/medium-impact news can reduce the
| displayed confidence. NewsRiskService returns confidence_adjustment in
| percentage-like points, so convert -15 to -1.5 on the 0-10 score scale.
|--------------------------------------------------------------------------
*/
$newsConfidenceAdjustmentForScore = (int)($newsRisk['confidence_adjustment'] ?? $newsRisk['score_penalty'] ?? 0);
if ($newsConfidenceAdjustmentForScore < 0) {
    $displayConfluenceScore = round(max(0, $displayConfluenceScore + ($newsConfidenceAdjustmentForScore / 10)), 1);
    $probabilityScore = max(0, $probabilityScore + $newsConfidenceAdjustmentForScore);
}

$displayConfidenceLabel = $tradePlan['institutional_confidence'] ?? institutional_confidence_label(
    $displayConfluenceScore,
    $probabilityScore,
    $confirmationCount,
    $partialConfirmationCount
);

$readinessSemanticLabel = $tradePlan['institutional_readiness'] ?? institutional_readiness_from_confirmation(
    $displayConfluenceScore,
    $confirmationCount,
    $partialConfirmationCount,
    $confirmationStage,
    $tradePlan['status'] ?? ''
);

$confluence['score'] = $displayConfluenceScore;
$confluence['confidence'] = $displayConfidenceLabel;
$confidenceLabel = $displayConfidenceLabel;
$readinessSemanticLabel = readiness_display_label($readinessSemanticLabel, $confirmationStage, $confirmationCount, $partialConfirmationCount);
$qualityLabel = $readinessSemanticLabel;
$executionModeReadable = execution_mode_label($phase2ExecutionModeDisplay ?? $phase2ExecutionMode ?? 'NORMAL');

$fvgContextText = contextual_fvg_text($bias ?? '', $activeFVG['type'] ?? ($latestFvg['type'] ?? ''));

$baseSlDistanceLabel = $tradePlan['base_sl_distance_label'] ?? ($selectedStyleData['sl_distance_label'] ?? ($tradePlan['sl_distance_label'] ?? 'N/A'));
$dynamicSlDistanceLabel = $tradePlan['dynamic_sl_distance_label'] ?? ($tradePlan['sl_distance_label'] ?? 'N/A');
$dynamicSlReason = $tradePlan['dynamic_sl_reason'] ?? '';
$actualRiskDistance = isset($tradePlan['entry_price'], $tradePlan['stop_loss'])
    ? abs((float)$tradePlan['entry_price'] - (float)$tradePlan['stop_loss'])
    : null;
$actualRiskDistanceLabel = $actualRiskDistance !== null
    ? TradingStyleService::distanceLabel($symbol['symbol'] ?? '', $actualRiskDistance)
    : $dynamicSlDistanceLabel;
$slWasAdjusted = isset($tradePlan['base_sl_distance'], $tradePlan['dynamic_sl_distance'])
    ? abs((float)$tradePlan['dynamic_sl_distance'] - (float)$tradePlan['base_sl_distance']) > 0.00001
    : false;
$zoneInteractionLabel = canonical_zone_interaction_label(
    $mitigationState ?? [],
    $tradePlan['entry_label'] ?? 'Developing setup'
);

$executionStateLabel = $zoneInteractionLabel;

$directionAlignmentLabel = ($contextBias === $bias && in_array($bias, ['BUY', 'SELL'], true))
    ? 'Aligned'
    : ((in_array($contextBias, ['BUY', 'SELL'], true) && in_array($bias, ['BUY', 'SELL'], true)) ? 'Countertrend / Mixed' : 'Neutral');

$directionAlignmentClass = $directionAlignmentLabel === 'Aligned'
    ? 'bg-emerald-50 text-emerald-700 border-emerald-200'
    : ($directionAlignmentLabel === 'Countertrend / Mixed'
        ? 'bg-amber-50 text-amber-700 border-amber-200'
        : 'bg-slate-50 text-slate-700 border-slate-200');

/*
|--------------------------------------------------------------------------
| Final Institutional Conflict Override
|--------------------------------------------------------------------------
| Prevents the dashboard from misleading the user when HTF and LTF disagree.
| In a conflict, the correct action is WAIT, not BUY or SELL.
|--------------------------------------------------------------------------
*/

$isDirectionalConflict = institutional_direction_conflict($contextBias, $bias);
$institutionalConflictGuidance = institutional_conflict_guidance($contextBias, $bias, $pdLabel);

if ($isDirectionalConflict) {
    $finalAction = 'WAIT';
    $decisionTone = 'caution';

    $tradePlan['direction'] = 'WAIT';
    $tradePlan['display_status'] = 'Directional Conflict';
    $tradePlan['status'] = 'DIRECTIONAL_CONFLICT';
    $tradePlan['execution_mode'] = 'DIRECTIONAL_CONFLICT';
    $tradePlan['execution_guidance'] = $institutionalConflictGuidance;

    $executionStateDisplay = 'Directional Conflict';
    $marketStateLabel = 'Directional Conflict';
    $phase2ExecutionMode = 'DIRECTIONAL_CONFLICT';
    $phase2ExecutionModeDisplay = 'Directional Conflict';

    $finalReason = $institutionalConflictGuidance;
    $finalSubText = 'No institutional execution is valid yet because the higher-timeframe and execution-timeframe directions are not aligned. Wait for structure shift, displacement, and candle confirmation before considering entry.';

    $readinessSemanticLabel = 'Directional Conflict';
    $qualityLabel = 'Directional Conflict';
    $confidenceLabel = 'MEDIUM';
    $confluence['confidence'] = 'MEDIUM';
    $confluence['score'] = min((float)($confluence['score'] ?? 0), 5.5);

    $earlyLocationExecutionClass = 'CONFLICT';
    $locationExecutionClass = 'CONFLICT';

    if ($contextBias === 'SELL' && $bias === 'BUY') {
        $locationQualityLabel = 'No Buy - Premium Conflict';
        $locationQualityScore = min((float)$locationQualityScore, 25);
    } elseif ($contextBias === 'BUY' && $bias === 'SELL') {
        $locationQualityLabel = 'No Sell - Discount Conflict';
        $locationQualityScore = min((float)$locationQualityScore, 25);
    }

    $executionModeReadable = 'Directional Conflict';
}

/*
|--------------------------------------------------------------------------
| Dynamic Buy/Sell Scenario Planner
|--------------------------------------------------------------------------
| Creates two institutional scenarios from actual demand/supply zones.
| Entries are not based on current market price; they come from OB/FVG zones.
|--------------------------------------------------------------------------
*/

$tpMultipliersForScenario = is_array($tradePlan['tp_rr'] ?? null)
    ? $tradePlan['tp_rr']
    : ['tp1' => 1, 'tp2' => 2, 'tp3' => 3];

$baseScenarioSlDistance = (float)($tradePlan['sl_distance'] ?? $tradePlan['dynamic_sl_distance'] ?? $tradePlan['base_sl_distance'] ?? 0);
if ($baseScenarioSlDistance <= 0) {
    $baseScenarioSlDistance = (float)($tradePlan['risk'] ?? 0);
}
if ($baseScenarioSlDistance <= 0) {
    $baseScenarioSlDistance = class_exists('TradingStyleService')
        ? TradingStyleService::getStopDistance((string)($symbol['symbol'] ?? ''), $selectedStyleData)
        : (dashboard_symbol_pip_size_safe($symbol['symbol'] ?? '') * 30);
}
/*
|--------------------------------------------------------------------------
| Scenario risk distance normalization
|--------------------------------------------------------------------------
| Use the dynamic SL shown in Execution Quality as the actual scenario risk.
| For XAUUSD this converts 3.04 Gold points / 304 pips into 30.40 price units
| before SL and 1R/2R/3R targets are calculated.
|--------------------------------------------------------------------------
*/
$baseScenarioSlDistance = scenario_risk_price_distance($baseScenarioSlDistance, $symbol['symbol'], $currentPrice, $selectedStyleData, $atr);

$buyScenario = institutional_build_side_scenario(
    'BUY',
    $contextBias,
    $bias,
    $currentPrice,
    $orderBlocks,
    $fvgZones,
    $equilibrium,
    $pdLabel,
    $rangePosition,
    $baseScenarioSlDistance,
    $atr,
    $volatility,
    $tpMultipliersForScenario,
    $symbol['symbol'],
    $symbol['decimals'],
    $confirmationStage,
    $confirmationCount,
    $partialConfirmationCount
);

$sellScenario = institutional_build_side_scenario(
    'SELL',
    $contextBias,
    $bias,
    $currentPrice,
    $orderBlocks,
    $fvgZones,
    $equilibrium,
    $pdLabel,
    $rangePosition,
    $baseScenarioSlDistance,
    $atr,
    $volatility,
    $tpMultipliersForScenario,
    $symbol['symbol'],
    $symbol['decimals'],
    $confirmationStage,
    $confirmationCount,
    $partialConfirmationCount
);

$preferredScenario = $sellScenario['score'] >= $buyScenario['score'] ? $sellScenario : $buyScenario;
$secondaryScenario = $preferredScenario['side'] === 'SELL' ? $buyScenario : $sellScenario;

if ($preferredScenario['score'] < 55 || $confirmationStage !== 'EXECUTION_CONFIRMED') {
    $displayAction = 'WAIT';
    $finalAction = 'WAIT';
}

$scenarioSummary = 'Preferred scenario: ' . $preferredScenario['side'] . ' - ' . $preferredScenario['status'] . '. Alternative scenario: ' . $secondaryScenario['side'] . ' - ' . $secondaryScenario['status'] . '.';

// Re-check confirmation using the preferred valid scenario side. This keeps the confirmation layer aligned to the actual scenario being planned.
$preferredDirectionForConfirmation = $preferredScenario['side'] ?? ($tradePlan['direction'] ?? $bias ?? 'WAIT');
$fallbackLiquiditySweepState = institutional_detect_liquidity_sweep_fallback($candles, $preferredDirectionForConfirmation, $liquidity);

if (empty($liquiditySweepState['confirmed'])) {
    if (!empty($fallbackLiquiditySweepState['confirmed'])) {
        $liquiditySweepState = $fallbackLiquiditySweepState;
        $confirmationCount = min(4, (int)$confirmationCount + 1);
    } elseif (!empty($fallbackLiquiditySweepState['partial']) && empty($liquiditySweepState['partial'])) {
        $liquiditySweepState = $fallbackLiquiditySweepState;
        $partialConfirmationCount = max(1, (int)$partialConfirmationCount);
    }
}

if ($confirmationCount >= 3) {
    $confirmationStage = 'PARTIAL_CONFIRMATION';
    $confirmationReadiness = 'Partial Confirmation';
} elseif ($confirmationCount >= 1 || $partialConfirmationCount >= 1) {
    $confirmationStage = 'EARLY_CONFIRMATION';
    $confirmationReadiness = 'Developing Setup';
}

$whySetupExists = [];

if (!empty($tradePlan['entry_source']) && ($tradePlan['entry_source'] ?? 'N/A') !== 'N/A') {
    $whySetupExists[] = 'Entry source: ' . $tradePlan['entry_source'];
}

if (!empty($activeOrderBlock)) {
    $whySetupExists[] = 'Order block zone is available';
}

if (!empty($activeFVG)) {
    $whySetupExists[] = 'Fair Value Gap zone is available';
    $whySetupExists[] = contextual_fvg_text($bias ?? '', $activeFVG['type'] ?? '');
}

if (!empty($liquidity['sweep']['narrative'])) {
    $whySetupExists[] = $liquidity['sweep']['narrative'];
}

if (!empty($latestEvent['type']) && !empty($latestEvent['direction'])) {
    $whySetupExists[] = 'Latest structure: ' . $latestEvent['type'] . ' ' . $latestEvent['direction'];
}

if ($directionAlignmentLabel === 'Aligned') {
    $whySetupExists[] = 'Higher-timeframe and execution-timeframe bias are aligned';
} elseif ($directionAlignmentLabel === 'Countertrend / Mixed') {
    $whySetupExists[] = 'HTF and LTF are mixed, so confidence is reduced';
}

if (!empty($locationGuidance)) {
    $whySetupExists[] = $locationGuidance;
}

if ($geoRiskLevel === 'HIGH') {
    $whySetupExists[] = 'Geopolitical risk is elevated, so reduced exposure is recommended';
}

if (!empty($dynamicSlReason)) {
    $whySetupExists[] = $dynamicSlReason;
}

if (!empty($executionStateLabel)) {
    $whySetupExists[] = 'Zone interaction state: ' . $executionStateLabel;
}

if (!empty($confirmationGuidance)) {
    $whySetupExists[] = $confirmationGuidance;
}

if (!empty($probabilityScore)) {
    $whySetupExists[] = 'Estimated setup probability: ' . number_format((float)$probabilityScore, 1) . '%.';
}

if ($confirmationStage !== 'EXECUTION_CONFIRMED') {
    $whySetupExists[] = 'Execution trigger is not fully confirmed yet, so aggressive entry should wait.';
}

if ($hasScheduledEvent) {
    $whySetupExists[] = 'Scheduled news risk is active. The setup remains visible, but use reduced risk, confirmation, and spread/slippage caution.';
}

if ($isDirectionalConflict) {
    $whySetupExists[] = 'No trade should be taken while HTF and LTF conflict.';
    $whySetupExists[] = $institutionalConflictGuidance;
}

$whySetupExists = array_values(array_unique(array_filter($whySetupExists)));

/*
|--------------------------------------------------------------------------
| Move global setup confluence into the scenario cards
|--------------------------------------------------------------------------
| The separate "Why This Setup Exists" panel has been removed from the view.
| To avoid losing accurate analysis context, this merges the same engine-derived
| reasons into the BUY/SELL scenario cards. Side-specific comments are routed
| only to the matching side, while neutral risk/context comments can support
| both scenario narratives.
|--------------------------------------------------------------------------
*/
if (!function_exists('dashboard_reason_applies_to_side')) {
    function dashboard_reason_applies_to_side($reason, $side)
    {
        $reasonUpper = strtoupper((string)$reason);
        $sideUpper = strtoupper((string)$side);
        $opposite = $sideUpper === 'BUY' ? 'SELL' : 'BUY';

        // If a reason explicitly mentions one side, keep it on that side only.
        if (strpos($reasonUpper, $sideUpper) !== false) {
            return true;
        }

        if (strpos($reasonUpper, $opposite) !== false) {
            return false;
        }

        return true;
    }
}

if (!function_exists('dashboard_merge_scenario_reasons')) {
    function dashboard_merge_scenario_reasons($scenario, $side, $setupReasons, $rankedSetups)
    {
        if (!is_array($scenario)) {
            return $scenario;
        }

        $sideUpper = strtoupper((string)$side);
        $existing = [];

        foreach (($scenario['reasons'] ?? []) as $reason) {
            $reason = trim((string)$reason);
            if ($reason !== '') {
                $existing[] = $reason;
            }
        }

        // Add ranked setup context for the same side only.
        if (is_array($rankedSetups)) {
            foreach ($rankedSetups as $ranked) {
                if (!is_array($ranked)) {
                    continue;
                }

                $rankDirection = strtoupper((string)($ranked['direction'] ?? ''));
                if ($rankDirection !== $sideUpper) {
                    continue;
                }

                $source = trim((string)($ranked['source'] ?? ''));
                $type = trim((string)($ranked['type'] ?? ''));
                $score = $ranked['score'] ?? null;

                if ($source !== '' || $type !== '') {
                    $summary = trim(($source !== '' ? $source : 'Ranked zone') . ($type !== '' ? ' / ' . $type : ''));
                    if (is_numeric($score)) {
                        $summary .= ' scored ' . number_format((float)$score, 1) . '/10 before confirmation cap.';
                    }
                    $existing[] = $summary;
                }

                foreach (array_slice(($ranked['reasons'] ?? []), 0, 2) as $rankReason) {
                    $rankReason = trim((string)$rankReason);
                    if ($rankReason !== '') {
                        $existing[] = $rankReason;
                    }
                }
            }
        }

        // Add accurate engine-derived setup context, filtered by side where necessary.
        foreach (($setupReasons ?? []) as $reason) {
            $reason = trim((string)$reason);
            if ($reason !== '' && dashboard_reason_applies_to_side($reason, $sideUpper)) {
                $existing[] = $reason;
            }
        }

        // Keep it readable inside the collapsible scenario drawer.
        $scenario['reasons'] = array_slice(array_values(array_unique(array_filter($existing))), 0, 12);

        return $scenario;
    }
}

$buyScenario = dashboard_merge_scenario_reasons($buyScenario ?? [], 'BUY', $whySetupExists ?? [], $rankedSetups ?? []);
$sellScenario = dashboard_merge_scenario_reasons($sellScenario ?? [], 'SELL', $whySetupExists ?? [], $rankedSetups ?? []);
$preferredScenario = ($sellScenario['score'] ?? 0) >= ($buyScenario['score'] ?? 0) ? $sellScenario : $buyScenario;
$secondaryScenario = ($preferredScenario['side'] ?? '') === 'SELL' ? $buyScenario : $sellScenario;
$scenarioSummary = 'Preferred scenario: ' . ($preferredScenario['side'] ?? 'N/A') . ' - ' . ($preferredScenario['status'] ?? 'Waiting') . '. Alternative scenario: ' . ($secondaryScenario['side'] ?? 'N/A') . ' - ' . ($secondaryScenario['status'] ?? 'Waiting') . '.';

$heroSummaryItems = [
    'HTF: ' . ($contextBias ?: 'N/A') . ' (' . ($contextTimeframe ?: 'HTF') . ')',
    'LTF: ' . ($bias ?: 'N/A') . ' (' . ($timeframe ?: 'TF') . ')' . ($isDirectionalConflict ? ' - not aligned' : ''),
    'Location: ' . ($locationQualityLabel ?: $pdLabel) . ($rangePosition !== null ? ' (' . number_format((float)$rangePosition, 1) . '%)' : ''),
    'Lifecycle: ' . ($setupLifecycleStage ?: 'FORMING'),
    'Risk State: ' . execution_mode_label($phase2ExecutionMode ?: 'NORMAL'),
    'Confirmation: ' . ($confirmationReadiness ?: 'Pending'),
    'Probability: ' . number_format((float)$probabilityScore, 1) . '%',
    'Trigger: ' . ucwords(strtolower(str_replace('_', ' ', (string)$triggerState))),
];

$setupAlertNotice = '';
$setupAlertNoticeType = 'info';

if (isset($_GET['alert'])) {
    $alertState = strtolower((string)$_GET['alert']);

    if (in_array($alertState, ['saved', 'created', 'activated', 'success'], true)) {
        $setupAlertNotice = 'Alert tracking has been activated for this setup.';
        $setupAlertNoticeType = 'success';
    } elseif (in_array($alertState, ['exists', 'already', 'active'], true)) {
        $setupAlertNotice = 'Alert tracking is already active for this setup.';
        $setupAlertNoticeType = 'info';
    } elseif (in_array($alertState, ['error', 'failed'], true)) {
        $setupAlertNotice = 'Alert tracking could not be activated. Please check your alert settings.';
        $setupAlertNoticeType = 'error';
    }
}

$setupAlertKey = md5(
    ($symbol['symbol'] ?? '') . '|' .
    $timeframe . '|' .
    $selectedStyle . '|' .
    ($tradePlan['direction'] ?? '') . '|' .
    ($tradePlan['entry_low'] ?? '') . '|' .
    ($tradePlan['entry_high'] ?? '') . '|' .
    ($tradePlan['stop_loss'] ?? '')
);

$telegramAlertStatus = '';

if (false) {
    $alertKey = md5(
        $symbol['symbol'] . '|' .
        $timeframe . '|' .
        ($tradePlan['direction'] ?? '') . '|' .
        ($tradePlan['entry_low'] ?? '') . '|' .
        ($tradePlan['entry_high'] ?? '') . '|' .
        ($tradePlan['stop_loss'] ?? '') . '|' .
        ($tradePlan['tp1'] ?? '') . '|' .
        ($tradePlan['tp2'] ?? '') . '|' .
        ($tradePlan['tp3'] ?? '')
    );

    if (!isset($_SESSION['sent_trade_alerts'])) {
        $_SESSION['sent_trade_alerts'] = [];
    }

    if (!in_array($alertKey, $_SESSION['sent_trade_alerts'], true)) {
        $telegramMessage =
            "TRADE READY\n" .
            "Pair: " . $symbol['symbol'] . "\n" .
            "Timeframe: " . $timeframe . "\n" .
            "HTF Bias: " . $contextBias . "\n" .
            "Direction: " . ($tradePlan['direction'] ?? 'N/A') . "\n" .
            "Entry Zone: " . ($tradePlan['entry_low'] ?? '-') . " - " . ($tradePlan['entry_high'] ?? '-') . "\n" .
            "SL: " . ($tradePlan['stop_loss'] ?? '-') . "\n" .
            "TP1: " . ($tradePlan['tp1'] ?? '-') . "\n" .
            "TP2: " . ($tradePlan['tp2'] ?? '-') . "\n" .
            "TP3: " . ($tradePlan['tp3'] ?? '-') . "\n";

        try {
            AlertService::sendTelegramToUser($_SESSION['user_id'] ?? 0, $telegramMessage);
            $_SESSION['sent_trade_alerts'][] = $alertKey;
            $telegramAlertStatus = 'Telegram alert activated successfully.';
        } catch (Throwable $e) {
            $telegramAlertStatus = 'Telegram alert failed.';
        }
    }
}

?>
<?php
/*
|--------------------------------------------------------------------------
| ITA Terminal View v3
|--------------------------------------------------------------------------
| Fresh presentation layer only.
| All analysis, scenario, alert, news/risk, lot sizing and XAUUSD SL/TP logic
| above this point remains intact.
|--------------------------------------------------------------------------
*/

if (!function_exists('ita_view_text')) {
    function ita_view_text($value, $fallback = 'N/A') {
        $text = trim((string)($value ?? ''));
        return $text === '' ? $fallback : $text;
    }
}

if (!function_exists('ita_view_bool_class')) {
    function ita_view_bool_class($state) {
        if (is_array($state) && !empty($state['confirmed'])) return 'is-complete';
        if (is_array($state) && !empty($state['partial'])) return 'is-partial';
        return 'is-pending';
    }
}

if (!function_exists('ita_view_risk_class')) {
    function ita_view_risk_class($risk) {
        $risk = strtoupper((string)$risk);
        if (in_array($risk, ['HIGH', 'EXTREME', 'CRITICAL'], true)) return 'risk-high';
        if ($risk === 'MEDIUM') return 'risk-medium';
        return 'risk-low';
    }
}


if (!function_exists('ita_target_compact_distance_label')) {
    function ita_target_compact_distance_label($distance, $symbolCode, $decimals)
    {
        if ($distance === null || $distance === '' || !is_numeric($distance)) {
            return 'N/A';
        }

        $distance = abs((float)$distance);
        $symbolCode = strtoupper(str_replace(['/', '-', '_', ' '], '', trim((string)$symbolCode)));
        $pipSize = function_exists('dashboard_symbol_pip_size_safe') ? dashboard_symbol_pip_size_safe($symbolCode) : 0.0001;
        $assetType = function_exists('dashboard_symbol_asset_type_safe') ? dashboard_symbol_asset_type_safe($symbolCode) : '';
        $units = $pipSize > 0 ? ($distance / $pipSize) : 0;

        if ($assetType === 'CRYPTO' || $assetType === 'INDICES') {
            return number_format($units, 0) . ' points';
        }

        if ($symbolCode === 'XAUUSD' || $symbolCode === 'XAGUSD') {
            return number_format($units, 0) . ' pips';
        }

        return number_format($units, 1) . ' pips';
    }
}


if (!function_exists('ita_view_scenario_target')) {
    function ita_view_scenario_target($target, $entry, $rr, $symbolCode, $decimals) {
        if ($target === null || $entry === null || !is_numeric($target) || !is_numeric($entry)) {
            return null;
        }

        $distance = abs((float)$target - (float)$entry);

        return [
            'rr_label' => 'TP' . (int)$rr . ' (' . (int)$rr . 'R)',
            'target_label' => fp($target, (int)$decimals),
            'distance_label' => ita_target_compact_distance_label($distance, $symbolCode, $decimals),
        ];
    }
}

$dashboardSymbol = (string)($symbol['symbol'] ?? 'SYMBOL');
$dashboardDisplayName = ita_view_text($symbol['display_name'] ?? $symbol['name'] ?? '', '');
$dashboardTitle = trim($dashboardSymbol . ($dashboardDisplayName !== '' ? ' · ' . $dashboardDisplayName : ''));
$dashboardAction = strtoupper((string)($finalAction ?? 'WAIT'));
$dashboardPrice = is_numeric($currentPrice ?? null) ? fp($currentPrice, (int)($symbol['decimals'] ?? 2)) : 'N/A';
$dashboardState = 'state-wait';
if ($dashboardAction === 'BUY') {
    $dashboardState = 'state-buy';
} elseif ($dashboardAction === 'SELL') {
    $dashboardState = 'state-sell';
}

$displayStatus = clean_status_label($tradePlan['display_status'] ?? ($tradePlan['status'] ?? ($readinessSemanticLabel ?? 'Developing Setup')));
$displayScore = $confluence['score'] ?? $displayConfluenceScore ?? 0;
$displayRawScore = isset($rawConfluenceScore) ? number_format((float)$rawConfluenceScore, 1) : ita_view_text($displayScore);
$displayConfidence = clean_status_label($confidenceLabel ?? 'N/A');
$displayLocationLabel = ita_view_text($locationQualityLabel ?? $locationLabel ?? 'N/A');
$displayLocationScore = isset($locationQualityScore) ? number_format((float)$locationQualityScore, 1) . '%' : 'N/A';
$dashboardUpcomingRisk = function_exists('dashboard_upcoming_symbol_news_risk')
    ? dashboard_upcoming_symbol_news_risk($dashboardSymbol, 1440)
    : ['risk' => ($dashboardNewsRiskLevel ?? $scheduledRiskLevel ?? ($newsRisk['risk'] ?? 'LOW'))];

$displayNewsRisk = strtoupper((string)($dashboardUpcomingRisk['risk'] ?? 'LOW'));
$displayGeoRisk = strtoupper((string)($geoDisplayLevel ?? $geoRiskLevel ?? ($geo['risk'] ?? 'LOW')));
$displayRiskEnvironment = strtoupper((string)($riskEnvironment ?? $displayNewsRisk));
$displayExecutionMode = execution_mode_label($dashboardExecutionMode ?? $phase2ExecutionModeDisplay ?? $phase2ExecutionMode ?? 'NORMAL');
$displayMacro = ita_view_text($macroSupportsDisplay ?? $macroSupports ?? 'Neutral');
$displayMacroMessage = ita_view_text($macroMessageDisplay ?? 'No strong macro bias detected.');
$displayGuidance = ita_view_text($dashboardGuidanceMessage ?? (function_exists('dashboard_execution_guidance_message') ? dashboard_execution_guidance_message($displayNewsRisk, $displayGeoRisk, $dashboardExecutionMode ?? 'NORMAL') : 'Use normal confirmation rules before execution.'));
$scenarioDeck = [$sellScenario ?? [], $buyScenario ?? []];

$miniCloses = [];
foreach ((is_array($candles ?? null) ? array_slice($candles, -54) : []) as $miniCandle) {
    if (isset($miniCandle['close_price']) && is_numeric($miniCandle['close_price'])) {
        $miniCloses[] = (float)$miniCandle['close_price'];
    }
}
$sparkPoints = '';
if (count($miniCloses) >= 2) {
    $minClose = min($miniCloses);
    $maxClose = max($miniCloses);
    $rangeClose = max(0.0000001, $maxClose - $minClose);
    $pointCount = count($miniCloses);

    foreach ($miniCloses as $index => $closeValue) {
        $x = $pointCount > 1 ? ($index / ($pointCount - 1)) * 100 : 0;
        $y = 86 - (($closeValue - $minClose) / $rangeClose) * 70;
        $sparkPoints .= number_format($x, 2, '.', '') . ',' . number_format($y, 2, '.', '') . ' ';
    }
}

$confirmationSteps = [
    ['name' => 'Zone Mitigation', 'state' => $mitigationState ?? [], 'fallback' => 'Mitigation pending'],
    ['name' => 'Liquidity Sweep', 'state' => $liquiditySweepState ?? [], 'fallback' => 'Liquidity sweep pending'],
    ['name' => 'Displacement', 'state' => $displacementState ?? [], 'fallback' => 'Displacement pending'],
    ['name' => 'Candle Confirmation', 'state' => $candleConfirmationState ?? [], 'fallback' => 'Candle confirmation pending'],
];

$newsEventsForDisplay = [];
if (!empty($newsEventsProfessional) && is_array($newsEventsProfessional)) {
    $newsEventsForDisplay = $newsEventsProfessional;
} elseif (!empty($newsRisk['events']) && is_array($newsRisk['events'])) {
    $newsEventsForDisplay = $newsRisk['events'];
} elseif (!empty($newsRisk['scheduled_events']) && is_array($newsRisk['scheduled_events'])) {
    $newsEventsForDisplay = $newsRisk['scheduled_events'];
}
$newsEventsForDisplay = array_slice($newsEventsForDisplay, 0, 20);

/*
|--------------------------------------------------------------------------
| Absolute news_events display fallback
|--------------------------------------------------------------------------
| If the risk/advisory layers do not pass scheduled events through to the UI,
| read directly from news_events. This keeps calendar rows visible
| while risk scoring remains separate.
|--------------------------------------------------------------------------
*/
try {
    $directDisplayEvents = dashboard_db_news_events($symbol['symbol'] ?? 'XAUUSD', 1440, 360, false);
    if (is_array($directDisplayEvents) && !empty($directDisplayEvents)) {
        $mergedDisplayEvents = array_merge($newsEventsForDisplay, $directDisplayEvents);
        $seenDisplayEvents = [];
        $cleanDisplayEvents = [];
        foreach ($mergedDisplayEvents as $displayEvent) {
            if (!is_array($displayEvent)) { continue; }
            $eventKey = md5(strtoupper((string)($displayEvent['title'] ?? $displayEvent['event'] ?? '')) . '|' . (string)($displayEvent['timestamp'] ?? $displayEvent['event_timestamp'] ?? $displayEvent['time'] ?? '') . '|' . strtoupper((string)($displayEvent['currency'] ?? '')));
            if (isset($seenDisplayEvents[$eventKey])) { continue; }
            $seenDisplayEvents[$eventKey] = true;
            $cleanDisplayEvents[] = $displayEvent;
        }
        $newsEventsForDisplay = array_slice($cleanDisplayEvents, 0, 20);
    }
} catch (Throwable $e) {
    // Keep the existing display events.
}

$geoEventsForDisplay = [];
if (!empty($geo['events']) && is_array($geo['events'])) {
    $geoEventsForDisplay = array_slice($geo['events'], 0, 8);
}

$newsPenaltyRaw = null;
if (isset($scoreImpact) && is_numeric($scoreImpact)) {
    $newsPenaltyRaw = (float)$scoreImpact;
} elseif (isset($newsRisk['score_penalty']) && is_numeric($newsRisk['score_penalty'])) {
    $newsPenaltyRaw = (float)$newsRisk['score_penalty'];
} elseif (isset($preNewsRisk['score_penalty']) && is_numeric($preNewsRisk['score_penalty'])) {
    $newsPenaltyRaw = (float)$preNewsRisk['score_penalty'];
}

$hasRecordedNewsScore = $newsPenaltyRaw !== null;
$newsScoreLabel = $hasRecordedNewsScore
    ? (($newsPenaltyRaw > 0 ? '-' : '') . rtrim(rtrim(number_format(abs((float)$newsPenaltyRaw), 2, '.', ''), '0'), '.') . ' score impact')
    : 'Not recorded';

$confidenceBeforeNewsDisplay = (isset($confidenceBeforeNews) && is_numeric($confidenceBeforeNews))
    ? (string)round((float)$confidenceBeforeNews, 1)
    : 'N/A';

$confidenceAfterNewsDisplay = (isset($confidenceAfterNews) && is_numeric($confidenceAfterNews))
    ? (string)round((float)$confidenceAfterNews, 1)
    : 'N/A';

$newsConfidenceLabel = ($confidenceBeforeNewsDisplay !== 'N/A' && $confidenceAfterNewsDisplay !== 'N/A')
    ? (string)round(((float)$confidenceAfterNewsDisplay - (float)$confidenceBeforeNewsDisplay), 1)
    : 'Not recorded';

$newsRiskSource = ita_view_text($newsRisk['source'] ?? $preNewsRisk['source'] ?? 'Calendar / risk engine');
$newsRiskMessage = ita_view_text($newsRisk['message'] ?? $preNewsRisk['message'] ?? 'No major scheduled-news risk currently affecting this symbol.');
$scheduledRiskDisplay = strtoupper((string)($newsRisk['scheduled_risk'] ?? $scheduledRiskLevel ?? $displayNewsRisk));
$primaryNewsEvent = !empty($newsEventsProfessional[0]) ? $newsEventsProfessional[0] : ($newsEventsForDisplay[0] ?? null);


if (!function_exists('dashboard_forex_factory_impact')) {
    function dashboard_forex_factory_impact($event)
    {
        if (!is_array($event)) {
            return 'LOW';
        }

        // Production path: use the stored DB impact_level first.
        foreach (['impact_level', 'impact'] as $key) {
            if (!empty($event[$key])) {
                $value = strtoupper(trim((string)$event[$key]));
                if (in_array($value, ['LOW', 'MEDIUM', 'HIGH'], true)) {
                    return $value;
                }
            }
        }

        // Then use stored label/raw/source metadata if present.
        $combined = strtolower(trim(implode(' ', array_filter([
            $event['impact_label'] ?? '',
            $event['impact_raw'] ?? '',
            $event['folder_class'] ?? '',
            $event['impact_source'] ?? '',
        ]))));

        if (dashboard_text_contains_safe($combined, 'low impact') || dashboard_text_contains_safe($combined, 'yellow')) {
            return 'LOW';
        }

        if (
            dashboard_text_contains_safe($combined, 'medium impact') ||
            dashboard_text_contains_safe($combined, 'orange') ||
            dashboard_text_contains_safe($combined, 'semi-red') ||
            dashboard_text_contains_safe($combined, 'semired')
        ) {
            return 'MEDIUM';
        }

        if (dashboard_text_contains_safe($combined, 'high impact') || dashboard_text_contains_safe($combined, 'red')) {
            return 'HIGH';
        }

        return 'LOW';
    }
}

if (!function_exists('dashboard_forex_factory_impact_label')) {
    function dashboard_forex_factory_impact_label($impact)
    {
        $impact = strtoupper((string)$impact);

        if ($impact === 'HIGH') {
            return 'High Impact Expected';
        }

        if ($impact === 'MEDIUM') {
            return 'Medium Impact Expected';
        }

        return 'Low Impact Expected';
    }
}


if (!function_exists('ita_target_pips_label')) {
    function ita_target_pips_label($distance, $symbolCode, $decimals)
    {
        if ($distance === null || $distance === '' || !is_numeric($distance)) {
            return 'N/A';
        }

        return trade_distance_label(abs((float)$distance), $symbolCode, $decimals);
    }
}

?>
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><?= e($dashboardSymbol) ?> Institutional Analysis</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <style>
        :root {
            --bg: #eef3f8;
            --surface: #ffffff;
            --surface-2: #f7fafc;
            --surface-3: #eef4ff;
            --text: #0f172a;
            --muted: #64748b;
            --soft: #94a3b8;
            --line: #d8e2ee;
            --line-2: #c6d3e1;
            --blue: #2563eb;
            --green: #059669;
            --red: #dc2626;
            --amber: #d97706;
            --purple: #7c3aed;
            --shadow: 0 18px 48px rgba(15, 23, 42, .08);
            --shadow-soft: 0 10px 24px rgba(15, 23, 42, .06);
        }

        html[data-theme="dark"] {
            --bg: #07111f;
            --surface: #0f172a;
            --surface-2: #111c2f;
            --surface-3: #14243d;
            --text: #f8fafc;
            --muted: #b8c2d0;
            --soft: #8998ae;
            --line: rgba(148, 163, 184, .20);
            --line-2: rgba(148, 163, 184, .30);
            --blue: #60a5fa;
            --green: #34d399;
            --red: #fb7185;
            --amber: #fbbf24;
            --purple: #a78bfa;
            --shadow: 0 22px 65px rgba(0, 0, 0, .30);
            --shadow-soft: 0 12px 28px rgba(0, 0, 0, .22);
        }

        * { box-sizing: border-box; }
        html { background: var(--bg); }
        body {
            margin: 0;
            min-height: 100vh;
            color: var(--text);
            background:
                radial-gradient(circle at 8% -8%, rgba(37, 99, 235, .14), transparent 34rem),
                radial-gradient(circle at 92% 0%, rgba(16, 185, 129, .10), transparent 38rem),
                linear-gradient(180deg, color-mix(in srgb, var(--bg) 88%, white), var(--bg));
            font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
            letter-spacing: -.01em;
        }
        a { color: inherit; text-decoration: none; }

        .shell {
            width: min(1580px, calc(100% - 32px));
            margin: 0 auto;
        }

        .topbar {
            position: static;
            top: auto;
            z-index: auto;
            background: color-mix(in srgb, var(--surface) 92%, transparent);
            backdrop-filter: blur(18px);
            border-bottom: 1px solid var(--line);
        }
        .topbar-inner {
            min-height: 72px;
            display: flex;
            align-items: center;
            justify-content: space-between;
            gap: 18px;
        }
        .brand {
            display: flex;
            align-items: center;
            gap: 12px;
            min-width: 0;
        }
        .brand-mark {
            width: 42px;
            height: 42px;
            border: 1px solid var(--line-2);
            border-radius: 14px;
            display: grid;
            place-items: center;
            background: linear-gradient(135deg, color-mix(in srgb, var(--blue) 22%, transparent), color-mix(in srgb, var(--green) 14%, transparent));
            font-weight: 950;
            letter-spacing: .08em;
            flex: 0 0 auto;
        }
        .eyebrow {
            color: var(--soft);
            font-size: 10px;
            line-height: 1;
            font-weight: 950;
            letter-spacing: .18em;
            text-transform: uppercase;
        }
        .brand-title {
            margin: 5px 0 0;
            font-size: clamp(18px, 2vw, 25px);
            line-height: 1.08;
            letter-spacing: -.04em;
            font-weight: 950;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        .nav {
            display: flex;
            align-items: center;
            justify-content: flex-end;
            gap: 8px;
            flex-wrap: wrap;
        }
        .nav a,
        .nav button {
            min-height: 36px;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            padding: 0 13px;
            border-radius: 11px;
            border: 1px solid var(--line);
            background: var(--surface-2);
            color: var(--text);
            font-size: 12px;
            font-weight: 900;
            box-shadow: var(--shadow-soft);
            cursor: pointer;
        }
        .nav .primary { background: linear-gradient(135deg, #2563eb, #1d4ed8); color: #fff; border: 0; }
        .nav .danger { background: linear-gradient(135deg, #dc2626, #b91c1c); color: #fff; border: 0; }

        .page { padding: 18px 0 46px; display: grid; gap: 18px; }
        .grid-main {
            display: grid;
            grid-template-columns: minmax(0, 1.42fr) minmax(360px, .72fr);
            gap: 18px;
            align-items: stretch;
        }
        .hero {
            position: relative;
            overflow: hidden;
            color: #f8fafc;
            border: 1px solid rgba(96, 165, 250, .24);
            border-radius: 28px;
            background:
                radial-gradient(circle at 84% 10%, rgba(37,99,235,.28), transparent 28rem),
                radial-gradient(circle at 9% 2%, rgba(16,185,129,.14), transparent 24rem),
                linear-gradient(135deg, #071126 0%, #0b1730 56%, #07111f 100%);
            box-shadow: 0 30px 85px rgba(2, 6, 23, .34);
        }
        .hero:before {
            content: "";
            position: absolute;
            inset: 0;
            background-image:
                linear-gradient(rgba(148,163,184,.075) 1px, transparent 1px),
                linear-gradient(90deg, rgba(148,163,184,.075) 1px, transparent 1px);
            background-size: 46px 46px;
            mask-image: linear-gradient(to bottom, rgba(0,0,0,.75), transparent 82%);
        }
        .hero-inner {
            position: relative;
            z-index: 1;
            padding: 26px;
            display: grid;
            grid-template-columns: minmax(0, 1fr) minmax(330px, .56fr);
            gap: 22px;
            min-height: 420px;
        }
        .status-pill {
            display: inline-flex;
            align-items: center;
            gap: 8px;
            min-height: 30px;
            padding: 0 12px;
            border-radius: 999px;
            border: 1px solid rgba(255,255,255,.16);
            background: rgba(255,255,255,.08);
            color: rgba(255,255,255,.88);
            font-size: 12px;
            font-weight: 950;
        }
        .decision {
            margin-top: 20px;
            font-size: clamp(58px, 7.3vw, 104px);
            line-height: .84;
            letter-spacing: -.085em;
            font-weight: 950;
        }
        .state-wait .decision, .state-wait .accent { color: #fbbf24; }
        .state-buy .decision, .state-buy .accent { color: #34d399; }
        .state-sell .decision, .state-sell .accent { color: #fb7185; }

        .reason {
            margin-top: 18px;
            max-width: 780px;
            color: rgba(255,255,255,.80);
            font-size: 14px;
            line-height: 1.68;
        }
        .pill-row {
            display: flex;
            flex-wrap: wrap;
            gap: 8px;
            margin-top: 16px;
        }
        .pill-row span {
            min-height: 26px;
            display: inline-flex;
            align-items: center;
            padding: 0 10px;
            border-radius: 999px;
            border: 1px solid rgba(255,255,255,.14);
            background: rgba(255,255,255,.075);
            color: rgba(255,255,255,.82);
            font-size: 11px;
            font-weight: 850;
        }
        .price-card {
            border: 1px solid rgba(255,255,255,.14);
            background: rgba(255,255,255,.08);
            border-radius: 22px;
            padding: 18px;
            align-self: stretch;
        }
        .price {
            margin-top: 8px;
            font-size: clamp(40px, 5vw, 66px);
            line-height: .95;
            letter-spacing: -.06em;
            font-weight: 950;
        }
        .spark {
            height: 134px;
            margin-top: 16px;
            border-radius: 16px;
            border: 1px solid rgba(255,255,255,.10);
            background: rgba(0,0,0,.18);
            overflow: hidden;
        }
        .spark svg { width: 100%; height: 100%; display: block; }
        .hero-metrics {
            grid-column: 1 / -1;
            display: grid;
            grid-template-columns: repeat(4, minmax(0, 1fr));
            gap: 10px;
            margin-top: 2px;
        }
        .hero-metric {
            min-height: 88px;
            border: 1px solid rgba(255,255,255,.13);
            background: rgba(255,255,255,.075);
            border-radius: 17px;
            padding: 14px;
        }

        .label {
            color: var(--soft);
            font-size: 10px;
            text-transform: uppercase;
            letter-spacing: .14em;
            font-weight: 950;
        }
        .hero .label { color: rgba(255,255,255,.52); }
        .value {
            margin-top: 8px;
            font-size: 16px;
            line-height: 1.22;
            font-weight: 950;
        }
        .note {
            margin-top: 5px;
            color: var(--muted);
            font-size: 12px;
            line-height: 1.45;
            font-weight: 700;
        }
        .hero .note { color: rgba(255,255,255,.58); }

        .panel {
            border: 1px solid var(--line);
            background: color-mix(in srgb, var(--surface) 96%, transparent);
            border-radius: 22px;
            box-shadow: var(--shadow);
            overflow: hidden;
        }
        .panel-pad { padding: 18px; }
        .panel-head {
            display: flex;
            align-items: flex-start;
            justify-content: space-between;
            gap: 12px;
            margin-bottom: 14px;
        }
        .panel-head h2,
        .panel-head h3 {
            margin: 0;
            font-size: 18px;
            line-height: 1.15;
            letter-spacing: -.035em;
            font-weight: 950;
        }
        .panel-head p {
            margin: 4px 0 0;
            color: var(--muted);
            font-size: 12px;
            line-height: 1.45;
            font-weight: 700;
        }
        .badge {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            min-height: 28px;
            padding: 0 10px;
            border-radius: 999px;
            border: 1px solid var(--line);
            background: var(--surface-2);
            color: var(--text);
            font-size: 11px;
            font-weight: 950;
            white-space: nowrap;
        }
        .side-stack { display: grid; gap: 18px; }
        .metrics {
            display: grid;
            grid-template-columns: repeat(2, minmax(0,1fr));
            gap: 10px;
        }
        .metric {
            border: 1px solid var(--line);
            background: var(--surface-2);
            border-radius: 16px;
            padding: 13px;
            min-height: 82px;
        }
        .progress {
            height: 9px;
            border-radius: 999px;
            background: color-mix(in srgb, var(--muted) 20%, transparent);
            overflow: hidden;
        }
        .progress > div {
            height: 100%;
            border-radius: inherit;
            background: linear-gradient(90deg, var(--blue), var(--green));
        }

        .workspace {
            display: grid;
            grid-template-columns: minmax(260px,.72fr) minmax(0,1.08fr) minmax(340px,.9fr);
            gap: 18px;
            align-items: start;
        }
        .form-stack { display: grid; gap: 10px; }
        select, input, textarea {
            width: 100%;
            min-height: 42px;
            border: 1px solid var(--line);
            border-radius: 12px;
            background: var(--surface-2);
            color: var(--text);
            padding: 0 12px;
            font-size: 13px;
            font-weight: 800;
            outline: none;
        }
        input[type="checkbox"] {
            width: 16px;
            height: 16px;
            min-height: 16px;
            padding: 0;
            accent-color: #2563eb;
        }
        .btn {
            min-height: 42px;
            border: 0;
            border-radius: 12px;
            background: linear-gradient(135deg, #2563eb, #1d4ed8);
            color: white;
            font-size: 13px;
            font-weight: 950;
            cursor: pointer;
            box-shadow: 0 14px 28px rgba(37,99,235,.22);
        }
        details.clean-details { margin-top: 12px; }
        details.clean-details summary {
            cursor: pointer;
            color: var(--muted);
            font-size: 13px;
            font-weight: 900;
        }

        .section-title {
            display: flex;
            align-items: flex-end;
            justify-content: space-between;
            gap: 16px;
        }
        .section-title h2 {
            margin: 5px 0 0;
            font-size: 23px;
            line-height: 1.08;
            font-weight: 950;
            letter-spacing: -.045em;
        }
        .section-title p {
            margin: 4px 0 0;
            color: var(--muted);
            font-size: 13px;
            line-height: 1.5;
            font-weight: 700;
        }

        .scenario-grid {
            display: grid;
            grid-template-columns: repeat(2, minmax(0,1fr));
            gap: 18px;
        }
        .scenario {
            position: relative;
            overflow: hidden;
            border: 1px solid var(--line);
            background: var(--surface);
            border-radius: 26px;
            padding: 20px;
            box-shadow: var(--shadow);
        }
        .scenario:before {
            content: "";
            position: absolute;
            left: 0;
            top: 18px;
            bottom: 18px;
            width: 4px;
            border-radius: 999px;
            background: var(--blue);
        }
        .scenario.buy:before { background: var(--green); }
        .scenario.sell:before { background: var(--red); }
        .scenario.primary { border-color: color-mix(in srgb, var(--blue) 42%, var(--line)); }
        .scenario-head {
            display: flex;
            align-items: flex-start;
            justify-content: space-between;
            gap: 14px;
        }
        .side-chip {
            display: inline-flex;
            align-items: center;
            justify-content: center;
            min-height: 26px;
            padding: 0 10px;
            border-radius: 999px;
            color: white;
            font-size: 11px;
            font-weight: 950;
            letter-spacing: .10em;
        }
        .side-chip.buy { background: var(--green); }
        .side-chip.sell { background: var(--red); }
        .scenario h3 {
            margin: 10px 0 0;
            font-size: 28px;
            line-height: 1;
            font-weight: 950;
            letter-spacing: -.055em;
        }
        .scenario-status {
            margin-top: 7px;
            color: var(--muted);
            font-size: 13px;
            font-weight: 900;
        }
        .instruction {
            margin-top: 14px;
            border: 1px solid var(--line);
            background: var(--surface-2);
            border-radius: 16px;
            padding: 13px;
            font-size: 13px;
            line-height: 1.6;
            font-weight: 750;
        }
        .scenario-matrix {
            display: grid;
            grid-template-columns: repeat(2, minmax(0,1fr));
            gap: 10px;
            margin-top: 12px;
        }
        .cell {
            border: 1px solid var(--line);
            background: var(--surface-2);
            border-radius: 16px;
            padding: 14px;
            min-height: 118px;
        }
        .cell-value {
            margin-top: 8px;
            font-size: 20px;
            line-height: 1.16;
            font-weight: 950;
            letter-spacing: -.03em;
        }
        .targets { display: grid; gap: 8px; margin-top: 10px; }
        .target {
            display: grid;
            grid-template-columns: 66px minmax(0,1fr) auto;
            align-items: center;
            gap: 10px;
            border: 1px solid var(--line);
            background: var(--surface);
            border-radius: 12px;
            padding: 10px;
        }
        .target-rr { color: var(--soft); font-size: 10px; font-weight: 950; letter-spacing: .10em; text-transform: uppercase; }
        .target-price { font-size: 17px; font-weight: 950; }
        .target-distance { text-align: right; color: var(--muted); font-size: 11px; font-weight: 850; }
        .reason-grid {
            display: grid;
            grid-template-columns: repeat(2, minmax(0,1fr));
            gap: 8px;
            margin-top: 10px;
        }
        .reason-pill {
            border: 1px solid var(--line);
            background: var(--surface-2);
            border-radius: 12px;
            padding: 9px 10px;
            font-size: 12px;
            line-height: 1.35;
            font-weight: 800;
        }

        .confirmation-grid {
            display: grid;
            grid-template-columns: repeat(5, minmax(0,1fr));
            gap: 10px;
        }
        .step {
            border: 1px solid var(--line);
            background: var(--surface-2);
            border-radius: 16px;
            padding: 14px;
            min-height: 124px;
        }
        .step-num {
            width: 28px;
            height: 28px;
            display: grid;
            place-items: center;
            border-radius: 999px;
            border: 1px solid var(--line);
            background: var(--surface);
            font-size: 12px;
            font-weight: 950;
        }
        .step.is-complete .step-num { background: var(--green); color: white; border-color: var(--green); }
        .step.is-partial .step-num { background: var(--amber); color: #111827; border-color: var(--amber); }
        .step-title { margin-top: 12px; font-size: 13px; font-weight: 950; }
        .step-note { margin-top: 5px; color: var(--muted); font-size: 11px; line-height: 1.45; font-weight: 700; }

        .intel-grid {
            display: grid;
            grid-template-columns: repeat(12, minmax(0,1fr));
            gap: 18px;
        }
        .span-3 { grid-column: span 3; }
        .span-4 { grid-column: span 4; }
        .span-5 { grid-column: span 5; }
        .span-6 { grid-column: span 6; }
        .span-7 { grid-column: span 7; }
        .span-8 { grid-column: span 8; }
        .span-12 { grid-column: span 12; }
        .list { display: grid; gap: 0; }
        .row {
            display: flex;
            justify-content: space-between;
            align-items: flex-start;
            gap: 14px;
            border-bottom: 1px solid var(--line);
            padding: 9px 0;
        }
        .row:last-child { border-bottom: 0; }
        .key { color: var(--muted); font-size: 12px; font-weight: 850; }
        .val { text-align: right; font-size: 12px; font-weight: 950; max-width: 66%; }
        .notice {
            border: 1px solid var(--line);
            border-radius: 15px;
            background: var(--surface-2);
            padding: 13px;
            color: var(--muted);
            font-size: 13px;
            line-height: 1.5;
            font-weight: 750;
        }
        .news-grid {
            display: grid;
            grid-template-columns: repeat(2, minmax(0,1fr));
            gap: 10px;
            margin-top: 12px;
        }
        .news {
            border: 1px solid var(--line);
            background: var(--surface-2);
            border-radius: 14px;
            padding: 12px;
        }
        .news-title { font-size: 13px; line-height: 1.35; font-weight: 950; }
        .news-meta { margin-top: 5px; color: var(--muted); font-size: 11px; font-weight: 750; }
        .lot-result {
            margin-top: 12px;
            border: 1px solid var(--line);
            background: var(--surface-2);
            border-radius: 15px;
            padding: 13px;
            font-weight: 850;
        }
        .risk-high { color: var(--red); }
        .risk-medium { color: var(--amber); }
        .risk-low { color: var(--green); }
        .mt-3 { margin-top: 12px; }

        @media (max-width: 1240px) {
            .grid-main, .hero-inner, .workspace, .scenario-grid { grid-template-columns: 1fr; }
            .confirmation-grid { grid-template-columns: repeat(2, minmax(0,1fr)); }
            .span-3, .span-4, .span-5, .span-6, .span-7, .span-8 { grid-column: span 12; }
        }
        @media (max-width: 760px) {
            .shell { width: min(100% - 20px, 1580px); }
            .topbar-inner { align-items: flex-start; flex-direction: column; padding: 12px 0; }
            .nav { justify-content: flex-start; }
            .hero-inner { padding: 18px; }
            .hero-metrics, .metrics, .scenario-matrix, .reason-grid, .news-grid { grid-template-columns: 1fr; }
            .confirmation-grid { grid-template-columns: 1fr; }
            .target { grid-template-columns: 1fr; }
            .target-distance, .val { text-align: left; max-width: none; }
            .panel-pad, .scenario { padding: 15px; }
        }
    </style>

<style id="ita-hero-section-pro-redo">
/* Professional hero/header correction - analysis dashboard only */
.topbar {
    position: static !important;
    top: auto !important;
    z-index: auto !important;
}
.topbar-inner {
    min-height: 58px !important;
}
.brand-title {
    font-size: clamp(18px, 1.85vw, 24px) !important;
    line-height: 1.08 !important;
    letter-spacing: -0.035em !important;
}
.hero {
    height: auto !important;
    min-height: 0 !important;
    align-self: start !important;
    border-radius: 22px !important;
}
.hero-inner {
    min-height: 0 !important;
    height: auto !important;
    display: grid !important;
    grid-template-columns: minmax(0, 1fr) 240px !important;
    grid-auto-rows: auto !important;
    align-items: start !important;
    gap: 14px !important;
    padding: 18px !important;
}
.hero-inner > div:first-child {
    align-self: start !important;
}
.status-pill {
    min-height: 24px !important;
    padding: 0 10px !important;
    font-size: 10.5px !important;
    font-weight: 800 !important;
}
.decision {
    font-size: clamp(34px, 3.75vw, 52px) !important;
    line-height: 0.95 !important;
    margin-top: 10px !important;
    letter-spacing: -0.055em !important;
}
.reason {
    max-width: 620px !important;
    margin-top: 9px !important;
    font-size: 12.5px !important;
    line-height: 1.52 !important;
}
.hero-narrative {
    width: fit-content !important;
    max-width: 100% !important;
    margin-top: 12px !important;
}
.hero-narrative[open] {
    width: 100% !important;
}
.hero-narrative[open] + .price-card-clean,
.price-card-clean {
    align-self: start !important;
    justify-self: stretch !important;
    min-height: 0 !important;
    height: auto !important;
    max-height: 120px !important;
    padding: 12px 14px !important;
    border-radius: 14px !important;
}
.price-card-clean .label {
    font-size: 9px !important;
    line-height: 1 !important;
}
.price-card-clean .price {
    font-size: clamp(28px, 2.65vw, 38px) !important;
    line-height: 0.98 !important;
    margin-top: 6px !important;
    letter-spacing: -0.04em !important;
}
.price-card-clean .note {
    margin-top: 7px !important;
    font-size: 10.5px !important;
    line-height: 1.25 !important;
}
.spark {
    display: none !important;
}
.hero-metrics {
    grid-column: 1 / -1 !important;
    display: grid !important;
    grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
    gap: 8px !important;
    margin-top: 0 !important;
}
.hero-metric {
    min-height: 52px !important;
    height: auto !important;
    padding: 9px 10px !important;
    border-radius: 13px !important;
}
.hero-metric .label {
    font-size: 9px !important;
    line-height: 1 !important;
}
.hero-metric .value {
    margin-top: 6px !important;
    font-size: 13px !important;
    line-height: 1.25 !important;
    font-weight: 850 !important;
}
.hero-metric .note {
    display: none !important;
}
.pill-row:not(.compact-hidden-tags) {
    display: none !important;
}
.hero-narrative .pill-row.compact-hidden-tags {
    display: flex !important;
    flex-wrap: wrap !important;
    margin-top: 10px !important;
}
@media (max-width: 1240px) {
    .hero-inner {
        grid-template-columns: 1fr !important;
    }
    .price-card-clean {
        width: min(100%, 300px) !important;
        max-height: none !important;
    }
}
@media (max-width: 760px) {
    .hero-inner {
        padding: 14px !important;
    }
    .hero-metrics {
        grid-template-columns: 1fr !important;
    }
    .price-card-clean {
        width: 100% !important;
    }
}
</style>



<style id="ita-top-layout-v11-final">
/*
|--------------------------------------------------------------------------
| Correct top layout
|--------------------------------------------------------------------------
| Desktop/tablet: left column = Hero + Analyze/Execution.
| Right column = Market Intelligence + Setup Lifecycle.
| Track Setup = clean full-width horizontal row underneath both columns.
|--------------------------------------------------------------------------
*/
.top-command-layout {
    display: grid !important;
    grid-template-columns: minmax(0, 1.58fr) minmax(320px, 0.68fr) !important;
    gap: 14px !important;
    align-items: start !important;
}
.top-left-stack {
    display: grid !important;
    gap: 14px !important;
    align-items: start !important;
}
.under-hero-grid {
    display: grid !important;
    grid-template-columns: minmax(230px, 0.72fr) minmax(0, 1.28fr) !important;
    gap: 14px !important;
    align-items: start !important;
}
.top-command-layout .side-stack {
    display: grid !important;
    grid-template-columns: 1fr !important;
    gap: 14px !important;
    align-items: start !important;
}
.top-command-layout .side-stack > .panel {
    width: 100% !important;
    margin: 0 !important;
}
.analyze-panel,
.execution-panel {
    margin: 0 !important;
    min-height: 0 !important;
}
.analyze-panel .form-stack {
    gap: 8px !important;
}
.execution-panel .metrics {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 9px !important;
}
.execution-panel .metric {
    min-height: 72px !important;
}
.track-row-layout {
    margin: 0 !important;
    display: block !important;
}
.track-panel {
    margin: 0 !important;
    width: 100% !important;
}
.track-panel .panel-head {
    align-items: center !important;
    margin-bottom: 12px !important;
}

/* Keep Track Setup horizontal, but do not make it look like a huge scattered form. */
.track-form {
    display: grid !important;
    grid-template-columns:
        minmax(230px, 1.1fr)
        minmax(130px, .55fr)
        minmax(250px, 1.2fr)
        minmax(260px, 1.2fr)
        minmax(210px, .95fr) !important;
    gap: 10px !important;
    align-items: stretch !important;
}
.track-form > input[type="hidden"] {
    display: none !important;
}
.track-box {
    border: 1px solid var(--line) !important;
    background: var(--surface-2) !important;
    border-radius: 13px !important;
    padding: 10px !important;
    min-height: 100% !important;
}
.track-box .label {
    margin-bottom: 8px !important;
}
.track-box label {
    display: flex !important;
    align-items: center !important;
    gap: 7px !important;
    min-height: 24px !important;
    margin: 0 0 6px 0 !important;
    color: var(--text) !important;
    font-size: 11.5px !important;
    line-height: 1.25 !important;
    font-weight: 750 !important;
}
.track-box label:last-child {
    margin-bottom: 0 !important;
}
.track-box input[type="checkbox"] {
    width: 15px !important;
    height: 15px !important;
    flex: 0 0 auto !important;
}
.track-box select,
.track-box input[type="number"] {
    min-height: 36px !important;
    font-size: 12px !important;
}
.track-mini-inputs {
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 8px !important;
    margin-top: 8px !important;
}
.stage-grid {
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 4px 8px !important;
}
.track-expiry {
    display: grid !important;
    align-content: start !important;
    gap: 8px !important;
}
.track-expiry .btn {
    width: 100% !important;
    margin-top: 2px !important;
}
.track-panel .lot-result {
    margin-top: 10px !important;
}

/*
 Do not collapse at 1024/1280px. The screenshot viewport is wide enough for
 the requested two-column top area, so collapse only on genuinely small screens.
*/
@media (max-width: 900px) {
    .top-command-layout {
        grid-template-columns: 1fr !important;
    }
    .under-hero-grid {
        grid-template-columns: 1fr !important;
    }
    .track-form {
        grid-template-columns: 1fr !important;
    }
    .stage-grid {
        grid-template-columns: 1fr !important;
    }
}
</style>



<style id="ita-top-layout-v11-safeguard">
@media (min-width: 901px) {
    .top-command-layout {
        display: grid !important;
        grid-template-columns: minmax(0, 1.58fr) minmax(320px, 0.68fr) !important;
        gap: 14px !important;
        align-items: start !important;
    }
    .under-hero-grid {
        display: grid !important;
        grid-template-columns: minmax(230px, 0.72fr) minmax(0, 1.28fr) !important;
        gap: 14px !important;
        align-items: start !important;
    }
    .top-command-layout .side-stack {
        display: grid !important;
        grid-template-columns: 1fr !important;
        gap: 14px !important;
    }
}
</style>


<style id="ita-section-specific-polish-v12">
/* Hero badge: professional readable size */
.hero .status-pill {
    min-height: 30px !important;
    padding: 0 13px !important;
    font-size: 12px !important;
    line-height: 1 !important;
    font-weight: 850 !important;
    letter-spacing: -0.01em !important;
}

/* Compact current price panel */
.hero-inner {
    grid-template-columns: minmax(0, 1fr) 205px !important;
    column-gap: 16px !important;
}
.price-card-clean {
    justify-self: end !important;
    width: 205px !important;
    max-width: 205px !important;
    min-height: 92px !important;
    max-height: 96px !important;
    padding: 14px !important;
    display: flex !important;
    flex-direction: column !important;
    justify-content: center !important;
}
.price-card-clean .label {
    font-size: 9px !important;
    line-height: 1 !important;
}
.price-card-clean .price {
    margin-top: 7px !important;
    font-size: clamp(28px, 2.45vw, 38px) !important;
    line-height: 0.95 !important;
}
.price-card-clean .note {
    display: none !important;
}

/* Clean hero metric typography */
.hero-metric .value {
    font-size: 13px !important;
    line-height: 1.3 !important;
    font-weight: 780 !important;
}
.hero-metric .label {
    font-weight: 850 !important;
}

/* Clean form controls in Analyze and Track */
.analyze-panel select,
.track-panel select,
.track-panel input[type="number"] {
    appearance: none !important;
    -webkit-appearance: none !important;
    background-color: var(--surface) !important;
    border: 1px solid var(--line) !important;
    border-radius: 12px !important;
    min-height: 42px !important;
    padding: 0 38px 0 12px !important;
    font-size: 13px !important;
    font-weight: 650 !important;
    color: var(--text) !important;
    background-image:
        linear-gradient(45deg, transparent 50%, var(--muted) 50%),
        linear-gradient(135deg, var(--muted) 50%, transparent 50%) !important;
    background-position:
        calc(100% - 18px) 50%,
        calc(100% - 13px) 50% !important;
    background-size: 5px 5px, 5px 5px !important;
    background-repeat: no-repeat !important;
}
.analyze-panel .form-stack {
    gap: 9px !important;
}
.analyze-panel .btn {
    margin-top: 2px !important;
    min-height: 42px !important;
}

/* Dynamic SL */
.dynamic-sl-card .value {
    display: flex !important;
    flex-wrap: wrap !important;
    align-items: baseline !important;
    gap: 6px !important;
    line-height: 1.25 !important;
}
.dynamic-sl-card .soft-unit {
    color: var(--muted) !important;
    font-size: 12px !important;
    font-weight: 750 !important;
}

/* More Execution Details */
.execution-detail-grid {
    display: grid !important;
    grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
    gap: 9px !important;
}
.detail-mini {
    border: 1px solid var(--line) !important;
    background: var(--surface-2) !important;
    border-radius: 12px !important;
    padding: 10px !important;
}
.detail-mini span {
    display: block !important;
    color: var(--soft) !important;
    font-size: 9.5px !important;
    text-transform: uppercase !important;
    letter-spacing: .12em !important;
    font-weight: 850 !important;
}
.detail-mini strong {
    display: block !important;
    margin-top: 6px !important;
    color: var(--text) !important;
    font-size: 12.5px !important;
    line-height: 1.25 !important;
    font-weight: 780 !important;
}
.detail-mini small {
    display: block !important;
    margin-top: 4px !important;
    color: var(--muted) !important;
    font-size: 11px !important;
    line-height: 1.35 !important;
    font-weight: 650 !important;
}

/* Track Setup field polish */
.track-box {
    background: color-mix(in srgb, var(--surface-2) 92%, white) !important;
}
.track-scenario select,
.track-expiry select {
    width: 100% !important;
}
.track-mini-inputs input[type="number"] {
    padding-right: 12px !important;
    background-image: none !important;
}
.track-box .label {
    font-size: 9.5px !important;
    font-weight: 850 !important;
    color: var(--soft) !important;
}
.track-box label {
    font-weight: 720 !important;
}

@media (max-width: 760px) {
    .hero-inner {
        grid-template-columns: 1fr !important;
    }
    .price-card-clean {
        width: 100% !important;
        max-width: none !important;
        justify-self: stretch !important;
    }
    .execution-detail-grid {
        grid-template-columns: 1fr !important;
    }
}
</style>


<style id="ita-scenario-confirmation-polish-v13">
/* Requested scenario/confirmation polish */

/* Give Scenario Matrix proper breathing room after Track Setup. */
.scenario-title-block {
    margin-top: 18px !important;
    padding-top: 2px !important;
}

/* Make preferred/alternative summary clear and useful. */
.scenario-summary-line {
    display: flex !important;
    flex-wrap: wrap !important;
    gap: 8px !important;
    margin-top: 7px !important;
}
.scenario-summary-line span {
    display: inline-flex !important;
    align-items: center !important;
    min-height: 28px !important;
    padding: 0 10px !important;
    border-radius: 999px !important;
    border: 1px solid var(--line) !important;
    background: var(--surface) !important;
    color: var(--muted) !important;
    font-size: 12px !important;
    line-height: 1 !important;
    font-weight: 750 !important;
}
.scenario-summary-line .preferred-summary {
    background: color-mix(in srgb, var(--blue) 12%, var(--surface)) !important;
    color: var(--text) !important;
    border-color: color-mix(in srgb, var(--blue) 34%, var(--line)) !important;
    font-weight: 850 !important;
}

/* Make BUY and SELL scenario risk boxes consistent. */
.scenario-matrix {
    align-items: stretch !important;
}
.scenario-matrix .cell {
    min-height: 116px !important;
    display: flex !important;
    flex-direction: column !important;
}
.scenario-matrix .cell .note {
    max-width: 100% !important;
}
.scenario-matrix .cell:nth-child(3),
.scenario-matrix .cell:nth-child(4) {
    min-height: 178px !important;
}

/* Dynamic Targets should be compact and identical in both cards. */
.targets {
    gap: 7px !important;
}
.target {
    display: grid !important;
    grid-template-columns: 62px minmax(0, 1fr) !important;
    align-items: center !important;
    min-height: 42px !important;
    padding: 8px 10px !important;
    border-radius: 11px !important;
}
.target-rr {
    font-size: 10px !important;
    font-weight: 800 !important;
    color: var(--soft) !important;
    white-space: nowrap !important;
}
.target-price {
    font-size: 13px !important;
    line-height: 1.2 !important;
    font-weight: 750 !important;
    white-space: nowrap !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
}
.target-pips {
    font-size: 11px !important;
    color: var(--muted) !important;
    font-weight: 700 !important;
}

/* Collapsible scenario reasons to reduce card height. */
.scenario-reasons {
    border: 1px solid var(--line) !important;
    background: var(--surface-2) !important;
    border-radius: 14px !important;
    padding: 0 !important;
    overflow: hidden !important;
}
.scenario-reasons summary {
    cursor: pointer !important;
    list-style: none !important;
    padding: 11px 12px !important;
    color: var(--soft) !important;
    text-transform: uppercase !important;
    letter-spacing: .12em !important;
    font-size: 10px !important;
    line-height: 1 !important;
    font-weight: 850 !important;
}
.scenario-reasons summary::-webkit-details-marker {
    display: none !important;
}
.scenario-reasons summary::after {
    content: "Show" !important;
    float: right !important;
    color: var(--muted) !important;
    text-transform: none !important;
    letter-spacing: 0 !important;
    font-size: 11px !important;
    font-weight: 750 !important;
}
.scenario-reasons[open] summary::after {
    content: "Hide" !important;
}
.scenario-reasons .reason-grid {
    padding: 0 12px 12px !important;
    margin-top: 0 !important;
}

/* Confirmation Engine should now be four confirmation steps only. */
.confirmation-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr)) !important;
}
.confirmation-grid .step {
    min-height: 112px !important;
}

/* Keep target rows readable on smaller screens. */
@media (max-width: 900px) {
    .confirmation-grid {
        grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    }
}
@media (max-width: 760px) {
    .scenario-summary-line {
        flex-direction: column !important;
        align-items: stretch !important;
    }
    .scenario-summary-line span {
        width: 100% !important;
    }
    .target {
        grid-template-columns: 1fr !important;
    }
    .target-price {
        white-space: normal !important;
    }
    .confirmation-grid {
        grid-template-columns: 1fr !important;
    }
}
</style>


<style id="ita-ranking-and-reason-dedupe-v14">
/* Professional Setup Ranking cards */
.setup-ranking-panel {
    min-height: 100% !important;
}
.setup-ranking-list {
    display: grid !important;
    gap: 10px !important;
}
.setup-rank-card {
    border: 1px solid var(--line) !important;
    background: var(--surface-2) !important;
    border-radius: 14px !important;
    padding: 12px !important;
}
.rank-card-main {
    display: grid !important;
    grid-template-columns: 34px minmax(0, 1fr) !important;
    gap: 10px !important;
    align-items: start !important;
}
.rank-position {
    width: 30px !important;
    height: 30px !important;
    border-radius: 999px !important;
    display: grid !important;
    place-items: center !important;
    border: 1px solid var(--line) !important;
    background: var(--surface) !important;
    color: var(--muted) !important;
    font-size: 11px !important;
    font-weight: 850 !important;
}
.rank-content {
    min-width: 0 !important;
}
.rank-title-row {
    display: flex !important;
    align-items: flex-start !important;
    justify-content: space-between !important;
    gap: 12px !important;
}
.rank-title-row h4 {
    margin: 0 !important;
    color: var(--text) !important;
    font-size: 13.5px !important;
    line-height: 1.25 !important;
    font-weight: 850 !important;
}
.rank-title-row p {
    margin: 4px 0 0 !important;
    color: var(--muted) !important;
    font-size: 11.5px !important;
    line-height: 1.35 !important;
    font-weight: 650 !important;
}
.rank-grade {
    flex: 0 0 auto !important;
    text-align: right !important;
}
.rank-grade span {
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    min-width: 30px !important;
    min-height: 24px !important;
    padding: 0 8px !important;
    border-radius: 999px !important;
    border: 1px solid var(--line) !important;
    background: var(--surface) !important;
    color: var(--text) !important;
    font-size: 12px !important;
    font-weight: 850 !important;
}
.rank-grade strong {
    display: block !important;
    margin-top: 4px !important;
    color: var(--muted) !important;
    font-size: 10.5px !important;
    line-height: 1 !important;
    font-weight: 750 !important;
}
.rank-reason-list {
    margin: 9px 0 0 !important;
    padding: 0 !important;
    list-style: none !important;
    display: grid !important;
    gap: 5px !important;
}
.rank-reason-list li {
    position: relative !important;
    padding-left: 12px !important;
    color: var(--muted) !important;
    font-size: 11.5px !important;
    line-height: 1.38 !important;
    font-weight: 650 !important;
}
.rank-reason-list li::before {
    content: "" !important;
    position: absolute !important;
    left: 0 !important;
    top: .62em !important;
    width: 4px !important;
    height: 4px !important;
    border-radius: 999px !important;
    background: var(--blue) !important;
}

/* Scenario reason drawer now contains the former global setup context, so make it readable and not cramped. */
.scenario-reasons .reason-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 7px !important;
}
.scenario-reasons .reason-pill {
    min-height: auto !important;
    padding: 9px 10px !important;
    font-size: 11.5px !important;
    line-height: 1.35 !important;
    font-weight: 650 !important;
    color: var(--text) !important;
    background: var(--surface) !important;
}

/* Removed the standalone Why This Setup Exists panel; allow Market Structure to use the freed space. */
.intel-grid > .span-7 {
    grid-column: span 7 !important;
}

@media (max-width: 1240px) {
    .intel-grid > .span-5,
    .intel-grid > .span-7 {
        grid-column: span 12 !important;
    }
}
@media (max-width: 760px) {
    .rank-card-main {
        grid-template-columns: 1fr !important;
    }
    .rank-position {
        width: auto !important;
        height: auto !important;
        display: inline-flex !important;
        padding: 5px 8px !important;
    }
    .scenario-reasons .reason-grid {
        grid-template-columns: 1fr !important;
    }
}
</style>


<style id="ita-news-risk-lot-polish-v15">
/* News & Risk Intelligence: clean, non-duplicative, and careful with model-vs-calendar wording */
.news-risk-panel .panel-head {
    align-items: flex-start !important;
}
.news-watch-badge {
    background: color-mix(in srgb, var(--amber) 12%, var(--surface)) !important;
    color: color-mix(in srgb, var(--amber) 70%, var(--text)) !important;
    border-color: color-mix(in srgb, var(--amber) 35%, var(--line)) !important;
}

.news-risk-summary {
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 10px !important;
}
.news-summary-card {
    border: 1px solid var(--line) !important;
    background: var(--surface-2) !important;
    border-radius: 14px !important;
    padding: 12px !important;
    min-height: 96px !important;
}
.news-summary-card span,
.execution-note span {
    display: block !important;
    color: var(--soft) !important;
    font-size: 9.5px !important;
    line-height: 1 !important;
    text-transform: uppercase !important;
    letter-spacing: .12em !important;
    font-weight: 850 !important;
}
.news-summary-card strong {
    display: block !important;
    margin-top: 8px !important;
    color: var(--text) !important;
    font-size: 14px !important;
    line-height: 1.25 !important;
    font-weight: 820 !important;
}
.news-summary-card small {
    display: block !important;
    margin-top: 5px !important;
    color: var(--muted) !important;
    font-size: 11.5px !important;
    line-height: 1.4 !important;
    font-weight: 650 !important;
}

.execution-note {
    display: grid !important;
    grid-template-columns: 150px minmax(0, 1fr) !important;
    gap: 12px !important;
    align-items: start !important;
    margin-top: 10px !important;
    border: 1px solid var(--line) !important;
    background: color-mix(in srgb, var(--blue) 8%, var(--surface)) !important;
    border-radius: 14px !important;
    padding: 12px !important;
}
.execution-note strong {
    display: block !important;
    margin-top: 7px !important;
    color: var(--text) !important;
    font-size: 13px !important;
    font-weight: 820 !important;
}
.execution-note p {
    margin: 0 !important;
    color: var(--muted) !important;
    font-size: 12px !important;
    line-height: 1.45 !important;
    font-weight: 650 !important;
}

.upcoming-events-clean {
    margin-top: 12px !important;
    border: 1px solid var(--line) !important;
    background: var(--surface-2) !important;
    border-radius: 16px !important;
    padding: 12px !important;
}
.upcoming-header.clean {
    display: flex !important;
    justify-content: space-between !important;
    align-items: flex-start !important;
    gap: 12px !important;
    margin-bottom: 10px !important;
}
.upcoming-header.clean h4 {
    margin: 0 !important;
    color: var(--text) !important;
    font-size: 14px !important;
    line-height: 1.2 !important;
    font-weight: 850 !important;
}
.upcoming-header.clean p {
    margin: 4px 0 0 !important;
    color: var(--muted) !important;
    font-size: 11.5px !important;
    line-height: 1.35 !important;
    font-weight: 650 !important;
}
.upcoming-header.clean > span {
    flex: 0 0 auto !important;
    display: inline-flex !important;
    align-items: center !important;
    min-height: 24px !important;
    padding: 0 8px !important;
    border: 1px solid var(--line) !important;
    background: var(--surface) !important;
    border-radius: 999px !important;
    color: var(--muted) !important;
    font-size: 11px !important;
    font-weight: 800 !important;
}

.event-list-clean {
    display: grid !important;
    gap: 8px !important;
}
.event-row-clean {
    display: grid !important;
    grid-template-columns: 68px minmax(0, 1fr) !important;
    gap: 10px !important;
    align-items: start !important;
    border: 1px solid var(--line) !important;
    background: var(--surface) !important;
    border-radius: 13px !important;
    padding: 10px !important;
}
.event-impact-pill {
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    min-height: 24px !important;
    border-radius: 999px !important;
    border: 1px solid var(--line) !important;
    background: var(--surface-2) !important;
    color: var(--muted) !important;
    font-size: 10px !important;
    line-height: 1 !important;
    font-weight: 850 !important;
}
.event-impact-pill.high {
    color: var(--amber) !important;
    background: color-mix(in srgb, var(--amber) 10%, var(--surface)) !important;
    border-color: color-mix(in srgb, var(--amber) 28%, var(--line)) !important;
}
.event-impact-pill.medium {
    color: var(--blue) !important;
    background: color-mix(in srgb, var(--blue) 10%, var(--surface)) !important;
    border-color: color-mix(in srgb, var(--blue) 26%, var(--line)) !important;
}
.event-title-clean {
    color: var(--text) !important;
    font-size: 12.5px !important;
    line-height: 1.35 !important;
    font-weight: 800 !important;
}
.event-meta-clean {
    margin-top: 4px !important;
    color: var(--muted) !important;
    font-size: 11.2px !important;
    line-height: 1.35 !important;
    font-weight: 650 !important;
}
.geo-details-clean {
    margin-top: 10px !important;
}

/* Lot Size Calculator: professional field layout */
#lot-size-calculator {
    align-self: stretch !important;
}
.lot-form {
    display: grid !important;
    gap: 12px !important;
}
.lot-field {
    display: grid !important;
    gap: 7px !important;
    margin: 0 !important;
}
.lot-field span {
    color: var(--soft) !important;
    font-size: 9.5px !important;
    line-height: 1 !important;
    text-transform: uppercase !important;
    letter-spacing: .12em !important;
    font-weight: 850 !important;
}
.lot-field select,
.lot-input-wrap {
    width: 100% !important;
    min-height: 44px !important;
    border: 1px solid var(--line) !important;
    background: var(--surface-2) !important;
    border-radius: 13px !important;
    color: var(--text) !important;
    box-shadow: inset 0 1px 0 rgba(255,255,255,.35) !important;
}
.lot-field select {
    appearance: none !important;
    -webkit-appearance: none !important;
    padding: 0 40px 0 13px !important;
    font-size: 13px !important;
    font-weight: 700 !important;
    background-image:
        linear-gradient(45deg, transparent 50%, var(--muted) 50%),
        linear-gradient(135deg, var(--muted) 50%, transparent 50%) !important;
    background-position:
        calc(100% - 18px) 50%,
        calc(100% - 13px) 50% !important;
    background-size: 5px 5px, 5px 5px !important;
    background-repeat: no-repeat !important;
}
.lot-field-grid {
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 10px !important;
}
.lot-input-wrap {
    display: flex !important;
    align-items: center !important;
    overflow: hidden !important;
}
.lot-input-wrap em {
    flex: 0 0 auto !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    min-width: 38px !important;
    height: 44px !important;
    color: var(--muted) !important;
    font-style: normal !important;
    font-size: 12px !important;
    font-weight: 850 !important;
    border-right: 1px solid var(--line) !important;
}
.lot-input-wrap em:last-child {
    border-right: 0 !important;
    border-left: 1px solid var(--line) !important;
}
.lot-input-wrap input {
    flex: 1 1 auto !important;
    min-width: 0 !important;
    height: 44px !important;
    min-height: 44px !important;
    border: 0 !important;
    background: transparent !important;
    border-radius: 0 !important;
    padding: 0 12px !important;
    color: var(--text) !important;
    font-size: 13px !important;
    font-weight: 750 !important;
    box-shadow: none !important;
}
.lot-btn {
    min-height: 46px !important;
    border-radius: 13px !important;
    font-size: 13px !important;
    font-weight: 850 !important;
}
#lot-size-calculator .lot-result {
    margin-top: 12px !important;
    border-radius: 13px !important;
}

@media (max-width: 900px) {
    .news-risk-summary,
    .lot-field-grid {
        grid-template-columns: 1fr !important;
    }
    .execution-note,
    .event-row-clean {
        grid-template-columns: 1fr !important;
    }
}
</style>


<style id="ita-forex-factory-impact-v16">
.event-impact-pill.low {
    color: var(--muted) !important;
    background: color-mix(in srgb, #eab308 8%, var(--surface)) !important;
    border-color: color-mix(in srgb, #eab308 22%, var(--line)) !important;
}
.event-impact-pill.medium {
    color: var(--amber) !important;
    background: color-mix(in srgb, var(--amber) 10%, var(--surface)) !important;
    border-color: color-mix(in srgb, var(--amber) 28%, var(--line)) !important;
}
.event-impact-pill.high {
    color: var(--red) !important;
    background: color-mix(in srgb, var(--red) 10%, var(--surface)) !important;
    border-color: color-mix(in srgb, var(--red) 28%, var(--line)) !important;
}
.event-row-clean {
    grid-template-columns: 150px minmax(0, 1fr) !important;
}
@media (max-width: 900px) {
    .event-row-clean {
        grid-template-columns: 1fr !important;
    }
}
</style>


<style id="ita-news-risk-pro-v19">
.news-risk-pro .news-risk-head {
    align-items: flex-start !important;
}
.news-risk-pro .header-impact {
    white-space: nowrap !important;
    min-height: 30px !important;
    padding: 0 12px !important;
}
.event-impact-pill.none {
    color: var(--muted) !important;
    background: var(--surface) !important;
    border-color: var(--line) !important;
}
.news-risk-summary.pro {
    display: grid !important;
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
    gap: 10px !important;
}
.news-risk-summary.pro .news-summary-card {
    min-height: 104px !important;
}
.news-risk-summary.pro .news-summary-card strong {
    font-size: 13.5px !important;
    line-height: 1.25 !important;
}
.upcoming-events-clean.pro {
    margin-top: 12px !important;
}
.event-row-clean.pro {
    grid-template-columns: 150px minmax(0, 1fr) !important;
    align-items: center !important;
}
.event-row-clean.pro .event-impact-pill {
    min-height: 28px !important;
    text-align: center !important;
}
.event-title-clean {
    font-size: 13px !important;
}
.event-meta-clean {
    display: flex !important;
    flex-wrap: wrap !important;
    gap: 4px !important;
    align-items: center !important;
}
.event-countdown {
    color: var(--blue) !important;
    font-weight: 800 !important;
}
.event-data-clean {
    margin-top: 5px !important;
    color: var(--muted) !important;
    font-size: 11.2px !important;
    line-height: 1.35 !important;
    font-weight: 650 !important;
}
.extra-events-clean {
    margin-top: 10px !important;
    border: 1px dashed var(--line) !important;
    background: color-mix(in srgb, var(--surface-2) 75%, transparent) !important;
    border-radius: 14px !important;
    padding: 10px !important;
}
.extra-events-clean summary {
    cursor: pointer !important;
    list-style: none !important;
    color: var(--muted) !important;
    font-size: 12px !important;
    font-weight: 850 !important;
    margin-bottom: 0 !important;
}
.extra-events-clean summary::-webkit-details-marker {
    display: none !important;
}
.extra-events-clean summary::before {
    content: "▸ " !important;
    color: var(--blue) !important;
}
.extra-events-clean[open] summary {
    margin-bottom: 10px !important;
}
.extra-events-clean[open] summary::before {
    content: "▾ " !important;
}
@media (max-width: 900px) {
    .news-risk-summary.pro,
    .event-row-clean.pro {
        grid-template-columns: 1fr !important;
    }
}
</style>


<style id="ita-sr-only-v20">
.sr-only {
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    padding: 0 !important;
    margin: -1px !important;
    overflow: hidden !important;
    clip: rect(0, 0, 0, 0) !important;
    white-space: nowrap !important;
    border: 0 !important;
}
</style>



<style id="ita-lot-calculator-no-preview-polish-v25">
#lot-size-calculator .lot-form {
    gap: 12px !important;
}
#lot-size-calculator .lot-btn {
    margin-top: 2px !important;
}
#lot-size-calculator .lot-result-clean {
    margin-top: 14px !important;
    border-radius: 16px !important;
    padding: 14px !important;
}
#lot-size-calculator .lot-result-grid {
    display: grid !important;
    grid-template-columns: repeat(3, minmax(0, 1fr)) !important;
    gap: 9px !important;
    margin-top: 12px !important;
}
#lot-size-calculator .lot-result-grid > div {
    border: 1px solid var(--line) !important;
    background: var(--surface) !important;
    border-radius: 13px !important;
    padding: 10px !important;
}
#lot-size-calculator .lot-result-grid span {
    display: block !important;
    color: var(--soft) !important;
    font-size: 9px !important;
    text-transform: uppercase !important;
    letter-spacing: .11em !important;
    font-weight: 850 !important;
    line-height: 1 !important;
}
#lot-size-calculator .lot-result-grid strong {
    display: block !important;
    margin-top: 7px !important;
    color: var(--text) !important;
    font-size: 13px !important;
    line-height: 1.25 !important;
    font-weight: 800 !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
    white-space: nowrap !important;
}
#lot-size-calculator .lot-warning {
    margin-top: 10px !important;
    border: 1px solid color-mix(in srgb, var(--amber) 38%, var(--line)) !important;
    background: color-mix(in srgb, var(--amber) 12%, var(--surface)) !important;
    color: color-mix(in srgb, var(--amber) 45%, var(--text)) !important;
    border-radius: 13px !important;
    padding: 10px 12px !important;
    font-size: 12px !important;
    line-height: 1.45 !important;
    font-weight: 750 !important;
}
@media (max-width: 900px) {
    #lot-size-calculator .lot-result-grid {
        grid-template-columns: 1fr !important;
    }
}
</style>



<style id="ita-news-events-upcoming-released-v27">
.released-events-clean {
    margin-top: 12px !important;
}
.released-events-clean > summary {
    color: var(--text) !important;
}
.event-row-clean.released {
    opacity: .88 !important;
    background: color-mix(in srgb, var(--surface) 88%, var(--surface-2)) !important;
}
.extra-events-clean.nested {
    margin-top: 10px !important;
    background: transparent !important;
}
.news-summary-card small {
    min-height: 0 !important;
}
</style>


<style id="ita-minimum-lot-size-v28">
#lot-size-calculator .lot-warning {
    margin-top: 10px !important;
    border: 1px solid color-mix(in srgb, var(--amber) 38%, var(--line)) !important;
    background: color-mix(in srgb, var(--amber) 12%, var(--surface)) !important;
    color: color-mix(in srgb, var(--amber) 45%, var(--text)) !important;
    border-radius: 13px !important;
    padding: 10px 12px !important;
    font-size: 12px !important;
    line-height: 1.45 !important;
    font-weight: 750 !important;
}
</style>


<style id="ita-lot-risk-note-clean-v29">
#lot-size-calculator .lot-risk-note {
    margin-top: 10px !important;
    border: 1px solid color-mix(in srgb, var(--blue) 24%, var(--line)) !important;
    background: color-mix(in srgb, var(--blue) 6%, var(--surface)) !important;
    color: var(--muted) !important;
    border-radius: 13px !important;
    padding: 10px 12px !important;
    font-size: 12px !important;
    line-height: 1.45 !important;
    font-weight: 650 !important;
}
#lot-size-calculator .lot-risk-note-title {
    color: var(--text) !important;
    font-size: 11px !important;
    line-height: 1 !important;
    text-transform: uppercase !important;
    letter-spacing: .11em !important;
    font-weight: 850 !important;
    margin-bottom: 6px !important;
}
</style>


<style id="ita-recent-releases-visible-v30">
.released-events-clean {
    margin-top: 14px !important;
    border-style: solid !important;
    background: color-mix(in srgb, var(--surface-2) 88%, white) !important;
}
.released-events-clean > summary {
    display: flex !important;
    justify-content: space-between !important;
    align-items: center !important;
    gap: 12px !important;
    color: var(--text) !important;
    font-size: 14px !important;
    font-weight: 900 !important;
}
.released-events-clean > summary span {
    display: inline-flex !important;
    align-items: center !important;
    min-height: 24px !important;
    padding: 0 9px !important;
    border: 1px solid var(--line) !important;
    border-radius: 999px !important;
    background: var(--surface) !important;
    color: var(--muted) !important;
    font-size: 11px !important;
    font-weight: 850 !important;
}
.recent-release-note {
    margin: -2px 0 10px !important;
    color: var(--muted) !important;
    font-size: 11.5px !important;
    line-height: 1.4 !important;
    font-weight: 650 !important;
}
.event-row-clean.released {
    opacity: .92 !important;
}
</style>


<style id="ita-recent-releases-24h-clean-v31">
.released-events-clean .recent-release-note {
    max-width: 760px !important;
}
.released-events-clean .extra-events-clean.nested {
    display: none !important;
}
</style>


<style id="ita-upcoming-events-24h-today-v32">
.upcoming-scope-note {
    display: block !important;
    margin-top: 3px !important;
    color: var(--muted) !important;
    font-size: 11px !important;
    line-height: 1.2 !important;
    font-weight: 700 !important;
    letter-spacing: .01em !important;
}
</style>

</head>
<body>

<header class="topbar">
    <div class="shell topbar-inner">
        <div class="brand">
            <div class="brand-mark">ITA</div>
            <div style="min-width:0">
                <div class="eyebrow">Institutional Trading Assistant</div>
                <h1 class="brand-title"><?= e($dashboardSymbol) ?> Institutional Analysis</h1>
            </div>
        </div>
        <nav class="nav">
            <button type="button" data-theme-toggle>Light Mode</button>
            <a href="my_alerts.php">My Alerts</a>
            <a href="settings.php">Settings</a>
            <a class="primary" href="../public/index.php">Symbols</a>
            <a class="danger" href="../public/logout.php">Logout</a>
        </nav>
    </div>
</header>

<main class="shell page">

    <section class="top-command-layout">
        <div class="top-left-stack">
            <div class="hero <?= e($dashboardState) ?>">
            <div class="hero-inner">
                <div>
                    <div class="status-pill">Institutional Decision · <?= e($displayStatus) ?></div>
                    <div class="decision"><?= e($dashboardAction) ?></div>
                    <p class="reason"><strong>Reason:</strong> <?= e($finalReason ?? 'Awaiting a complete institutional confirmation sequence.') ?></p>

                    <details class="clean-details hero-narrative">
                        <summary style="color:rgba(255,255,255,.82)">View full institutional narrative</summary>
                        <p class="reason"><?= nl2br(e($fullNarrative ?? $displayInstruction ?? $finalReason ?? 'No extended narrative available.')) ?></p>
                        <?php if (!empty($heroSummaryItems)): ?>
                            <div class="pill-row compact-hidden-tags">
                                <?php foreach (($heroSummaryItems ?? []) as $item): ?>
                                    <span><?= e($item) ?></span>
                                <?php endforeach; ?>
                            </div>
                        <?php endif; ?>
                    </details>
                </div>

                <div class="price-card price-card-clean">
                    <div class="label">Current Price</div>
                    <div class="price accent"><?= e($dashboardPrice) ?></div>
                    <div class="dashboard-live-price-source text-[10px] font-bold text-slate-500 mt-1"><?= e($liveQuoteSourceLabel ?? $livePriceSource ?? 'Price source') ?></div>
                </div>

                <div class="hero-metrics">
                    <div class="hero-metric"><div class="label">Execution Score</div><div class="value"><?= e($displayScore) ?>/10</div></div>
                    <div class="hero-metric"><div class="label">Confidence</div><div class="value"><?= e($displayConfidence) ?></div></div>
                    <div class="hero-metric"><div class="label">Location Quality</div><div class="value"><?= e($displayLocationLabel) ?> - <?= e($displayLocationScore) ?></div></div>
                    <div class="hero-metric"><div class="label">Risk Environment</div><div class="value">News: <?= e($displayNewsRisk) ?> · Geo: <?= e($displayGeoRisk) ?></div></div>
                </div>
            </div>
        </div>

            <div class="under-hero-grid">
                <div class="panel panel-pad analyze-panel">
            <div class="panel-head"><div><h2>Analyze Market</h2><p>Run a new institutional model scan.</p></div></div>
            <form method="post" action="dashboard.php" class="form-stack">
                <input type="hidden" name="analyze" value="1">
                <select name="asset_type" id="assetTypeSelect">
                    <?php foreach ($symbolGroups as $assetType => $groupedSymbols): ?>
                        <option value="<?= e($assetType) ?>" <?= $selectedAssetType === $assetType ? 'selected' : '' ?>><?= e(ucwords(strtolower($assetType))) ?></option>
                    <?php endforeach; ?>
                </select>
                <select name="symbol" id="symbolSelect">
                    <?php foreach ($symbolGroups as $assetType => $groupedSymbols): ?>
                        <optgroup label="<?= e(ucwords(strtolower($assetType))) ?>" data-asset-type="<?= e($assetType) ?>">
                            <?php foreach ($groupedSymbols as $s): ?>
                                <option value="<?= e($s['symbol']) ?>" data-asset-type="<?= e($assetType) ?>" <?= $symbol['symbol'] === $s['symbol'] ? 'selected' : '' ?>>
                                    <?= e($s['symbol']) ?> - <?= e($s['display_name'] ?? $s['name'] ?? '') ?>
                                </option>
                            <?php endforeach; ?>
                        </optgroup>
                    <?php endforeach; ?>
                </select>
                <select name="timeframe">
                    <?php foreach ($timeframes as $tf): ?>
                        <option value="<?= e($tf['code']) ?>" <?= $timeframe === $tf['code'] ? 'selected' : '' ?>><?= e($tf['code']) ?></option>
                    <?php endforeach; ?>
                </select>
                <select name="style">
                    <?php foreach ($tradingStyles as $styleRow): ?>
                        <option value="<?= e($styleRow['style_key']) ?>" <?= $selectedStyle === $styleRow['style_key'] ? 'selected' : '' ?>><?= e($styleRow['style_name']) ?></option>
                    <?php endforeach; ?>
                </select>
                <button class="btn" type="submit">Analyze Market</button>
            </form>
        </div>

                <div class="panel panel-pad execution-panel">
            <div class="panel-head">
                <div><h2>Execution Desk</h2><p>Readiness, entry model and dynamic risk.</p></div>
                <span class="badge"><?= e($displayScore) ?>/10</span>
            </div>
            <div class="metrics">
                <div class="metric"><div class="label">Confidence</div><div class="value"><?= e($displayConfidence) ?></div><div class="note">Current setup confidence</div></div>
                <div class="metric"><div class="label">Entry Model</div><div class="value"><?= e($tradePlan['entry_source'] ?? 'N/A') ?></div><div class="note"><?= e($displayStatus) ?></div></div>
                <div class="metric"><div class="label">Execution Style</div><div class="value"><?= e($tradePlan['style_name'] ?? ($selectedStyleData['style_name'] ?? 'N/A')) ?></div><div class="note">Base: <?= e($baseSlDistanceLabel ?? 'N/A') ?></div></div>
                <div class="metric dynamic-sl-card">
                    <div class="label">Dynamic SL</div>
                    <?php
                        $dynamicSlMain = e($actualRiskDistanceLabel ?? 'N/A');
                        $dynamicSlPips = '';
                        if (preg_match('/^(.*?)\s*\(([^)]*pips?)\)$/i', (string)($actualRiskDistanceLabel ?? ''), $slMatch)) {
                            $dynamicSlMain = e(trim($slMatch[1]));
                            $dynamicSlPips = e(trim($slMatch[2]));
                        }
                    ?>
                    <div class="value"><?= $dynamicSlMain ?><?php if ($dynamicSlPips !== ''): ?> <span class="soft-unit"><?= $dynamicSlPips ?></span><?php endif; ?></div>
                    <div class="note"><?= e($dynamicSlReason ?? 'Dynamic risk model') ?></div>
                </div>
            </div>
            <details class="clean-details">
                <summary>More execution details</summary>
                <div class="execution-detail-grid mt-3">
                    <div class="detail-mini">
                        <span>Location Quality</span>
                        <strong><?= e($displayLocationLabel) ?></strong>
                        <small><?= e($displayLocationScore) ?></small>
                    </div>
                    <div class="detail-mini">
                        <span>Confirmation</span>
                        <strong><?= e($confirmationReadiness ?? 'N/A') ?></strong>
                        <small><?= e(confirmation_text($confirmationCount ?? 0, $partialConfirmationCount ?? 0)) ?></small>
                    </div>
                    <div class="detail-mini">
                        <span>Macro</span>
                        <strong><?= e($displayMacro) ?></strong>
                        <small><?= e($displayMacroMessage) ?></small>
                    </div>
                </div>
            </details>
        </div>
            </div>
        </div>

        <aside class="side-stack">
            <div class="panel panel-pad">
                <div class="panel-head">
                    <div>
                        <h2>Market Intelligence</h2>
                        <p>Bias, session, volatility and macro state.</p>
                    </div>
                    <span class="badge"><?= e($timeframe) ?></span>
                </div>
                <div class="metrics">
                    <div class="metric"><div class="label">HTF Bias</div><div class="value"><?= e($contextBias ?? 'N/A') ?></div><div class="note"><?= !empty($contextTimeframe) ? e($contextTimeframe) : 'Higher timeframe' ?></div></div>
                    <div class="metric"><div class="label">LTF Bias</div><div class="value"><?= e($bias ?? 'N/A') ?></div><div class="note"><?= e($timeframe) ?> execution</div></div>
                    <div class="metric"><div class="label">Session</div><div class="value"><?= e($sessionName ?? 'N/A') ?></div><div class="note">Market session</div></div>
                    <div class="metric"><div class="label">Volatility</div><div class="value"><?= e($volatility ?? 'N/A') ?></div><div class="note">ATR <?= isset($atr) ? fp($atr, $symbol['decimals']) : 'N/A' ?></div></div>
                </div>
            </div>

            <div class="panel panel-pad">
                <div class="panel-head">
                    <div>
                        <h2>Setup Lifecycle</h2>
                        <p><?= e($setupLifecycleStage ?? 'Developing Setup') ?></p>
                    </div>
                    <span class="badge"><?= e($readinessSemanticLabel ?? 'Watchlist') ?></span>
                </div>
                <div class="progress"><div style="width: <?= e(max(0, min(100, (float)($setupLifecycleProgress ?? 0)))) ?>%"></div></div>
                <div class="metrics mt-3">
                    <div class="metric"><div class="label">Alignment</div><div class="value"><?= e($directionAlignmentLabel ?? 'N/A') ?></div><div class="note"><?= e($alignmentStrength ?? 0) ?>% strength</div></div>
                    <div class="metric"><div class="label">Execution Mode</div><div class="value"><?= e($displayExecutionMode) ?></div><div class="note"><?= e($displayGuidance) ?></div></div>
                </div>
            </div>
        </aside>
    </section>

    <section class="track-row-layout">
        <div class="panel panel-pad track-panel">
            <div class="panel-head">
                <div>
                    <h2>Track Setup</h2>
                    <p>Save alerts, news rules, notification stages and expiration for this setup.</p>
                </div>
            </div>

            <form method="post" action="save_alert.php" id="trackSetupForm" class="track-form">
                <input type="hidden" name="symbol" value="<?= e($symbol['symbol']) ?>">
                <input type="hidden" name="timeframe" value="<?= e($timeframe) ?>">
                <input type="hidden" name="style" value="<?= e($selectedStyle) ?>">
                <input type="hidden" name="setup_key" value="<?= e($setupAlertKey) ?>">
                <input type="hidden" name="return_to" value="<?= e($_SERVER['REQUEST_URI'] ?? '') ?>">
                <input type="hidden" name="preferred_side" value="<?= e($preferredScenario['side'] ?? '') ?>">
                <input type="hidden" name="preferred_status" value="<?= e($preferredScenario['status'] ?? '') ?>">
                <input type="hidden" name="preferred_entry" value="<?= e($preferredScenario['entry'] ?? '') ?>">
                <input type="hidden" name="preferred_entry_low" value="<?= e($preferredScenario['entry_low'] ?? '') ?>">
                <input type="hidden" name="preferred_entry_high" value="<?= e($preferredScenario['entry_high'] ?? '') ?>">
                <input type="hidden" name="preferred_stop_loss" value="<?= e($preferredScenario['stop_loss'] ?? '') ?>">
                <input type="hidden" name="preferred_tp1" value="<?= e($preferredScenario['tp1'] ?? '') ?>">
                <input type="hidden" name="preferred_tp2" value="<?= e($preferredScenario['tp2'] ?? '') ?>">
                <input type="hidden" name="preferred_tp3" value="<?= e($preferredScenario['tp3'] ?? '') ?>">
                <input type="hidden" name="preferred_alert_trigger_price" value="<?= e($preferredScenario['alert_trigger_price'] ?? '') ?>">
                <input type="hidden" name="preferred_liquidity_sweep_level" value="<?= e($preferredScenario['sweep_area']['level'] ?? '') ?>">
                <input type="hidden" name="preferred_structure_trigger" value="<?= e($preferredScenario['structure_shift']['label'] ?? '') ?>">
                <input type="hidden" name="buy_scenario_json" value="<?= e(json_encode($buyScenario ?? [], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)) ?>">
                <input type="hidden" name="sell_scenario_json" value="<?= e(json_encode($sellScenario ?? [], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)) ?>">

                <div class="track-box track-scenario">
                    <div class="label">Scenario</div>
                    <select name="scenario_to_track">
                        <option value="preferred">Preferred Scenario (<?= e($preferredScenario['side'] ?? 'N/A') ?>)</option>
                        <option value="sell">Sell Scenario</option>
                        <option value="buy">Buy Scenario</option>
                    </select>
                </div>

                <div class="track-box track-channels">
                    <div class="label">Channels</div>
                    <label><input type="checkbox" id="alertTelegram" name="telegram" checked> Telegram</label>
                    <label><input type="checkbox" id="alertEmail" name="email"> Email</label>
                </div>

                <div class="track-box track-news">
                    <div class="label">News Rules</div>
                    <label><input type="checkbox" name="avoid_news_trades" value="1" <?= !empty($userAlertPreferences['avoid_news_trades']) ? 'checked' : '' ?>> Warn around news windows</label>
                    <label><input type="checkbox" name="trade_high_impact_news" value="1" <?= !empty($userAlertPreferences['trade_high_impact_news']) ? 'checked' : '' ?>> Allow high-impact news trading</label>
                    <label><input type="checkbox" name="trade_medium_impact_news" value="1" <?= !empty($userAlertPreferences['trade_medium_impact_news']) ? 'checked' : '' ?>> Allow medium-impact news trading</label>
                    <div class="track-mini-inputs">
                        <input type="number" name="news_block_minutes_before" value="<?= e($userAlertPreferences['news_block_minutes_before'] ?? 60) ?>" min="0" max="240" aria-label="Minutes before news">
                        <input type="number" name="news_block_minutes_after" value="<?= e($userAlertPreferences['news_block_minutes_after'] ?? 30) ?>" min="0" max="240" aria-label="Minutes after news">
                    </div>
                </div>

                <div class="track-box track-stages">
                    <div class="label">Alert Stages</div>
                    <div class="stage-grid">
                        <label><input type="checkbox" name="notify_stage_1" value="1" <?= !empty($userAlertPreferences['notify_stage_1']) ? 'checked' : '' ?>> Zone approached</label>
                        <label><input type="checkbox" name="notify_stage_2" value="1" <?= !empty($userAlertPreferences['notify_stage_2']) ? 'checked' : '' ?>> Liquidity sweep</label>
                        <label><input type="checkbox" name="notify_stage_3" value="1" <?= !empty($userAlertPreferences['notify_stage_3']) ? 'checked' : '' ?>> Displacement</label>
                        <label><input type="checkbox" name="notify_final_setup" value="1" <?= !empty($userAlertPreferences['notify_final_setup']) ? 'checked' : '' ?>> Final setup</label>
                    </div>
                </div>

                <div class="track-box track-expiry">
                    <div class="label">Expiration</div>
                    <select name="expiration_option">
                        <?php foreach (['never' => 'Never Expire', '1h' => 'Expire after 1 hour', '4h' => 'Expire after 4 hours', '1d' => 'Expire after 1 day', 'session' => 'End of session'] as $expireKey => $expireLabel): ?>
                            <option value="<?= e($expireKey) ?>" <?= (($userAlertPreferences['expiration_option'] ?? 'never') === $expireKey) ? 'selected' : '' ?>><?= e($expireLabel) ?></option>
                        <?php endforeach; ?>
                    </select>
                    <button class="btn" type="submit">Track This Setup</button>
                </div>
            </form>

            <div id="alertTrackingStatus" class="hidden mt-3"></div>
            <?php if (!empty($setupAlertNotice)): ?><div class="lot-result"><?= e($setupAlertNotice) ?></div><?php endif; ?>
            <?php if (!empty($telegramAlertStatus)): ?><div class="lot-result risk-low"><?= e($telegramAlertStatus) ?></div><?php endif; ?>
        </div>
    </section>

    <div class="section-title scenario-title-block">
        <div>
            <div class="eyebrow">Scenario Matrix</div>
            <h2>Primary and Alternative Institutional Paths</h2>
            <div class="scenario-summary-line">
                <span class="preferred-summary">Preferred scenario: <?= e(strtoupper((string)($preferredScenario['side'] ?? 'N/A'))) ?> - <?= e($preferredScenario['status'] ?? 'Waiting') ?></span>
                <span class="alternative-summary">Alternative scenario: <?= e(strtoupper((string)($secondaryScenario['side'] ?? 'N/A'))) ?> - <?= e($secondaryScenario['status'] ?? 'Waiting') ?></span>
            </div>
        </div>
        <span class="badge"><?= e($displayBadgeText ?? $readinessSemanticLabel ?? 'Watchlist') ?></span>
    </div>

    <section class="scenario-grid">
        <?php foreach ($scenarioDeck as $scenario): ?>
            <?php
                $zone = $scenario['zone'] ?? null;
                $sideLower = strtolower((string)($scenario['side'] ?? ''));
                $isPreferred = (($scenario['side'] ?? '') === ($preferredScenario['side'] ?? ''));
            ?>
            <article class="scenario <?= e($sideLower) ?> <?= $isPreferred ? 'primary' : '' ?>">
                <div class="scenario-head">
                    <div>
                        <span class="side-chip <?= e($sideLower) ?>"><?= e($scenario['side'] ?? 'N/A') ?></span>
                        <div class="eyebrow mt-3"><?= $isPreferred ? 'Primary Scenario' : 'Alternative Scenario' ?></div>
                        <h3><?= e($scenario['side'] ?? 'N/A') ?> Scenario</h3>
                        <div class="scenario-status"><?= e($scenario['status'] ?? 'Waiting') ?></div>
                    </div>
                    <span class="badge"><?= e($scenario['score'] ?? 0) ?>% quality</span>
                </div>

                <div class="instruction"><?= e($scenario['instruction'] ?? 'Wait for confirmation before execution.') ?></div>

                <div class="scenario-matrix">
                    <div class="cell">
                        <div class="label">Institutional Zone</div>
                        <div class="cell-value"><?= e($zone['kind'] ?? 'No qualified zone') ?></div>
                        <div class="cell-value"><?= $zone ? fp($scenario['entry_low'], $symbol['decimals']) . ' - ' . fp($scenario['entry_high'], $symbol['decimals']) : 'N/A' ?></div>
                        <div class="note"><?= $zone ? 'Source: ' . e($zone['source']) . ' | Type: ' . e($zone['type']) : 'Wait for clean OB/FVG zone.' ?></div>
                    </div>

                    <div class="cell">
                        <div class="label">Suggested Entry</div>
                        <div class="cell-value"><?= $scenario['entry'] !== null ? fp($scenario['entry'], $symbol['decimals']) : 'Wait for trigger' ?></div>
                        <div class="note"><?= e(scenario_entry_distance_display($scenario['side'] ?? '', $scenario['entry'] ?? null, $currentPrice, $symbol['symbol'], $symbol['decimals'])) ?></div>
                        <div class="note"><?= e($scenario['entry_note'] ?? 'Entry is zone-based.') ?></div>
                    </div>

                    <div class="cell">
                        <div class="label">Dynamic Stop Loss</div>
                        <div class="cell-value"><?= $scenario['stop_loss'] !== null ? fp($scenario['stop_loss'], $symbol['decimals']) : 'Wait' ?></div>
                        <div class="note"><?= $scenario['risk_distance'] !== null ? e(trade_distance_label($scenario['risk_distance'], $symbol['symbol'], $symbol['decimals'])) : e($scenario['entry_note'] ?? 'Wait for trigger') ?></div>
                    </div>

                    <div class="cell">
                        <div class="label">Dynamic Targets</div>
                        <div class="targets">
                            <?php foreach ([1 => ($scenario['tp1'] ?? null), 2 => ($scenario['tp2'] ?? null), 3 => ($scenario['tp3'] ?? null)] as $rr => $targetValue): ?>
                                <?php $targetParts = ita_view_scenario_target($targetValue, $scenario['entry'] ?? null, $rr, $symbol['symbol'], $symbol['decimals']); ?>
                                <div class="target">
                                    <?php if ($targetParts): ?>
                                        <div class="target-rr"><?= e($targetParts['rr_label']) ?></div>
                                        <div class="target-price"><?= e($targetParts['target_label']) ?> <span class="target-distance-inline">(<?= e($targetParts['distance_label']) ?>)</span></div>
                                    <?php else: ?>
                                        <div class="target-rr">TP<?= (int)$rr ?></div>
                                        <div class="target-price">N/A</div>
                                        <div class="target-distance">Waiting</div>
                                    <?php endif; ?>
                                </div>
                            <?php endforeach; ?>
                        </div>
                    </div>
                </div>

                <details class="scenario-reasons mt-3">
                    <summary>Why this scenario exists</summary>
                    <div class="reason-grid">
                        <?php foreach (($scenario['reasons'] ?? []) as $reason): ?>
                            <div class="reason-pill"><?= e($reason) ?></div>
                        <?php endforeach; ?>
                    </div>
                </details>
            </article>
        <?php endforeach; ?>
    </section>

    <section class="panel panel-pad">
        <div class="panel-head">
            <div><h2>Institutional Confirmation Engine</h2><p>Mitigation, liquidity sweep, displacement and candle confirmation.</p></div>
            <span class="badge"><?= e($confirmationReadiness ?? 'Developing') ?> · <?= e($confirmationCount ?? 0) ?>/4 confirmed · <?= e($partialConfirmationCount ?? 0) ?> partial · <?= e(max(0, 4 - (int)($confirmationCount ?? 0))) ?> left</span>
        </div>
        <div class="confirmation-grid">
            <?php foreach ($confirmationSteps as $index => $step): ?>
                <?php
                    $state = is_array($step['state']) ? $step['state'] : [];
                    $stepClass = ita_view_bool_class($state);
                    $label = $state['label'] ?? $step['fallback'];
                    $message = $state['message'] ?? ($state['narrative'] ?? '');
                ?>
                <div class="step <?= e($stepClass) ?>">
                    <div class="step-num"><?= (int)($index + 1) ?></div>
                    <div class="step-title"><?= e($step['name']) ?></div>
                    <div class="step-note"><?= e($label) ?></div>
                    <?php if ($message !== ''): ?><div class="step-note"><?= e($message) ?></div><?php endif; ?>
                </div>
            <?php endforeach; ?>

        </div>
    </section>

    <section class="intel-grid">
        <div class="panel panel-pad span-5 setup-ranking-panel">
            <div class="panel-head"><div><h3>Setup Ranking</h3><p>Best available institutional zones ranked by quality and proximity.</p></div></div>

            <?php if (!empty($rankedSetups)): ?>
                <div class="setup-ranking-list">
                    <?php foreach (array_slice($rankedSetups, 0, 3) as $setup): ?>
                        <?php
                            $rankScore = ranking_score_display($setup['score'] ?? 0, $confirmationCount, $partialConfirmationCount);
                            $rankGrade = setup_rank_grade_refined((float)$rankScore);
                            $rankReasons = array_values(array_filter(array_map('strval', $setup['reasons'] ?? [])));
                        ?>
                        <article class="setup-rank-card">
                            <div class="rank-card-main">
                                <div class="rank-position">#<?= e($setup['rank'] ?? '-') ?></div>

                                <div class="rank-content">
                                    <div class="rank-title-row">
                                        <div>
                                            <h4><?= e($setup['source'] ?? 'Institutional Zone') ?></h4>
                                            <p><?= e($setup['direction'] ?? 'WAIT') ?> · <?= e($setup['type'] ?? 'N/A') ?></p>
                                        </div>
                                        <div class="rank-grade">
                                            <span><?= e($rankGrade) ?></span>
                                            <strong><?= e($rankScore) ?>/10</strong>
                                        </div>
                                    </div>

                                    <?php if (!empty($rankReasons)): ?>
                                        <ul class="rank-reason-list">
                                            <?php foreach (array_slice($rankReasons, 0, 2) as $rankReason): ?>
                                                <li><?= e($rankReason) ?></li>
                                            <?php endforeach; ?>
                                        </ul>
                                    <?php endif; ?>
                                </div>
                            </div>
                        </article>
                    <?php endforeach; ?>
                </div>
            <?php else: ?>
                <div class="notice">No ranked setup is available yet.</div>
            <?php endif; ?>
        </div>

        <div class="panel panel-pad span-7">
            <div class="panel-head"><div><h3>Market Structure</h3><p>Bias, alignment and location.</p></div></div>
            <div class="list">
                <div class="row"><span class="key">HTF Bias</span><span class="val"><?= e($contextBias) ?> <?= !empty($contextTimeframe) ? '(' . e($contextTimeframe) . ')' : '' ?></span></div>
                <div class="row"><span class="key"><?= e($timeframe) ?> Bias</span><span class="val"><?= e($bias) ?></span></div>
                <div class="row"><span class="key">Market State</span><span class="val"><?= e($isDirectionalConflict ? 'Directional Conflict' : $readinessSemanticLabel) ?></span></div>
                <div class="row"><span class="key">Directional Alignment</span><span class="val"><?= e($directionAlignmentLabel) ?> · <?= e($alignmentStrength) ?>%</span></div>
                <div class="row"><span class="key">Location Quality</span><span class="val"><?= e($displayLocationLabel) ?> · <?= e($displayLocationScore) ?></span></div>
                <div class="row"><span class="key">Volatility / ATR</span><span class="val"><?= e($volatility) ?> / <?= isset($atr) ? fp($atr, $symbol['decimals']) : 'N/A' ?></span></div>
            </div>
            <?php if (!empty($institutionalContextText ?? '')): ?><div class="notice mt-3"><?= e($institutionalContextText) ?></div><?php endif; ?>
        </div>

        <div class="panel panel-pad span-4">
            <div class="panel-head"><div><h3>Order Block Zone</h3><p>Active institutional block.</p></div></div>
            <?php if (!empty($activeOrderBlock)): ?>
                <div class="list">
                    <div class="row"><span class="key">Type</span><span class="val"><?= e($activeOrderBlock['type'] ?? 'N/A') ?></span></div>
                    <div class="row"><span class="key">Zone</span><span class="val"><?= fp($activeOrderBlock['zone_low'] ?? $activeOrderBlock['low'] ?? null, $symbol['decimals']) ?> - <?= fp($activeOrderBlock['zone_high'] ?? $activeOrderBlock['high'] ?? null, $symbol['decimals']) ?></span></div>
                    <div class="row"><span class="key">Strength</span><span class="val"><?= e($activeOrderBlock['strength'] ?? $activeOrderBlock['quality'] ?? 'N/A') ?></span></div>
                    <div class="row"><span class="key">Mitigated</span><span class="val"><?= !empty($activeOrderBlock['mitigated']) ? 'Yes' : 'No' ?></span></div>
                    <div class="row"><span class="key">Distance</span><span class="val"><?= e(zone_distance_display($currentPrice, $activeOrderBlock['zone_low'] ?? $activeOrderBlock['low'] ?? null, $activeOrderBlock['zone_high'] ?? $activeOrderBlock['high'] ?? null, $symbol['symbol'], $symbol['decimals'])) ?></span></div>
                </div>
            <?php else: ?>
                <div class="notice">No active order block detected.</div>
            <?php endif; ?>
        </div>

        <div class="panel panel-pad span-4">
            <div class="panel-head"><div><h3>Fair Value Gap Zone</h3><p>Active imbalance context.</p></div></div>
            <?php if (!empty($activeFVG)): ?>
                <div class="list">
                    <div class="row"><span class="key">Type</span><span class="val"><?= e($activeFVG['type'] ?? 'N/A') ?></span></div>
                    <div class="row"><span class="key">Zone</span><span class="val"><?= fp($activeFVG['low'] ?? null, $symbol['decimals']) ?> - <?= fp($activeFVG['high'] ?? null, $symbol['decimals']) ?></span></div>
                    <div class="row"><span class="key">Status</span><span class="val"><?= e($activeFvgZoneStatus ?? 'N/A') ?></span></div>
                    <div class="row"><span class="key">Quality</span><span class="val"><?= e($activeFVG['quality'] ?? 'N/A') ?>/5</span></div>
                    <div class="row"><span class="key">Distance</span><span class="val"><?= e(zone_distance_display($currentPrice, $activeFVG['low'] ?? null, $activeFVG['high'] ?? null, $symbol['symbol'], $symbol['decimals'])) ?></span></div>
                </div>
            <?php elseif (!empty($latestFvg)): ?>
                <div class="list">
                    <div class="row"><span class="key">Latest FVG</span><span class="val"><?= e($latestFvg['type'] ?? 'N/A') ?></span></div>
                    <div class="row"><span class="key">Latest Zone</span><span class="val"><?= fp($latestFvg['low'] ?? null, $symbol['decimals']) ?> - <?= fp($latestFvg['high'] ?? null, $symbol['decimals']) ?></span></div>
                </div>
            <?php else: ?>
                <div class="notice">No active FVG detected.</div>
            <?php endif; ?>
        </div>

        <div class="panel panel-pad span-4">
            <div class="panel-head"><div><h3>Liquidity Map</h3><p>Buy-side, sell-side and range levels.</p></div></div>
            <div class="list">
                <div class="row"><span class="key">Buy-side Liquidity</span><span class="val"><?= $liquidityBuySide !== null ? fp($liquidityBuySide, $symbol['decimals']) : 'N/A' ?></span></div>
                <div class="row"><span class="key">Sell-side Liquidity</span><span class="val"><?= $liquiditySellSide !== null ? fp($liquiditySellSide, $symbol['decimals']) : 'N/A' ?></span></div>
                <?php if (isset($liquidity['buy_side_distance'])): ?><div class="row"><span class="key">Buy-side Distance</span><span class="val"><?= e(trade_distance_label($liquidity['buy_side_distance'], $symbol['symbol'], $symbol['decimals'])) ?></span></div><?php endif; ?>
                <?php if (isset($liquidity['sell_side_distance'])): ?><div class="row"><span class="key">Sell-side Distance</span><span class="val"><?= e(trade_distance_label($liquidity['sell_side_distance'], $symbol['symbol'], $symbol['decimals'])) ?></span></div><?php endif; ?>
                <div class="row"><span class="key">Range High</span><span class="val"><?= $rangeHigh !== null ? fp($rangeHigh, $symbol['decimals']) : 'N/A' ?></span></div>
                <div class="row"><span class="key">Range Low</span><span class="val"><?= $rangeLow !== null ? fp($rangeLow, $symbol['decimals']) : 'N/A' ?></span></div>
                <div class="row"><span class="key">Equilibrium</span><span class="val"><?= $equilibrium !== null ? fp($equilibrium, $symbol['decimals']) : 'N/A' ?></span></div>
                <div class="row"><span class="key">Current Array</span><span class="val"><?= e($pdLabel) ?><?= $rangePosition !== null ? ' (' . number_format((float)$rangePosition, 1) . '%)' : '' ?></span></div>
            </div>
        </div>

        <div class="panel panel-pad span-7 news-risk-panel news-risk-pro">
            <?php
                $eventsToShow = !empty($newsEventsProfessional) ? $newsEventsProfessional : $newsEventsForDisplay;
                $eventsToShow = is_array($eventsToShow ?? null) ? array_values(array_filter($eventsToShow, 'is_array')) : [];

                $nowTs = time();
                $futureLimitTs = $nowTs + 86400; // next 24 hours
                $recentLimitTs = $nowTs - 86400; // last 24 hours

                $lagosTz = new DateTimeZone('Africa/Lagos');
                $todayLagos = (new DateTimeImmutable('@' . $nowTs))->setTimezone($lagosTz)->format('Y-m-d');

                $isUpcomingWithinDisplayWindow = function (int $eventTs) use ($nowTs, $futureLimitTs, $todayLagos, $lagosTz): bool {
                    if ($eventTs < $nowTs) {
                        return false;
                    }

                    $eventDateLagos = (new DateTimeImmutable('@' . $eventTs))->setTimezone($lagosTz)->format('Y-m-d');

                    return $eventTs <= $futureLimitTs || $eventDateLagos === $todayLagos;
                };

                $upcomingEvents = [];
                $releasedEvents = [];

                foreach ($eventsToShow as $event) {
                    $eventTs = dashboard_event_client_timestamp($event);

                    if ($eventTs <= 0) {
                        continue;
                    }

                    $hasActualValue = dashboard_event_has_actual_value($event);

                    /*
                     * Actual value means the event has already been released.
                     * It must never remain in Upcoming Events even if a bad
                     * provider timestamp is still in the future.
                     */
                    if ($hasActualValue && $eventTs > $nowTs) {
                        $eventTs = $nowTs - 60;
                    }

                    $event['__display_ts'] = $eventTs;

                    if (!$hasActualValue && $isUpcomingWithinDisplayWindow($eventTs)) {
                        $upcomingEvents[] = $event;
                    } elseif ($eventTs < $nowTs && $eventTs >= $recentLimitTs) {
                        $releasedEvents[] = $event;
                    }
                }

                $upcomingEvents = array_values(array_filter($upcomingEvents, function ($event) use ($nowTs) {
                    if (!is_array($event)) {
                        return false;
                    }

                    if (dashboard_event_has_actual_value($event)) {
                        return false;
                    }

                    $ts = (int)($event['__display_ts'] ?? dashboard_event_client_timestamp($event));
                    return $ts > $nowTs;
                }));

                usort($upcomingEvents, function ($a, $b) {
                    return ($a['__display_ts'] ?? dashboard_event_client_timestamp($a)) <=> ($b['__display_ts'] ?? dashboard_event_client_timestamp($b));
                });

                usort($releasedEvents, function ($a, $b) {
                    return ($b['__display_ts'] ?? dashboard_event_client_timestamp($b)) <=> ($a['__display_ts'] ?? dashboard_event_client_timestamp($a));
                });

                /*
                 * Recent releases must not depend only on the upcoming-events list.
                 * Read released rows directly from news_events and merge them here.
                 */
                $directRecentReleases = dashboard_db_recent_releases($symbol['symbol'] ?? 'XAUUSD', 24);
                if (!empty($directRecentReleases)) {
                    $releaseSeen = [];
                    $mergedReleased = array_merge($releasedEvents, $directRecentReleases);
                    $releasedEvents = [];

                    foreach ($mergedReleased as $releaseEvent) {
                        if (!is_array($releaseEvent)) {
                            continue;
                        }

                        $releaseTs = (int)($releaseEvent['__display_ts'] ?? dashboard_event_client_timestamp($releaseEvent));
                        if ($releaseTs <= 0) {
                            continue;
                        }

                        $releaseKey = md5(
                            strtoupper((string)($releaseEvent['title'] ?? $releaseEvent['event'] ?? '')) .
                            '|' . strtoupper((string)($releaseEvent['currency'] ?? '')) .
                            '|' . $releaseTs
                        );

                        if (isset($releaseSeen[$releaseKey])) {
                            continue;
                        }

                        $releaseSeen[$releaseKey] = true;
                        $releaseEvent['__display_ts'] = $releaseTs;
                        $releasedEvents[] = $releaseEvent;
                    }

                    usort($releasedEvents, function ($a, $b) {
                        return ($b['__display_ts'] ?? dashboard_event_client_timestamp($b)) <=> ($a['__display_ts'] ?? dashboard_event_client_timestamp($a));
                    });
                }

                /*
                 * Keep Recent Releases clean:
                 * - released only
                 * - not older than 24 hours
                 * - no nested archive drawer
                 */
                $releasedEvents = array_values(array_filter($releasedEvents, function ($releaseEvent) use ($nowTs, $recentLimitTs) {
                    if (!is_array($releaseEvent)) {
                        return false;
                    }

                    $releaseTs = (int)($releaseEvent['__display_ts'] ?? dashboard_event_client_timestamp($releaseEvent));

                    return $releaseTs > 0 && $releaseTs < $nowTs && $releaseTs >= $recentLimitTs;
                }));

                usort($releasedEvents, function ($a, $b) {
                    return ($b['__display_ts'] ?? dashboard_event_client_timestamp($b)) <=> ($a['__display_ts'] ?? dashboard_event_client_timestamp($a));
                });

                $eventCount = count($upcomingEvents);
                $releasedCount = count($releasedEvents);

                $visibleEvents = array_slice($upcomingEvents, 0, 4);
                $hiddenEvents = array_slice($upcomingEvents, 4);

                $visibleReleasedEvents = $releasedEvents;

                $nextEvent = $upcomingEvents[0] ?? null;
                $nextEventTs = $nextEvent ? (int)($nextEvent['__display_ts'] ?? dashboard_event_client_timestamp($nextEvent)) : 0;

                $nextEventImpact = $nextEvent ? dashboard_forex_factory_impact($nextEvent) : 'NONE';
                $calendarImpactLabel = $nextEvent
                    ? (!empty($nextEvent['impact_label']) ? (string)$nextEvent['impact_label'] : dashboard_forex_factory_impact_label($nextEventImpact))
                    : 'No upcoming event';

                $calendarBadgeClass = $nextEvent ? strtolower((string)$nextEventImpact) : 'none';
                $impactGuidance = $nextEvent ? dashboard_calendar_impact_guidance($nextEventImpact) : 'No event is scheduled within the next 24 hours.';
            ?>

            <div class="panel-head news-risk-head">
                <div>
                    <h3>News & Risk Intelligence</h3>
                    <p>Calendar impact, countdown and geopolitical context.</p>
                </div>
                <span class="event-impact-pill header-impact <?= e($calendarBadgeClass) ?>"><?= e($calendarImpactLabel) ?></span>
            </div>

            <div class="news-risk-summary pro">
                <div class="news-summary-card">
                    <span>Next Event Impact</span>
                    <strong><?= e($calendarImpactLabel) ?></strong>
                    <small><?= e($impactGuidance) ?></small>
                </div>

                <div class="news-summary-card">
                    <span>Next Event</span>
                    <?php if ($nextEvent): ?>
                        <strong><?= e($nextEvent['title'] ?? $nextEvent['event'] ?? 'Economic event') ?></strong>
                        <small>
                            <span class="js-event-time" data-event-ts="<?= e($nextEventTs) ?>"><?= e(dashboard_event_server_time_label($nextEventTs)) ?></span>
                            · <span class="js-event-countdown" data-event-ts="<?= e($nextEventTs) ?>" data-has-actual="<?= e(dashboard_event_has_actual_value($nextEvent) ? '1' : '0') ?>"><?= e(dashboard_event_countdown_label($nextEventTs, $nextEvent)) ?></span>
                        </small>
                    <?php else: ?>
                        <strong>No upcoming event</strong>
                        <small>No calendar event was found within the next 24 hours or later today.</small>
                    <?php endif; ?>
                </div>

                <div class="news-summary-card">
                    <span>Geopolitical Risk</span>
                    <strong><?= e($displayGeoRisk) ?></strong>
                    <small><?= e($geoRiskGuidance ?? $geo['message'] ?? 'No major geopolitical risk detected currently.') ?></small>
                </div>
            </div>

            <div class="upcoming-events-clean pro">
                <div class="upcoming-header clean">
                    <div>
                        <h4>Upcoming Events</h4>
                        <small class="upcoming-scope-note">Next 24 hours / today only</small>
                    </div>
                    <span><?= e($eventCount) ?> upcoming</span>
                </div>

                <?php if (!empty($visibleEvents)): ?>
                    <div class="event-list-clean">
                        <?php foreach ($visibleEvents as $event): ?>
                            <?php
                                $eventImpact = dashboard_forex_factory_impact($event);
                                $eventImpactLabel = !empty($event['impact_label']) ? (string)$event['impact_label'] : dashboard_forex_factory_impact_label($eventImpact);
                                $eventTitle = trim((string)($event['title'] ?? $event['event'] ?? 'Economic event'));
                                $eventCurrency = strtoupper((string)($event['currency'] ?? ''));
                                $eventTs = (int)($event['__display_ts'] ?? dashboard_event_client_timestamp($event));
                                $forecast = $event['forecast'] ?? null;
                                $previous = $event['previous'] ?? null;
                                $actual = dashboard_event_actual_display($event, $eventTs);
                            ?>
                            <article class="event-row-clean pro">
                                <div class="event-impact-pill <?= e(strtolower($eventImpact)) ?>"><?= e($eventImpactLabel) ?></div>
                                <div class="event-main-clean">
                                    <div class="event-title-clean"><?= e($eventTitle) ?></div>
                                    <div class="event-meta-clean">
                                        <?= $eventCurrency !== '' ? e($eventCurrency) . ' · ' : '' ?>
                                        <span class="js-event-time" data-event-ts="<?= e($eventTs) ?>"><?= e(dashboard_event_server_time_label($eventTs)) ?></span>
                                        · <span class="event-countdown js-event-countdown" data-event-ts="<?= e($eventTs) ?>" data-has-actual="<?= e(dashboard_event_has_actual_value($event) ? '1' : '0') ?>"><?= e(dashboard_event_countdown_label($eventTs, $event)) ?></span>
                                    </div>
                                    <div class="event-data-clean">
                                        Forecast: <?= e($forecast ?? 'N/A') ?> · Previous: <?= e($previous ?? 'N/A') ?> · Actual: <?= e($actual) ?>
                                    </div>
                                </div>
                            </article>
                        <?php endforeach; ?>
                    </div>

                    <?php if (!empty($hiddenEvents)): ?>
                        <details class="extra-events-clean">
                            <summary>Show <?= e(count($hiddenEvents)) ?> more upcoming event<?= count($hiddenEvents) === 1 ? '' : 's' ?></summary>
                            <div class="event-list-clean">
                                <?php foreach ($hiddenEvents as $event): ?>
                                    <?php
                                        $eventImpact = dashboard_forex_factory_impact($event);
                                        $eventImpactLabel = !empty($event['impact_label']) ? (string)$event['impact_label'] : dashboard_forex_factory_impact_label($eventImpact);
                                        $eventTitle = trim((string)($event['title'] ?? $event['event'] ?? 'Economic event'));
                                        $eventCurrency = strtoupper((string)($event['currency'] ?? ''));
                                        $eventTs = (int)($event['__display_ts'] ?? dashboard_event_client_timestamp($event));
                                        $forecast = $event['forecast'] ?? null;
                                        $previous = $event['previous'] ?? null;
                                        $actual = dashboard_event_actual_display($event, $eventTs);
                                    ?>
                                    <article class="event-row-clean pro">
                                        <div class="event-impact-pill <?= e(strtolower($eventImpact)) ?>"><?= e($eventImpactLabel) ?></div>
                                        <div class="event-main-clean">
                                            <div class="event-title-clean"><?= e($eventTitle) ?></div>
                                            <div class="event-meta-clean">
                                                <?= $eventCurrency !== '' ? e($eventCurrency) . ' · ' : '' ?>
                                                <span class="js-event-time" data-event-ts="<?= e($eventTs) ?>"><?= e(dashboard_event_server_time_label($eventTs)) ?></span>
                                                · <span class="event-countdown js-event-countdown" data-event-ts="<?= e($eventTs) ?>" data-has-actual="<?= e(dashboard_event_has_actual_value($event) ? '1' : '0') ?>"><?= e(dashboard_event_countdown_label($eventTs, $event)) ?></span>
                                            </div>
                                            <div class="event-data-clean">
                                                Forecast: <?= e($forecast ?? 'N/A') ?> · Previous: <?= e($previous ?? 'N/A') ?> · Actual: <?= e($actual) ?>
                                            </div>
                                        </div>
                                    </article>
                                <?php endforeach; ?>
                            </div>
                        </details>
                    <?php endif; ?>
                <?php else: ?>
                    <div class="notice">No upcoming calendar event was found within the next 24 hours or later today.</div>
                <?php endif; ?>

                <?php if (!empty($visibleReleasedEvents)): ?>
                    <details class="extra-events-clean released-events-clean" open>
                        <summary>Recent Releases <span><?= e($releasedCount) ?> event<?= $releasedCount === 1 ? '' : 's' ?></span></summary>
                        <div class="recent-release-note">Released news appears here automatically and stays visible for 24 hours. Actual values are shown when available.</div>
                        <div class="event-list-clean">
                            <?php foreach ($visibleReleasedEvents as $event): ?>
                                <?php
                                    $eventImpact = dashboard_forex_factory_impact($event);
                                    $eventImpactLabel = !empty($event['impact_label']) ? (string)$event['impact_label'] : dashboard_forex_factory_impact_label($eventImpact);
                                    $eventTitle = trim((string)($event['title'] ?? $event['event'] ?? 'Economic event'));
                                    $eventCurrency = strtoupper((string)($event['currency'] ?? ''));
                                    $eventTs = (int)($event['__display_ts'] ?? dashboard_event_client_timestamp($event));
                                    $forecast = $event['forecast'] ?? null;
                                    $previous = $event['previous'] ?? null;
                                    $actual = dashboard_event_actual_display($event, $eventTs);
                                ?>
                                <article class="event-row-clean pro released">
                                    <div class="event-impact-pill <?= e(strtolower($eventImpact)) ?>"><?= e($eventImpactLabel) ?></div>
                                    <div class="event-main-clean">
                                        <div class="event-title-clean"><?= e($eventTitle) ?></div>
                                        <div class="event-meta-clean">
                                            <?= $eventCurrency !== '' ? e($eventCurrency) . ' · ' : '' ?>
                                            <span class="js-event-time" data-event-ts="<?= e($eventTs) ?>"><?= e(dashboard_event_server_time_label($eventTs)) ?></span>
                                            · <span class="event-countdown js-event-countdown" data-event-ts="<?= e($eventTs) ?>" data-has-actual="<?= e(dashboard_event_has_actual_value($event) ? '1' : '0') ?>"><?= e(dashboard_event_countdown_label($eventTs, $event)) ?></span>
                                        </div>
                                        <div class="event-data-clean">
                                            Forecast: <?= e($forecast ?? 'N/A') ?> · Previous: <?= e($previous ?? 'N/A') ?> · Actual: <?= e($actual) ?>
                                        </div>
                                    </div>
                                </article>
                            <?php endforeach; ?>
                        </div>
                    </details>
                <?php endif; ?>
            </div>

            <?php if (!empty($geoEventsForDisplay)): ?>
                <details class="clean-details geo-details-clean">
                    <summary>View geopolitical headline details</summary>
                    <div class="news-grid">
                        <?php foreach ($geoEventsForDisplay as $event): ?>
                            <div class="news">
                                <div class="news-title"><?= e($event['title'] ?? $event['headline'] ?? $event['event'] ?? 'Geopolitical headline') ?></div>
                                <div class="news-meta">
                                    <?= e($event['source'] ?? '') ?>
                                    <?= !empty($event['published_at']) ? ' · ' . e($event['published_at']) : (!empty($event['time']) ? ' · ' . e($event['time']) : '') ?>
                                </div>
                            </div>
                        <?php endforeach; ?>
                    </div>
                </details>
            <?php endif; ?>
        </div>

        <div class="panel panel-pad span-5" id="lot-size-calculator">
            <div class="panel-head"><div><h3>Lot Size Calculator</h3><p>Uses selected scenario entry and SL.</p></div></div>

            <?php
            $lotResult = null;
            $lotError = null;
            $estimatedRiskAmount = null;
            $plannedRiskAmount = null;
            $minimumLotRiskAmount = null;
            $minimumLotWarning = null;
            $lotScenarioUsed = null;
            $lotScenarioKey = strtoupper((string)($_POST['lot_scenario'] ?? 'PREFERRED'));
            $entryForLot = 0.0;
            $slForLot = 0.0;
            $lotRiskDistance = 0.0;
            $minimumBrokerLot = dashboard_symbol_min_lot_safe($symbol['symbol'] ?? '');
            $brokerLotStep = dashboard_symbol_lot_step_safe($symbol['symbol'] ?? '');

            if (!function_exists('dashboard_parse_money_number')) {
                function dashboard_parse_money_number($value): float
                {
                    $value = trim((string)$value);
                    $value = str_replace([',', '$', ' '], '', $value);
                    return is_numeric($value) ? (float)$value : 0.0;
                }
            }

            if (!function_exists('dashboard_decode_lot_scenario')) {
                function dashboard_decode_lot_scenario($json): array
                {
                    if (!is_string($json) || trim($json) === '') {
                        return [];
                    }

                    $decoded = json_decode($json, true);
                    return is_array($decoded) ? $decoded : [];
                }
            }

            if (!function_exists('dashboard_pick_lot_scenario')) {
                function dashboard_pick_lot_scenario(string $scenarioKey, array $preferredScenario, array $buyScenario, array $sellScenario): array
                {
                    $scenarioKey = strtoupper($scenarioKey);

                    if ($scenarioKey === 'BUY') {
                        return $buyScenario;
                    }

                    if ($scenarioKey === 'SELL') {
                        return $sellScenario;
                    }

                    return $preferredScenario;
                }
            }

            if (!function_exists('dashboard_lot_value_per_price_move')) {
                function dashboard_lot_value_per_price_move(string $symbolCode, float $entryPrice): float
                {
                    return dashboard_symbol_contract_size_safe($symbolCode, $entryPrice);
                }
            }

            if (!function_exists('dashboard_format_lot_size')) {
                function dashboard_format_lot_size(float $lot, float $step = 0.01): string
                {
                    if ($lot <= 0) {
                        return '0';
                    }

                    $precision = dashboard_lot_precision_safe($step);
                    $step = $step > 0 ? $step : 0.01;
                    $floored = floor($lot / $step) * $step;

                    return number_format($floored, $precision, '.', '');
                }
            }

            $postedPreferredScenario = dashboard_decode_lot_scenario($_POST['lot_preferred_scenario_json'] ?? '');
            $postedBuyScenario = dashboard_decode_lot_scenario($_POST['lot_buy_scenario_json'] ?? '');
            $postedSellScenario = dashboard_decode_lot_scenario($_POST['lot_sell_scenario_json'] ?? '');

            if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['calculate_lot'])) {
                $balance = dashboard_parse_money_number($_POST['balance'] ?? 0);
                $riskPercent = dashboard_parse_money_number($_POST['risk'] ?? 0);
                $lotScenarioKey = strtoupper((string)($_POST['lot_scenario'] ?? 'PREFERRED'));

                /*
                 * Use the posted scenario JSON first. This keeps the calculation
                 * tied to the exact Preferred/BUY/SELL card the user selected.
                 */
                $lotScenarioFromPost = dashboard_pick_lot_scenario(
                    $lotScenarioKey,
                    $postedPreferredScenario,
                    $postedBuyScenario,
                    $postedSellScenario
                );

                $lotScenarioFromServer = dashboard_pick_lot_scenario(
                    $lotScenarioKey,
                    is_array($preferredScenario ?? null) ? $preferredScenario : [],
                    is_array($buyScenario ?? null) ? $buyScenario : [],
                    is_array($sellScenario ?? null) ? $sellScenario : []
                );

                $lotScenario = !empty($lotScenarioFromPost) ? $lotScenarioFromPost : $lotScenarioFromServer;
                $lotScenarioUsed = $lotScenario;

                $entryForLot = isset($lotScenario['entry']) && is_numeric($lotScenario['entry']) ? (float)$lotScenario['entry'] : 0.0;
                $slForLot = isset($lotScenario['stop_loss']) && is_numeric($lotScenario['stop_loss']) ? (float)$lotScenario['stop_loss'] : 0.0;
                $lotRiskDistance = abs($entryForLot - $slForLot);

                $_SESSION['last_lot_calculator_values'] = [
                    'lot_scenario' => $lotScenarioKey,
                    'balance' => $balance,
                    'risk' => $riskPercent,
                ];

                if ($balance <= 0) {
                    $lotError = 'Please enter a valid account balance.';
                } elseif ($riskPercent <= 0) {
                    $lotError = 'Please enter a valid risk percentage.';
                } elseif ($entryForLot <= 0 || $slForLot <= 0 || $lotRiskDistance <= 0) {
                    $lotError = 'The selected scenario does not currently have a valid entry and stop loss. Choose another scenario or wait for a valid setup.';
                } else {
                    $plannedRiskAmount = $balance * ($riskPercent / 100);
                    $valuePerMove = dashboard_lot_value_per_price_move((string)($symbol['symbol'] ?? 'XAUUSD'), $entryForLot);
                    $rawLot = $plannedRiskAmount / ($lotRiskDistance * $valuePerMove);

                    if ($rawLot <= 0 || is_nan($rawLot) || is_infinite($rawLot)) {
                        $lotError = 'Lot size calculation failed because the selected risk distance is invalid.';
                    } else {
                        $minimumLotRiskAmount = round($lotRiskDistance * $valuePerMove * $minimumBrokerLot, 2);

                        /*
                         * Almost all brokers enforce 0.01 as the minimum tradeable lot.
                         * Therefore the dashboard should not recommend 0.00556 lots.
                         * It should apply 0.01 and show the real estimated risk.
                         */
                        if ($rawLot < $minimumBrokerLot) {
                            $lotResult = dashboard_format_lot_size($minimumBrokerLot, $brokerLotStep);
                            $estimatedRiskAmount = $minimumLotRiskAmount;
                            $minimumLotWarning = 'Your risk budget is $' . number_format((float)$plannedRiskAmount, 2) . ', but ' . dashboard_format_lot_size($minimumBrokerLot, $brokerLotStep) . ' lot is the smallest tradeable size for this symbol. With this setup distance, ' . dashboard_format_lot_size($minimumBrokerLot, $brokerLotStep) . ' lot gives an estimated risk of $' . number_format($minimumLotRiskAmount, 2) . '.';
                        } else {
                            $lotResult = dashboard_format_lot_size($rawLot, $brokerLotStep);
                            $lotResultNumeric = (float)$lotResult;
                            $estimatedRiskAmount = round($lotRiskDistance * $valuePerMove * $lotResultNumeric, 2);
                        }
                    }
                }
            }

            $lotFormScenario = strtoupper((string)($_POST['lot_scenario'] ?? ($_SESSION['last_lot_calculator_values']['lot_scenario'] ?? 'PREFERRED')));
            $lotFormBalance = $_POST['balance'] ?? ($_SESSION['last_lot_calculator_values']['balance'] ?? '1000');
            $lotFormRisk = $_POST['risk'] ?? ($_SESSION['last_lot_calculator_values']['risk'] ?? '1');
            ?>

            <form method="post" action="?analysis=1#lot-size-calculator" class="lot-form">
                <input type="hidden" name="analyze" value="1">
                <input type="hidden" name="symbol" value="<?= e($symbol['symbol'] ?? '') ?>">
                <input type="hidden" name="timeframe" value="<?= e($timeframe ?? '') ?>">
                <input type="hidden" name="style" value="<?= e($selectedStyle ?? '') ?>">
                <input type="hidden" name="lot_preferred_scenario_json" value="<?= e(json_encode($preferredScenario ?? [], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)) ?>">
                <input type="hidden" name="lot_buy_scenario_json" value="<?= e(json_encode($buyScenario ?? [], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)) ?>">
                <input type="hidden" name="lot_sell_scenario_json" value="<?= e(json_encode($sellScenario ?? [], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)) ?>">
                <label class="lot-field lot-select">
                    <span>Scenario</span>
                    <select name="lot_scenario">
                        <option value="PREFERRED" <?= ($lotFormScenario === 'PREFERRED') ? 'selected' : '' ?>>Preferred Scenario (<?= e($preferredScenario['side'] ?? 'N/A') ?>)</option>
                        <option value="SELL" <?= ($lotFormScenario === 'SELL') ? 'selected' : '' ?>>Sell Scenario</option>
                        <option value="BUY" <?= ($lotFormScenario === 'BUY') ? 'selected' : '' ?>>Buy Scenario</option>
                    </select>
                </label>

                <div class="lot-field-grid">
                    <label class="lot-field">
                        <span>Account Balance</span>
                        <div class="lot-input-wrap">
                            <em>$</em>
                            <input type="number" step="0.01" min="0" name="balance" placeholder="1000" value="<?= e($lotFormBalance) ?>">
                        </div>
                    </label>

                    <label class="lot-field">
                        <span>Risk</span>
                        <div class="lot-input-wrap">
                            <input type="number" step="0.01" min="0" name="risk" placeholder="1" value="<?= e($lotFormRisk) ?>">
                            <em>%</em>
                        </div>
                    </label>
                </div>

                <button class="btn lot-btn" type="submit" name="calculate_lot" value="1">Calculate Lot Size</button>
            </form>

            <?php if ($lotResult !== null): ?>
                <div id="lot-result" class="lot-result lot-result-clean">
                    <div class="label">Recommended Tradeable Position Size</div>
                    <div class="value"><?= e($lotResult) ?> lots</div>

                    <div class="lot-result-grid">
                        <div>
                            <span>Risk Budget</span>
                            <strong>$<?= e(number_format((float)$plannedRiskAmount, 2)) ?></strong>
                        </div>
                        <div>
                            <span>Estimated Risk</span>
                            <strong>$<?= e(number_format((float)$estimatedRiskAmount, 2)) ?></strong>
                        </div>
                        <div>
                            <span>Scenario</span>
                            <strong><?= e($lotScenarioUsed['side'] ?? $lotScenarioKey) ?></strong>
                        </div>
                    </div>

                    <div class="note">
                        Entry: <?= e(fp($entryForLot, (int)($symbol['decimals'] ?? 2))) ?> ·
                        SL: <?= e(fp($slForLot, (int)($symbol['decimals'] ?? 2))) ?> ·
                        Distance: <?= e(trade_distance_label($lotRiskDistance, $symbol['symbol'], $symbol['decimals'])) ?> ·
                        Balance: $<?= e(number_format((float)$balance, 2)) ?> ·
                        Risk: <?= e($riskPercent) ?>%
                    </div>

                    <?php if (!empty($minimumLotWarning)): ?>
                        <div class="lot-risk-note">
                            <div class="lot-risk-note-title">Why estimated risk is higher</div>
                            <div><?= e($minimumLotWarning) ?></div>
                        </div>
                    <?php endif; ?>
                </div>
                <script>window.addEventListener('load', function(){var el=document.getElementById('lot-result'); if(el) el.scrollIntoView({behavior:'smooth',block:'center'});});</script>
            <?php endif; ?>
            <?php if ($lotError): ?>
                <div id="lot-result" class="lot-result risk-high"><?= e($lotError) ?></div>
                <script>window.addEventListener('load', function(){var el=document.getElementById('lot-result'); if(el) el.scrollIntoView({behavior:'smooth',block:'center'});});</script>
            <?php endif; ?>
        </div>

        
    </section>

</main>

<script>
document.addEventListener('DOMContentLoaded', function () {
    const root = document.documentElement;
    const themeBtn = document.querySelector('[data-theme-toggle]');
    const savedTheme = localStorage.getItem('itaCommandThemeV3') || 'dark';

    function applyTheme(mode) {
        root.setAttribute('data-theme', mode);
        localStorage.setItem('itaCommandThemeV3', mode);
        if (themeBtn) themeBtn.textContent = mode === 'dark' ? 'Light Mode' : 'Dark Mode';
    }
    applyTheme(savedTheme);

    if (themeBtn) {
        themeBtn.addEventListener('click', function () {
            applyTheme(root.getAttribute('data-theme') === 'dark' ? 'light' : 'dark');
        });
    }

    const assetTypeSelect = document.getElementById('assetTypeSelect');
    const symbolSelect = document.getElementById('symbolSelect');

    function filterSymbolsByAssetType() {
        if (!assetTypeSelect || !symbolSelect) return;
        const selectedAssetType = assetTypeSelect.value;
        let firstVisibleOption = null;
        const currentValue = symbolSelect.value;
        let currentStillVisible = false;

        Array.from(symbolSelect.querySelectorAll('optgroup')).forEach(function (group) {
            const showGroup = group.dataset.assetType === selectedAssetType;
            group.hidden = !showGroup;

            Array.from(group.querySelectorAll('option')).forEach(function (option) {
                option.hidden = !showGroup;
                option.disabled = !showGroup;

                if (showGroup && !firstVisibleOption) firstVisibleOption = option;
                if (showGroup && option.value === currentValue) currentStillVisible = true;
            });
        });

        if (!currentStillVisible && firstVisibleOption) symbolSelect.value = firstVisibleOption.value;
    }

    if (assetTypeSelect) {
        assetTypeSelect.addEventListener('change', filterSymbolsByAssetType);
        filterSymbolsByAssetType();
    }

    const key = "trade_alert_<?= e($setupAlertKey ?? '') ?>";
    const form = document.getElementById("trackSetupForm");
    const box = document.getElementById("alertTrackingStatus");

    function renderSavedAlert() {
        if (!box) return;
        const saved = localStorage.getItem(key);
        if (!saved) return;

        let data = {};
        try { data = JSON.parse(saved); } catch (e) {}

        const channels = data.channels || "selected channel(s)";
        box.classList.remove('hidden');
        box.className = "lot-result risk-low";
        box.innerHTML = '<strong>Alert tracking is active for this setup.</strong><br><span class="note">Channels: ' + channels + '</span>';
    }

    renderSavedAlert();

    if (form) {
        form.addEventListener("submit", function (event) {
            const tCheck = document.getElementById("alertTelegram");
            const eCheck = document.getElementById("alertEmail");
            if ((!tCheck || !tCheck.checked) && (!eCheck || !eCheck.checked)) {
                event.preventDefault();
                alert("Please select Telegram, Email, or both before tracking this setup.");
                return false;
            }

            const channels = [];
            if (tCheck && tCheck.checked) channels.push("Telegram");
            if (eCheck && eCheck.checked) channels.push("Email");

            localStorage.setItem(key, JSON.stringify({
                channels: channels.length ? channels.join(" + ") : "No channel selected",
                time: new Date().toISOString()
            }));
        });
    }

    function pad(value) { return String(value).padStart(2, '0'); }
    function updateNewsCountdowns() {
        document.querySelectorAll('.news-countdown').forEach(function (el) {
            var ts = parseInt(el.getAttribute('data-event-ts') || '0', 10);
            if (!ts) return;
            var seconds = Math.max(0, Math.floor((ts * 1000 - Date.now()) / 1000));
            var mins = Math.floor(seconds / 60);
            if (seconds <= 0) {
                el.textContent = 'LIVE NOW';
            } else if (mins < 60) {
                el.textContent = 'in ' + mins + ' minutes';
            } else {
                el.textContent = 'in ' + Math.floor(mins / 60) + 'h ' + pad(mins % 60) + 'm';
            }
        });
    }
    updateNewsCountdowns();
    setInterval(updateNewsCountdowns, 30000);
});
</script>


<script id="ita-news-timezone-countdown-v19">
(function () {
    function formatTime(ts) {
        if (!ts || Number.isNaN(ts)) return 'Time pending';
        try {
            return new Intl.DateTimeFormat(undefined, {
                month: 'short',
                day: 'numeric',
                year: 'numeric',
                hour: 'numeric',
                minute: '2-digit',
                timeZone: 'Africa/Lagos',
                timeZoneName: 'short'
            }).format(new Date(ts * 1000));
        } catch (e) {
            return new Date(ts * 1000).toLocaleString();
        }
    }

    function formatCountdown(ts, hasActual) {
        if (!ts || Number.isNaN(ts)) return 'Countdown pending';

        const diffMs = (ts * 1000) - Date.now();
        const past = diffMs < 0;
        let seconds = Math.floor(Math.abs(diffMs) / 1000);
        const days = Math.floor(seconds / 86400);
        seconds %= 86400;
        const hours = Math.floor(seconds / 3600);
        seconds %= 3600;
        const minutes = Math.floor(seconds / 60);

        let label;
        if (days > 0) {
            label = `${days}d ${hours}h`;
        } else if (hours > 0) {
            label = `${hours}h ${minutes}m`;
        } else {
            label = `${Math.max(0, minutes)}m`;
        }

        if (!past) return `Starts in ${label}`;
        return hasActual ? `Released ${label} ago` : 'Released — awaiting actual';
    }

    function refreshNewsTimes() {
        document.querySelectorAll('.js-event-time[data-event-ts]').forEach(function (el) {
            const ts = Number(el.getAttribute('data-event-ts') || 0);
            el.textContent = formatTime(ts);
        });

        document.querySelectorAll('.js-event-countdown[data-event-ts]').forEach(function (el) {
            const ts = Number(el.getAttribute('data-event-ts') || 0);
            const hasActual = el.getAttribute('data-has-actual') === '1';
            el.textContent = formatCountdown(ts, hasActual);
        });
    }

    refreshNewsTimes();
    setInterval(refreshNewsTimes, 30000);
})();
</script>


</body>
</html>
