<?php
// Restore real visitor IP — only trust CF-Connecting-IP from Cloudflare's network
$cf_ips = ['173.245.48.0/20','103.21.244.0/22','103.22.200.0/22','103.31.4.0/22',
'141.101.64.0/18','108.162.192.0/18','190.93.240.0/20','188.114.96.0/20',
'197.234.240.0/22','198.41.128.0/17','162.158.0.0/15','104.16.0.0/13',
'104.24.0.0/14','172.64.0.0/13','131.0.72.0/22'];

foreach ($cf_ips as $cidr) {
    [$net, $mask] = explode('/', $cidr);
    if ((ip2long($_SERVER['REMOTE_ADDR'] ?? '0.0.0.0') & (~(-1 << (32 - $mask)))) === (ip2long($net) & (~(-1 << (32 - $mask))))) {
        $_SERVER['REMOTE_ADDR'] = $_SERVER['HTTP_CF_CONNECTING_IP'] ?? $_SERVER['REMOTE_ADDR'];
        break;
    }
}
/**
 * TraderAlign — Front Controller / Router
 * 
 * Handles all incoming requests and routes them to appropriate handlers.
 * Includes session management, database connection, and authentication.
 */

require_once __DIR__ . '/config/config.php';
require_once __DIR__ . '/src/ScopedDb.php';
require_once __DIR__ . '/src/PasswordPolicy.php';
require_once __DIR__ . '/src/RateLimiter.php';
require_once __DIR__ . '/src/Auth.php';
require_once __DIR__ . '/src/PropFirmHelpers.php';
require_once __DIR__ . '/src/RenderHelpers.php';
require_once __DIR__ . '/src/ReportsCoaching.php';
require_once __DIR__ . '/src/AccountPicker.php';
require_once __DIR__ . '/src/CurrencyService.php';
require_once __DIR__ . '/src/ValidationService.php';
require_once __DIR__ . '/src/RiskPanelEngine.php';
require_once __DIR__ . '/src/StrategyMilestoneReview.php';
require_once __DIR__ . '/src/Csrf.php';

// ── Session Configuration ───────────────────────────────
if (session_status() === PHP_SESSION_NONE) {
    session_name(SESSION_NAME);
    session_set_cookie_params([
        'lifetime' => SESSION_LIFETIME,
        'path' => '/',
        'httponly' => true,
        'samesite' => 'Lax',
        'secure' => isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on', // Enable secure in production
    ]);
    session_start();
}

// ── Database Connection ─────────────────────────────────
try {
    $dsn = "mysql:host=" . DB_HOST . ";port=" . DB_PORT . ";dbname=" . DB_NAME . ";charset=utf8mb4";
    $pdo = new PDO($dsn, DB_USER, DB_PASS, [
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES => false,
        PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8mb4",
    ]);
} catch (PDOException $e) {
    error_log("Database connection failed: " . $e->getMessage());
    http_response_code(500);
    die('Service temporarily unavailable. Please try again later.');
}

// ── Authentication ──────────────────────────────────────
$auth = new Auth($pdo);

// Server-side idle timeout (2026-08-19 security fix). Previously the only
// thing limiting how long a session cookie stayed valid was the browser's
// own Max-Age (refreshed by session_start() on every request, so client-
// side it already behaved like a sliding 24h idle window) plus PHP's
// unpredictable session-garbage-collection sweep — nothing on the SERVER
// actually enforced this. A stolen session cookie was valid indefinitely
// as far as the server was concerned. $_SESSION['last_activity_at'] is
// updated on every authenticated request below; if it's ever more than
// SESSION_LIFETIME stale, the session is destroyed server-side and this
// request is treated as logged out (same as if isLoggedIn() had always
// been false), regardless of what the cookie itself claims.
if ($auth->isLoggedIn()) {
    $idleFor = time() - (int)($_SESSION['last_activity_at'] ?? $_SESSION['logged_in_at'] ?? time());
    if ($idleFor > SESSION_LIFETIME) {
        $auth->signOut();
    } elseif ($auth->sessionPredatesPasswordChange()) {
        // 2026-08-19 security fix: this session was established before the
        // user's most recent password change (a change made on a DIFFERENT
        // device/session, since this one's own change handlers already
        // regenerate their own session ID directly). Force this one out too
        // — see Auth::sessionPredatesPasswordChange()'s own docblock.
        $auth->signOut();
    } else {
        $_SESSION['last_activity_at'] = time();
    }
}

$db = null;
if ($auth->isLoggedIn()) {
    $db = new ScopedDb($pdo, $auth->getUserId());
}

// ── Request Parsing ─────────────────────────────────────
$method = $_SERVER['REQUEST_METHOD'];
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);

// Remove base path prefix
$uri = str_replace('/traderalign', '', $uri);

// Normalize URI (remove trailing slash, default to '/')
$uri = rtrim($uri, '/') ?: '/';

// Base directory for file inclusions
$base = __DIR__;

// ── Security Headers ────────────────────────────────────
// Add security headers for all responses
header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header('X-XSS-Protection: 1; mode=block');
header('Referrer-Policy: strict-origin-when-cross-origin');

// Content-Security-Policy + Permissions-Policy (2026-08-19 security fix).
// The allowlist below is the real, complete list of external hosts this
// app loads anything from (confirmed by grepping every public/*.php,
// landing.html, and calculator-source.html for an external http(s) URL) —
// fonts.googleapis.com/fonts.gstatic.com (Google Fonts), cdn.jsdelivr.net
// and cdn.tailwindcss.com (used on dashboard.php/reports.php/
// resend-verification.php). api.resend.com is deliberately NOT here — that
// call happens server-side via PHP curl, never from the browser, so CSP
// (which only governs what the BROWSER is allowed to load/run) has nothing
// to do with it. 'unsafe-inline' is required for both script-src and
// style-src — this app has extensive inline <script>/onclick=/style=
// usage throughout (a real nonce-based rewrite would be a much larger,
// separate change) — so this is deliberately a permissive-but-still-real
// policy: it does NOT stop an inline-injected script from running, but it
// DOES stop the far more common real-world XSS payload shape (loading a
// script from an attacker-controlled external host) dead, and closes off
// object embedding, <base> tag hijacking, and form-hijacking to an
// external host. frame-ancestors 'none' is the modern equivalent of the
// X-Frame-Options: DENY already set above (kept for older browsers).
header("Content-Security-Policy: default-src 'self'; "
    . "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdn.tailwindcss.com; "
    . "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.tailwindcss.com; "
    . "font-src 'self' https://fonts.gstatic.com; "
    . "img-src 'self' data:; "
    . "connect-src 'self'; "
    . "object-src 'none'; "
    . "base-uri 'self'; "
    . "form-action 'self'; "
    . "frame-ancestors 'none'");
header('Permissions-Policy: geolocation=(), microphone=(), camera=(), payment=(), usb=()');

// 2026-08-19 security fix (pre-launch audit finding L-4): HSTS was
// entirely absent. Not added unconditionally — sending it over plain HTTP
// is meaningless (browsers only honor Strict-Transport-Security on a
// response actually delivered over HTTPS) and this app's own local-dev
// environment is plain HTTP, so an unconditional header here would be a
// no-op locally and untested. Gated on the exact same
// `$_SERVER['HTTPS'] === 'on'` check this file's own session-cookie
// `secure` flag already uses a few lines above — the day this app is
// deployed behind real TLS, this activates automatically with no separate
// pre-launch step to remember. 31536000s = 1 year, includeSubDomains
// since this app has no subdomains that need to stay on plain HTTP;
// preload deliberately omitted (submitting to the HSTS preload list is a
// one-way, hard-to-reverse operational decision that belongs with whoever
// owns the real production domain, not something to bake into app code).
if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
    header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
}

// ── Rate Limiting (Basic) ───────────────────────────────
// Simple IP-based rate limiting for API endpoints
$isApiEndpoint = strpos($uri, '/api/') === 0;
if ($isApiEndpoint && in_array($method, ['POST', 'PUT', 'DELETE'])) {
    $rateLimitKey = 'rate_' . ($_SERVER['REMOTE_ADDR'] ?? 'unknown') . '_' . $uri;
    
    // Use session for rate limiting (upgrade to Redis in production)
    if (!isset($_SESSION['rate_limits'])) {
        $_SESSION['rate_limits'] = [];
    }
    
    // Clean old entries
    $_SESSION['rate_limits'] = array_filter($_SESSION['rate_limits'], function($time) {
        return $time > (time() - 60); // 1 minute window
    });
    
    // Check rate limit
    $apiKey = md5($rateLimitKey);
    $attempts = count(array_filter($_SESSION['rate_limits'], function($key) use ($apiKey) {
        return strpos($key, $apiKey) === 0;
    }, ARRAY_FILTER_USE_KEY));
    
    if ($attempts > 30) { // Max 30 requests per minute
        http_response_code(429);
        header('Retry-After: 60');
        die('Too many requests. Please try again later.');
    }
    
    // Add current request timestamp
    $_SESSION['rate_limits'][$apiKey . '_' . microtime(true)] = time();
}

// ── CSRF Protection (centralized, 2026-08-19 security fix) ─────────────
// Applies to every state-changing request across the whole app, API and
// plain form-based public/ page alike — not just a handful of hand-picked
// endpoints. See src/Csrf.php for the two ways a request can carry its
// token (a hidden form field, or the X-CSRF-Token header assets/js/csrf.js
// adds automatically to every same-origin fetch() call from any page that
// includes public/sidebar.php — confirmed to be every authenticated page
// in this app, plus calculator-source.html via its own same-origin-iframe
// read of the parent page's token). The 6 endpoints that already had their
// own hand-rolled equivalent check (sign-in, sign-up, forgot-password,
// reset-password, resend-verification, settings' security section) are
// unaffected — they read the exact same $_SESSION['csrf_token'] /
// $_POST['csrf_token'] pair, so this check and theirs always agree.
if (in_array($method, ['POST', 'PUT', 'DELETE'], true) && !Csrf::verify()) {
    http_response_code(403);
    if ($isApiEndpoint || strpos($_SERVER['HTTP_ACCEPT'] ?? '', 'application/json') !== false) {
        header('Content-Type: application/json');
        echo json_encode([
            'success' => false,
            'error' => 'Invalid or missing security token. Please refresh the page and try again.',
        ]);
    } else {
        header('Content-Type: text/html; charset=UTF-8');
        echo '<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Session expired</title></head>'
            . '<body style="font-family:system-ui,sans-serif;max-width:32rem;margin:4rem auto;padding:0 1.5rem;color:#333">'
            . '<h1 style="font-size:1.5rem">Your session expired</h1>'
            . '<p>This usually happens if a page was left open a while before submitting, or was reloaded from a saved copy. '
            . 'Please go back and try again.</p>'
            . '<p><a href="' . htmlspecialchars(APP_URL) . '">← Back to TraderAlign</a></p>'
            . '</body></html>';
    }
    exit;
}

// ── Route Matching ──────────────────────────────────────
switch (true) {

    // ── Public Pages ────────────────────────────────────
    case $method === 'GET' && $uri === '/':
        readfile(__DIR__ . '/landing.html');
        break;

    // NOTE: no longer a public page as of 2026-08-21 (see the matching
    // .htaccess comment for why this case only just started actually
    // running). This file holds the Calculator's real client-side logic
    // and internal engineering comments — every request now requires a
    // real session before the file is ever read off disk.
    case $method === 'GET' && $uri === '/calculator-source.html':
        $auth->requireAuth();

        // public/calculator.php legitimately embeds this page in a same-
        // origin <iframe>, and its own JS fetches live FX rates from four
        // external providers — both of which the blanket X-Frame-Options:
        // DENY / connect-src 'self' headers set above this switch would
        // break. Override with the exact values the now-dead <Files
        // "calculator-source.html"> block in the root .htaccess used to
        // send back when Apache served this file directly (header()
        // replaces a same-named header already sent, so this safely wins).
        header('X-Frame-Options: SAMEORIGIN');
        header("Content-Security-Policy: default-src 'self'; "
            . "script-src 'self' 'unsafe-inline'; "
            . "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; "
            . "font-src 'self' https://fonts.gstatic.com; "
            . "img-src 'self' data:; "
            . "connect-src 'self' https://api.frankfurter.app https://api.exchangerate-api.com https://www.freeforexapi.com https://cdn.jsdelivr.net; "
            . "object-src 'none'; "
            . "base-uri 'self'; "
            . "form-action 'self'; "
            . "frame-ancestors 'self'");

        readfile(__DIR__ . '/calculator-source.html');
        break;

    // /strategy-source.html route REMOVED 2026-08-08 (Single Source of
    // Truth migration, Wave 6) — the target file never existed on disk
    // (confirmed via direct filesystem check), had zero references
    // anywhere in the app (grepped public/, src/, api/, index.php — the
    // route definition itself was the only mention), and Strategy
    // Builder (public/strategy.php) is fully self-contained, unlike
    // Calculator's real source-file-in-an-iframe split above — there is
    // no live feature this route was serving. First flagged, not yet
    // acted on, in docs/architecture/DEAD_FILE_CLEANUP_2026-08-07.md
    // (which also relocated a same-named "(backup)" file to dead-files/,
    // confirming this was a leftover from a retired split-file
    // architecture, not a live route missing its target). Removed rather
    // than restored, since there was nothing to restore.

    // ── Authentication Pages ────────────────────────────
    case $method === 'GET' && $uri === '/sign-up':
        require $base . '/public/sign-up.php';
        break;

    case $method === 'GET' && $uri === '/sign-in':
        require $base . '/public/sign-in.php';
        break;

    case $method === 'GET' && $uri === '/forgot-password':
        require $base . '/public/forgot-password.php';
        break;

    case $method === 'GET' && $uri === '/pricing':
        require $base . '/public/pricing.php';
        break;

    case $method === 'GET' && $uri === '/privacy-policy':
        require $base . '/public/privacy-policy.php';
        break;

    case $method === 'GET' && $uri === '/terms':
        require $base . '/public/terms.php';
        break;

    case $method === 'GET' && $uri === '/risk-disclosure':
        require $base . '/public/risk-disclosure.php';
        break;

    case $method === 'GET' && $uri === '/refund-policy':
        require $base . '/public/refund-policy.php';
        break;

    // ── Authentication API Endpoints ────────────────────
    case $method === 'POST' && $uri === '/api/auth/sign-up':
        require $base . '/api/auth/sign-up.php';
        break;

    case $method === 'POST' && $uri === '/api/auth/sign-in':
        require $base . '/api/auth/sign-in.php';
        break;

    case $method === 'POST' && $uri === '/api/auth/forgot-password':
        require $base . '/api/auth/forgot-password.php';
        break;

    // POST-only as of 2026-08-19 (security audit fix) — previously matched
    // any method, so a plain <img>/<a href> pointed here from any page
    // could log a visitor out via a simple GET request (SameSite=Lax
    // cookies don't block that, only cross-site POST). sidebar.php's own
    // Sign Out link now triggers a real POST via fetch() instead of a
    // plain navigation.
    case $method === 'POST' && $uri === '/api/auth/sign-out':
        require $base . '/api/auth/sign-out.php';
        break;

    // ── Email Verification ──────────────────────────────
    case $uri === '/verify' || strpos($uri, '/verify?') === 0:
        require $base . '/public/verify.php';
        break;

    case $uri === '/resend-verification':
        require $base . '/public/resend-verification.php';
        break;

    // ── Password Reset ──────────────────────────────────
    case $uri === '/reset-password':
        require $base . '/public/reset-password.php';
        break;

    // ── Dashboard & Reports ─────────────────────────────
    case $uri === '/dashboard':
        require $base . '/public/dashboard.php';
        break;

    case $method === 'GET' && $uri === '/reports':
        require $base . '/public/reports.php';
        break;

    case $method === 'GET' && $uri === '/trader-profile':
        require $base . '/public/trader-profile.php';
        break;

    // ── MT5 Reconciliation ──────────────────────────────
    case $method === 'GET' && $uri === '/mt5-upload':
        require $base . '/public/mt5-upload.php';
        break;

    case $uri === '/mt5-resolve':
        require $base . '/public/mt5-resolve.php';
        break;

    case $method === 'POST' && $uri === '/api/mt5/parse':
        require $base . '/api/mt5/parse.php';
        break;

    case $method === 'POST' && $uri === '/api/mt5/reconcile':
        require $base . '/api/mt5/reconcile.php';
        break;

    // ── Colex AI Interface ────────────────────────────
    case $method === 'GET' && $uri === '/colex':
        // Ensure user is authenticated
        if (!$auth->isLoggedIn()) {
            http_response_code(302);
            header('Location: /traderalign/sign-in');
            exit;
        }
        require $base . '/public/colex.php';
        break;

    case $method === 'POST' && $uri === '/api/colex/chat':
        // Ensure user is authenticated
        if (!$auth->isLoggedIn()) {
            http_response_code(401);
            header('Content-Type: application/json');
            echo json_encode([
                'success' => false,
                'error' => 'Authentication required',
                'message' => 'Please sign in to access Colex AI'
            ]);
            exit;
        }
        require $base . '/api/colex/chat.php';
        break;

    case $method === 'GET' && $uri === '/api/colex/chat':
        // GET requests for loading messages or conversations
        if (!$auth->isLoggedIn()) {
            http_response_code(401);
            header('Content-Type: application/json');
            echo json_encode([
                'success' => false,
                'error' => 'Authentication required',
                'message' => 'Please sign in to access Colex AI'
            ]);
            exit;
        }
        require $base . '/api/colex/chat.php';
        break;

    case $method === 'GET' && $uri === '/api/colex/stats':
        // Ensure user is authenticated
        if (!$auth->isLoggedIn()) {
            http_response_code(401);
            header('Content-Type: application/json');
            echo json_encode([
                'success' => false,
                'error' => 'Authentication required',
                'message' => 'Please sign in to access Colex AI'
            ]);
            exit;
        }
        require $base . '/api/colex/stats.php';
        break;

    case $method === 'POST' && $uri === '/api/colex/assist':
        // Ensure user is authenticated
        if (!$auth->isLoggedIn()) {
            http_response_code(401);
            header('Content-Type: application/json');
            echo json_encode([
                'success' => false,
                'error' => 'Authentication required',
                'message' => 'Please sign in to access Colex AI'
            ]);
            exit;
        }
        require $base . '/api/colex/assist.php';
        break;

    case $method === 'POST' && $uri === '/api/colex/upload-screenshot':
        // Ensure user is authenticated
        if (!$auth->isLoggedIn()) {
            http_response_code(401);
            header('Content-Type: application/json');
            echo json_encode([
                'success' => false,
                'error' => 'Authentication required',
                'message' => 'Please sign in to upload screenshots'
            ]);
            exit;
        }
        require $base . '/api/colex/upload-screenshot.php';
        break;

    case $method === 'GET' && $uri === '/api/colex/view-screenshot':
        require $base . '/api/colex/view-screenshot.php';
        break;

    case $method === 'POST' && $uri === '/api/colex/delete-conversation':
        // Ensure user is authenticated
        if (!$auth->isLoggedIn()) {
            http_response_code(401);
            header('Content-Type: application/json');
            echo json_encode([
                'success' => false,
                'error' => 'Authentication required',
                'message' => 'Please sign in to delete conversations'
            ]);
            exit;
        }
        require $base . '/api/colex/delete-conversation.php';
        break;

    case $method === 'POST' && $uri === '/api/colex/pin-conversation':
        // Ensure user is authenticated
        if (!$auth->isLoggedIn()) {
            http_response_code(401);
            header('Content-Type: application/json');
            echo json_encode([
                'success' => false,
                'error' => 'Authentication required',
                'message' => 'Please sign in to pin conversations'
            ]);
            exit;
        }
        require $base . '/api/colex/pin-conversation.php';
        break;

    // ── Trading Tools ───────────────────────────────────
    case $uri === '/journal':
        require $base . '/public/journal.php';
        break;

    // /checklist no longer exists as a standalone page (2026-08-01
    // Trading Center / Strategies redesign) — its responsibilities are now
    // split between Trading Center (account selection) and Strategies
    // (strategy selection + checklist). Old bookmarks/links redirect
    // rather than 404ing, carrying account_id/strategy_id through.
    case $method === 'GET' && $uri === '/checklist':
        $qs = $_GET;
        header('Location: /traderalign/strategies' . (empty($qs) ? '' : ('?' . http_build_query($qs))));
        exit;

    case $method === 'GET' && $uri === '/strategies':
        require $base . '/public/strategies.php';
        break;

    case $method === 'GET' && $uri === '/trading-center':
        require $base . '/public/trading-center.php';
        break;

    case $method === 'GET' && $uri === '/settings':
        require $base . '/public/settings.php';
        break;

    case $method === 'POST' && $uri === '/api/settings/update':
        require $base . '/api/settings/update.php';
        break;

    case $method === 'GET' && $uri === '/api/settings/defaults':
        require $base . '/api/settings/defaults.php';
        break;

    case $method === 'POST' && $uri === '/api/settings/sync-timezone':
        require $base . '/api/settings/sync-timezone.php';
        break;

    case $method === 'GET' && $uri === '/calculator':
        require $base . '/public/calculator.php';
        break;

    case $method === 'GET' && $uri === '/strategy':
        require $base . '/public/strategy.php';
        break;

    // ── Drafts ──────────────────────────────────────────
    case $method === 'GET' && $uri === '/drafts':
        require $base . '/public/drafts.php';
        break;

    case $method === 'POST' && $uri === '/drafts':
        require $base . '/public/drafts.php';
        break;

    // ── Onboarding ──────────────────────────────────────
    case $method === 'GET' && $uri === '/onboarding':
        require $base . '/public/onboarding.php';
        break;

    case $method === 'POST' && $uri === '/api/onboarding/step1':
        require $base . '/api/onboarding/step1.php';
        break;

    case $method === 'POST' && $uri === '/api/onboarding/step2':
        require $base . '/api/onboarding/step2.php';
        break;

    case $method === 'POST' && $uri === '/api/onboarding/step3':
        require $base . '/api/onboarding/step3.php';
        break;

    // ── Onboarding Journey Selection (GET + POST) ───────
    case $uri === '/onboarding-stage3':
        require $base . '/public/onboarding-stage3.php';
        break;

    // ── Validation Strategy (GET + POST) ────────────────
    case $uri === '/strategy-validation':
        require $base . '/public/strategy-validation.php';
        break;

    // ── Validation Journal ──────────────────────────────
    case $method === 'GET' && $uri === '/journal-validation':
        require $base . '/public/journal-validation.php';
        break;

    // ── Learning Center ─────────────────────────────────
    case $method === 'GET' && $uri === '/learning-center':
        require $base . '/public/learning-center.php';
        break;

    // ── Validation Migration API ────────────────────────
    // /api/migrate/promote-validation is now a thin, backward-compatible
    // alias — see that file's own header comment. /api/validation/graduate
    // is the real, single graduation endpoint (ValidationService::graduate()).
    case $method === 'POST' && $uri === '/api/migrate/promote-validation':
        require $base . '/api/migrate/promote-validation.php';
        break;

    case $method === 'POST' && $uri === '/api/validation/graduate':
        require $base . '/api/validation/graduate.php';
        break;

    case $method === 'POST' && $uri === '/api/validation/return-to-main':
        require $base . '/api/validation/return-to-main.php';
        break;

    // ── Checklist & Strategy APIs ───────────────────────
    case $method === 'POST' && $uri === '/api/checklist/save':
        require $base . '/api/checklist/save.php';
        break;

    case $method === 'POST' && $uri === '/api/strategy/save':
        require $base . '/api/strategy/save.php';
        break;

    case $method === 'GET' && $uri === '/api/strategy/load':
        require $base . '/api/strategy/load.php';
        break;

    case $method === 'GET' && $uri === '/api/strategy/health':
        require $base . '/api/strategy/health.php';
        break;

    case $method === 'GET' && $uri === '/api/strategy/changes':
        require $base . '/api/strategy/changes.php';
        break;

    case $method === 'POST' && $uri === '/api/trades/draft':
        require $base . '/api/trades/draft.php';
        break;

    case $method === 'POST' && $uri === '/api/trades/validate':
        require $base . '/api/trades/validate.php';
        break;

    case $method === 'POST' && $uri === '/api/calculator/compute':
        require $base . '/api/calculator/compute.php';
        break;

    case $method === 'GET' && $uri === '/api/calculator/instrument-hint':
        require $base . '/api/calculator/instrument-hint.php';
        break;

    case $method === 'GET' && $uri === '/api/journal/trades-more':
        require $base . '/api/journal/trades-more.php';
        break;

    case $method === 'POST' && $uri === '/api/journal/update-note':
        require $base . '/api/journal/update-note.php';
        break;

    case $method === 'POST' && $uri === '/api/journal/save-goal':
        require $base . '/api/journal/save-goal.php';
        break;

    case $method === 'POST' && $uri === '/api/journal/clear-streak-review':
        require $base . '/api/journal/clear-streak-review.php';
        break;

    case $method === 'POST' && $uri === '/api/journal/update-account-meta':
        require $base . '/api/journal/update-account-meta.php';
        break;

    case $method === 'POST' && $uri === '/api/journal/reveal-account-secret':
        require $base . '/api/journal/reveal-account-secret.php';
        break;

    case $method === 'POST' && $uri === '/api/journal/upload-screenshot':
        require $base . '/api/journal/upload-screenshot.php';
        break;

    case $method === 'GET' && $uri === '/api/journal/trade-screenshots':
        require $base . '/api/journal/trade-screenshots.php';
        break;

    case $method === 'POST' && $uri === '/api/journal/delete-screenshot':
        require $base . '/api/journal/delete-screenshot.php';
        break;

    case $method === 'GET' && $uri === '/api/journal/view-screenshot':
        require $base . '/api/journal/view-screenshot.php';
        break;

    // ── Account APIs ────────────────────────────────────
    case $method === 'POST' && $uri === '/api/accounts/create':
        require $base . '/api/accounts/create.php';
        break;

    case $method === 'POST' && $uri === '/api/accounts/close':
        require $base . '/api/accounts/close.php';
        break;

    case $method === 'POST' && $uri === '/api/accounts/transaction':
        require $base . '/api/accounts/transaction.php';
        break;

    case $method === 'GET' && $uri === '/api/accounts/eligible-for-promotion':
        require $base . '/api/accounts/eligible-for-promotion.php';
        break;

    case $method === 'GET' && $uri === '/api/account/state':
        require $base . '/api/account/state.php';
        break;

    case $method === 'GET' && $uri === '/add-account':
        require $base . '/public/add-account.php';
        break;

    case $method === 'GET' && $uri === '/add-prop-account':
        require $base . '/public/add-prop-account.php';
        break;

    case $method === 'GET' && $uri === '/add-live-account':
        require $base . '/public/add-live-account.php';
        break;

    case $method === 'GET' && $uri === '/manage-live-accounts':
        require $base . '/public/manage-live-accounts.php';
        break;

    // ── 404 Handler ─────────────────────────────────────
    default:
        http_response_code(404);
        
        // Check if request expects JSON
        if (strpos($_SERVER['HTTP_ACCEPT'] ?? '', 'application/json') !== false) {
            header('Content-Type: application/json');
            echo json_encode([
                'error' => true,
                'message' => 'Endpoint not found',
                'code' => 404
            ]);
            break;
        }
        
        // HTML 404 response
        echo '<!DOCTYPE html>';
        echo '<html lang="en">';
        echo '<head>';
        echo '<meta charset="UTF-8">';
        echo '<meta name="viewport" content="width=device-width, initial-scale=1.0">';
        echo '<meta name="robots" content="noindex, nofollow">';
        echo '<title>404 - Page Not Found | TraderAlign</title>';
        echo '<link rel="stylesheet" href="/traderalign/assets/tokens.css">';
        echo '<link rel="icon" type="image/png" sizes="32x32" href="/traderalign/assets/favicon/favicon-32.png">';
        echo '<link rel="icon" type="image/png" sizes="16x16" href="/traderalign/assets/favicon/favicon-16.png">';
        echo '<link rel="apple-touch-icon" sizes="180x180" href="/traderalign/assets/favicon/apple-touch-icon.png">';
        echo '<link rel="shortcut icon" href="/traderalign/assets/favicon/favicon.ico">';
        echo '<link rel="stylesheet" href="/traderalign/assets/css/brand.css">';
        echo '<style>';
        echo 'body{display:flex;align-items:center;justify-content:center;min-height:100vh;text-align:center;font-family:Inter,system-ui,sans-serif;background:var(--background);color:var(--foreground)}';
        echo '.error-container{max-width:400px;padding:2rem;display:flex;flex-direction:column;align-items:center}';
        echo 'h1{font-size:4rem;font-weight:800;margin-bottom:.5rem;background:linear-gradient(135deg,var(--primary),var(--warning));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}';
        echo 'p{color:var(--foreground-muted);margin-top:.5rem;font-size:1.1rem;line-height:1.5}';
        echo '.sub{font-size:.9rem;color:var(--foreground-dim)}';
        echo 'a{color:var(--primary);margin-top:1.5rem;display:inline-block;text-decoration:none;font-weight:500;transition:opacity 0.2s ease}';
        echo 'a:hover{opacity:0.8}';
        echo '</style>';
        echo '</head>';
        echo '<body>';
        echo '<div class="error-container">';
        echo '<img src="/traderalign/images/logo.png" alt="TraderAlign" class="ta-logo ta-logo--2xl" style="margin-bottom:1.5rem">';
        echo '<h1>404</h1>';
        echo '<p>Page not found</p>';
        echo '<p class="sub">The page you\'re looking for doesn\'t exist or has been moved.</p>';
        echo '<a href="/traderalign/">← Back to home</a>';
        echo '</div>';
        echo '</body>';
        echo '</html>';
        break;
}

// ── Cleanup ─────────────────────────────────────────────
// Log slow requests for performance monitoring
if (isset($_SERVER['REQUEST_TIME_FLOAT'])) {
    $executionTime = microtime(true) - $_SERVER['REQUEST_TIME_FLOAT'];
    if ($executionTime > 1.0) { // Log requests taking more than 1 second
        error_log("Slow request: {$method} {$uri} took " . round($executionTime, 2) . "s");
    }
}