<?php
error_reporting(E_ALL);
ini_set('display_errors', 0);
ini_set('log_errors', 1);

// Needed for AnalyticsEngine::getChallengeMetrics() — the real breach/pass
// boundary math (2026-07-28 Reports finishing pass, item 4/5 regression fix).
// The Account Status step's "mathematically breached/passed" pre-check
// previously recomputed its own approximate breach/pass thresholds from
// percentage fields snapshotted on the trade at capture time
// (account_size_at_entry * (1 - phase_max_dd_pct/100), etc.) — an independent
// approximation of the real math dashboard.php's resolve_trade_id handler
// actually uses to cap challenge_pnl. Now both read from the exact same
// source (getChallengeMetrics()), so the pre-check can never disagree with
// what the server will actually do.
require_once __DIR__ . '/../api/analytics/engine.php';
require_once __DIR__ . '/../src/Csrf.php';
require_once __DIR__ . '/../src/StrategyPicker.php';

$userId = $auth->requireAuth();

// ── Personalization (2026-08-01 Drafts UX review, item 11) — same lookup
// pattern as trader-profile.php: real display_name when set, honest
// 'Trader' fallback for legacy/incomplete-onboarding accounts, never a
// hardcoded "Dear Trader"-style placeholder pretending to be a real name.
$displayName = 'Trader';
try {
    $profileRow = $db->selectById('user_profiles', $userId, 'user_id');
    if ($profileRow && !empty($profileRow['display_name'])) {
        $displayName = $profileRow['display_name'];
    }
} catch (\Throwable $e) {
    error_log("drafts: failed to read display_name: " . $e->getMessage());
}

// ── Strategy Context (Dashboard is the single source of truth) ──────
// Validation Workflow isolation: this query previously had NO is_validation
// filter at all — a real leak (a validation strategy could appear in this
// filter and show its validation-trade drafts mixed into the main Drafts
// page). Now workflow-aware, matching the same is_validation scoping every
// other strategy-selection surface uses (src/StrategyPicker.php,
// public/strategy.php): main workflow sees only real strategies, the
// validation workflow sees only its own validation strategy/strategies.
$workflowState = ValidationService::getWorkflowState($userId, $db);
$allStrategies = [];
try {
    $validationCond = $workflowState === 'validation'
        ? 'is_validation = 1'
        : '(is_validation = 0 OR is_validation IS NULL)';
    $stmt = $pdo->prepare("SELECT id, name, current_version_id FROM strategies WHERE user_id = :uid AND is_active = 1 AND deleted_at IS NULL AND {$validationCond} ORDER BY created_at DESC");
    $stmt->execute([':uid' => $userId]);
    $allStrategies = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (\Throwable $e) {
    error_log("Failed to fetch strategies for filter: " . $e->getMessage());
}

// 2026-08-20: carry the active strategy forward automatically. Real bug
// reported directly: a trade saved against a strategy could still leave
// Drafts showing a different one, forcing a manual re-pick via Trading
// Center/Strategies just to find the trade. Priority order: an explicit
// ?strategy_id= on THIS request (also becomes the new system-wide active
// strategy), then the real, persisted pointer (spGetActiveStrategyId(),
// src/StrategyPicker.php, which replaces the old $_SESSION['active_
// strategy_id'] read that nothing in the codebase ever wrote), then most
// recently created, as a last resort.
$strategyId = $_GET['strategy_id'] ?? null;
$selectedStrategy = null;
$selectedStrategyName = null;
$selectedVersionId = null;

if ($strategyId) {
    foreach ($allStrategies as $strat) {
        if ((string)$strat['id'] === (string)$strategyId) {
            $selectedStrategy = $strat['id'];
            $selectedStrategyName = $strat['name'];
            $selectedVersionId = $strat['current_version_id'];
            break;
        }
    }
    if ($selectedStrategy) {
        spRecordActiveStrategy($userId, $selectedStrategy, $db, $workflowState);
    }
}

if (!$selectedStrategy) {
    $draftsActiveStrategyId = spGetActiveStrategyId($userId, $db, $workflowState);
    if ($draftsActiveStrategyId) {
        foreach ($allStrategies as $strat) {
            if ((string)$strat['id'] === (string)$draftsActiveStrategyId) {
                $selectedStrategy = $strat['id'];
                $selectedStrategyName = $strat['name'];
                $selectedVersionId = $strat['current_version_id'];
                break;
            }
        }
    }
}

// If no valid strategy found, default to the first active strategy
if (!$selectedStrategy && !empty($allStrategies)) {
    $selectedStrategy = $allStrategies[0]['id'];
    $selectedStrategyName = $allStrategies[0]['name'];
    $selectedVersionId = $allStrategies[0]['current_version_id'];
}

// Plain strategy name — no "(vN)" (TRADERALIGN STRATEGY BUSINESS RULES
// redesign, 2026-08-11, §13: version numbers are internal bookkeeping,
// never user-facing).
$strategyDisplayName = $selectedStrategyName ?? 'All Strategies';

$actionError = null;

// 2026-08-12 workflow-isolation audit: neither mutation below checked the
// trade's own is_validation flag against the current workflow — ScopedDb's
// selectById() already scopes by user_id (so this could never touch another
// USER's trade), but nothing stopped a crafted POST from confirming/
// cancelling a trade that belongs to the OTHER workflow (e.g. a validating
// user's request touching one of their own main-workflow trades). Not
// reachable through normal use of this page (a trade from the wrong
// workflow is never rendered into a form here to click), but fixed as
// defense-in-depth, same is_validation-vs-workflow_state check used
// throughout this file's own trade-fetching queries above.
function draftsTradeMatchesWorkflow(?array $trade, string $workflowState): bool {
    if (!$trade) return false;
    $isValidationTrade = !empty($trade['is_validation']);
    return $workflowState === 'validation' ? $isValidationTrade : !$isValidationTrade;
}

// Confirm Live: draft -> open, records opened_at
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['confirm_live_id'])) {
    $tradeId = $_POST['confirm_live_id'];
    $trade = $db->selectById('trades', $tradeId);
    if (!draftsTradeMatchesWorkflow($trade, $workflowState)) { $trade = null; }
    if ($trade && $trade['status'] === 'draft') {
        try {
            $stmt = $pdo->prepare("UPDATE trades SET status = 'open', opened_at = NOW() WHERE id = :id AND user_id = :uid");
            $stmt->execute([':id' => $tradeId, ':uid' => $userId]);
        } catch (\Throwable $e) {
            $actionError = $e->getMessage();
        }
    }
}

// Cancel Trade: draft|open -> cancelled. This never touches balance or
// lifecycle_state — it's for trades that should never have counted at all
// (entered in error, never actually executed), not a way to avoid recording
// a real loss. Extended from draft-only to also cover 'open' (2026-08-01
// Drafts UX review, item 10) — Won/Lost/Cancel are now presented together
// wherever a trade needs resolving, instead of Cancel only existing for
// still-pending drafts.
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['cancel_trade_id'])) {
    $tradeId = $_POST['cancel_trade_id'];
    $trade = $db->selectById('trades', $tradeId);
    if (!draftsTradeMatchesWorkflow($trade, $workflowState)) { $trade = null; }
    if ($trade && in_array($trade['status'], ['draft', 'open'], true)) {
        try {
            $reason = $trade['status'] === 'open' ? 'entered_in_error' : 'did_not_execute';
            $stmt = $pdo->prepare("UPDATE trades SET status = 'cancelled', cancellation_reason = :reason WHERE id = :id AND user_id = :uid");
            $stmt->execute([':id' => $tradeId, ':uid' => $userId, ':reason' => $reason]);
        } catch (\Throwable $e) {
            $actionError = $e->getMessage();
        }
    }
}

// ── Fetch draft/open trades with strategy filter ───────────
// Only fetch outcome rows (is_intent = 0), plus legacy rows (is_intent IS NULL)
if ($selectedVersionId) {
    try {
        $stmt = $pdo->prepare(
            "SELECT * FROM trades 
             WHERE user_id = :uid 
               AND status IN ('draft','open') 
               AND deleted_at IS NULL 
               AND (is_intent = 0 OR is_intent IS NULL)
               AND strategy_version_id = :svid
             ORDER BY captured_at DESC 
             LIMIT 100"
        );
        $stmt->execute([':uid' => $userId, ':svid' => $selectedVersionId]);
        $recentDrafts = $stmt->fetchAll(PDO::FETCH_ASSOC);
    } catch (\Throwable $e) {
        error_log("Drafts scoped query error: " . $e->getMessage());
        $recentDrafts = [];
    }
} else {
    // No strategy selected (e.g. the user has zero active strategies of
    // the current workflow's type yet) — still must not blend validation
    // and main-workflow drafts together. Defense-in-depth alongside the
    // $allStrategies scoping above, in case a draft's own strategy_version_id
    // ever points somewhere $allStrategies wouldn't (e.g. a since-archived
    // strategy version).
    try {
        $validationCond = $workflowState === 'validation'
            ? 'is_validation = 1'
            : '(is_validation = 0 OR is_validation IS NULL)';
        $stmt = $pdo->prepare(
            "SELECT * FROM trades
             WHERE user_id = :uid
               AND status IN ('draft','open')
               AND deleted_at IS NULL
               AND (is_intent = 0 OR is_intent IS NULL)
               AND {$validationCond}
             ORDER BY captured_at DESC
             LIMIT 100"
        );
        $stmt->execute([':uid' => $userId]);
        $recentDrafts = $stmt->fetchAll(PDO::FETCH_ASSOC);
    } catch (\Throwable $e) {
        error_log("Drafts query error: " . $e->getMessage());
        $recentDrafts = [];
    }
}

// ── Draft identity: stable ordinal numbering (2026-08-20 Drafts UX
// refinement) ─────────────────────────────────────────────────────────
// This is the fix for the core problem reported: several near-identical
// drafts (same instrument/direction/setup, saved seconds apart) were
// visually indistinguishable. Every draft/open trade now gets a
// permanent-feeling "Draft #N" identity, numbered by real creation order
// (captured_at ASC) across every trade currently fetched for this
// strategy — #1 is always the oldest, and a trade's number never changes
// just because the page is re-sorted or filtered. Purely a display-layer
// computation over data already fetched above — no new query, no schema
// change, no effect on the actual draft/resolve workflow below.
$draftNumByTradeId = [];
$orderedByCreation = $recentDrafts;
usort($orderedByCreation, fn($a, $b) => strtotime($a['captured_at'] ?? 'now') - strtotime($b['captured_at'] ?? 'now'));
foreach ($orderedByCreation as $i => $t) {
    $draftNumByTradeId[$t['id']] = $i + 1;
}

// ── Change 1: Group drafts by status + urgency system ───────────────
$draftStrategy = null;
$draftTradingStyle = 'day_trader';
if (!empty($recentDrafts)) {
    $firstDraft = $recentDrafts[0];
    if (!empty($firstDraft['strategy_version_id'])) {
        $sv = $db->selectById('strategy_versions', $firstDraft['strategy_version_id']);
        if ($sv) {
            $strategyRow = $db->selectById('strategies', $sv['strategy_id']);
            if ($strategyRow) {
                $draftTradingStyle = strtolower(str_replace(' ', '_', $strategyRow['trading_style'] ?? 'day_trader'));
            }
        }
    }
}

// Wave 6 (Single Source of Truth migration §Consolidated #9): keys were
// 'swing_trader'/'position_trader' — $draftTradingStyle above is derived
// from strategies.trading_style directly (real confirmed values: scalper/
// day_trader/swing/position, per CLAUDE.md's schema docs — never
// 'swing_trader'/'position_trader'), so a swing or position trader's
// drafts could never match either key and always silently fell through
// to the '?? $urgencyThresholds['day_trader']' fallback below — a swing
// trader's draft was flagged "urgent" after 12 hours (day_trader's
// threshold) instead of the correct 240 hours, a real, live, confirmed
// wrong-urgency-signal bug. Keys renamed to match the real data source.
$urgencyThresholds = [
    'scalper'  => ['nudge' => 0.5,  'warning' => 1,   'urgent' => 4],
    'day_trader' => ['nudge' => 3,    'warning' => 6,   'urgent' => 12],
    'swing'    => ['nudge' => 48,   'warning' => 120, 'urgent' => 240],
    'position' => ['nudge' => 720,  'warning' => 1440,'urgent' => 2160],
];
$thresholds = $urgencyThresholds[$draftTradingStyle] ?? $urgencyThresholds['day_trader'];

// trades.confirmed_at does not exist anywhere on this schema (confirmed
// against the live table) — the previous `?? $trade['confirmed_at']`
// fallback chains always silently resolved straight to captured_at, and
// the 'Resolve - Mark Outcome' group below could never receive a trade
// (its only entry condition depended on that same nonexistent column).
// Simplified to what the real data actually supports: captured_at is the
// one genuine "since" signal for a still-pending draft.
function getDraftUrgency(array $trade, array $thresholds): string {
    $since = $trade['captured_at'] ?? null;
    if (!$since) return 'none';
    $hoursOpen = (time() - strtotime($since)) / 3600;
    if ($hoursOpen >= $thresholds['urgent'])  return 'urgent';
    if ($hoursOpen >= $thresholds['warning']) return 'warning';
    if ($hoursOpen >= $thresholds['nudge'])   return 'nudge';
    return 'none';
}

// ── Default sort: "Recently Updated" first (2026-08-20 Drafts UX
// refinement, item 4) ────────────────────────────────────────────────
// "Recently updated" is built entirely from real, existing columns —
// never fabricated: a trade's most recent real activity is opened_at
// (when it was confirmed live) if it has one, else its own creation
// time. For a still-pending draft with no activity beyond being saved,
// this is identical to sorting by creation time, which is exactly
// correct — nothing has happened to it since. A small sort control lets
// a trader switch to Newest/Oldest Created instead.
$sortMode = $_GET['sort'] ?? 'recent';
if (!in_array($sortMode, ['recent', 'newest', 'oldest'], true)) $sortMode = 'recent';
function draftSortActivityKey(array $t): int {
    $activity = $t['opened_at'] ?? $t['captured_at'] ?? null;
    return $activity ? strtotime($activity) : 0;
}
function sortDraftGroup(array &$g, string $sortMode): void {
    switch ($sortMode) {
        case 'newest':
            usort($g, fn($a, $b) => strtotime($b['captured_at'] ?? 'now') - strtotime($a['captured_at'] ?? 'now'));
            break;
        case 'oldest':
            usort($g, fn($a, $b) => strtotime($a['captured_at'] ?? 'now') - strtotime($b['captured_at'] ?? 'now'));
            break;
        case 'recent':
        default:
            usort($g, fn($a, $b) => draftSortActivityKey($b) - draftSortActivityKey($a));
            break;
    }
}

$groupOpen    = [];
$groupPending = [];

foreach ($recentDrafts as $trade) {
    $trade['_urgency'] = getDraftUrgency($trade, $thresholds);
    $trade['_hours_open'] = !empty($trade['captured_at']) ? (time() - strtotime($trade['captured_at'])) / 3600 : 0;
    $trade['_draft_num'] = $draftNumByTradeId[$trade['id']] ?? 0;

    if ($trade['status'] === 'open') {
        $groupOpen[] = $trade;
    } else {
        // Every non-open row fetched here is status='draft' by construction
        // (the query only fetches status IN ('draft','open')) — the old
        // three-way split's third bucket ('Resolve - Mark Outcome') could
        // never actually receive a trade, since it depended on the
        // nonexistent confirmed_at column; removed rather than left as
        // dead, always-empty code.
        $groupPending[] = $trade;
    }
}

sortDraftGroup($groupOpen, $sortMode);
sortDraftGroup($groupPending, $sortMode);

$urgentCount = count(array_filter(
    array_merge($groupOpen, $groupPending),
    fn($t) => $t['_urgency'] === 'urgent'
));

// Honest two-sided summary: "potential" (sum of each trade's own planned
// max_potential_profit, falling back to projected_pnl — same field priority
// used per-card below) and "at_risk" (sum of max_potential_loss). Previously
// this summed only projected_pnl, which made a group of purely open, still-
// undecided positions display as one large "+" number that read like a real
// running P&L total — it was never that; there is no live price feed in this
// app, so there is no such thing as real floating P&L to show here. Showing
// both sides together, honestly labeled as planned outcomes, is the fix.
function groupSummary(array $trades): array {
    $potential = array_sum(array_map(fn($t) => (float)($t['max_potential_profit'] ?? $t['projected_pnl'] ?? 0), $trades));
    $atRisk    = array_sum(array_map(fn($t) => (float)($t['max_potential_loss'] ?? $t['risk_per_position'] ?? 0), $trades));
    return ['count' => count($trades), 'potential' => $potential, 'at_risk' => abs($atRisk)];
}
$summaryOpen    = groupSummary($groupOpen);
$summaryPending = groupSummary($groupPending);

/**
 * SUPERSEDED 2026-08-01 (Drafts UX review) — this "humanize the calculator's
 * own opinion text" approach was the review's single biggest complaint: the
 * calculator speaks *before* a trade, in encouraging second person ("Stay
 * with the plan", "This is what a professional trade looks like") — that
 * voice is correct there, but Drafts documents trades *after* they were
 * opened, and re-displaying pre-trade praise here read as flattery with no
 * evidence behind it. Drafts now builds its own past-tense, fact-only
 * sentences directly from the trade's stored numbers — see
 * buildEntryFacts() below — instead of reformatting trades.warnings_fired.
 * Left in place, unused, per the additive-only rule (nothing currently
 * calls this).
 */
function humanizeInsight(string $raw): array {
    $sep = strpos($raw, ': ');
    $title = trim($sep !== false ? substr($raw, 0, $sep) : $raw);
    $body  = trim($sep !== false ? substr($raw, $sep + 2) : '');

    static $map = [
        'SOLID RISK SIZING'             => ['Solid risk sizing', 'positive', '✓'],
        'GOOD SETUP'                    => ['Good setup', 'positive', '✓'],
        'MARGIN COMFORTABLE'            => ['Margin is comfortable', 'positive', '✓'],
        'CLEAN SETUP'                   => ['Clean, professional setup', 'positive', '✓'],
        'PRICES SNAPPED TO BROKER TICK' => ['Prices snapped to the broker tick', 'info', 'ℹ'],
        'SIZING UP'                     => ['Sizing up a little', 'caution', '⚠'],
        'ABOVE STANDARD'                => ['Risk sits above the usual standard', 'caution', '⚠'],
        'SLIGHTLY ABOVE PLAN'           => ['Slightly above your usual plan', 'caution', '⚠'],
        'STOP MAY BE TOO TIGHT'         => ['Stop may be a little tight', 'caution', '⚠'],
        'WATCH YOUR MARGIN'             => ['Keep an eye on margin', 'caution', '⚠'],
        'REWARD NEEDS WORK'             => ['Reward could be stronger', 'caution', '⚠'],
        'ACCOUNT SIZE, WORTH NOTING'    => ['Account size worth noting', 'caution', '⚠'],
        'KNOW YOUR NUMBERS'             => ['Worth knowing your numbers here', 'negative', '⚠'],
        'LOT SIZE WAY ABOVE PLAN'       => ['Lot size well above plan', 'negative', '⚠'],
        'ABOVE RECOMMENDED SIZE'        => ['Above the recommended size', 'negative', '⚠'],
        'BUFFER NEARLY GONE'            => ['Drawdown buffer nearly gone', 'negative', '⚠'],
    ];

    if (isset($map[$title])) {
        [$lead, $tone, $icon] = $map[$title];
    } else {
        $lead = $title !== '' ? ucfirst(strtolower($title)) : 'Note';
        $tone = 'info';
        $icon = 'ℹ';
    }

    return ['lead' => $lead, 'body' => $body, 'tone' => $tone, 'icon' => $icon];
}

/**
 * Past-tense, evidence-only summary of what was actually true when this
 * trade was opened — built directly from the trade's own stored numbers
 * (risk_pct, risk_reward_tp1, margin_used_pct, trades_remaining_buffer),
 * never from the calculator's own pre-trade opinion text. Each sentence is
 * included only when its underlying field is genuinely present — nothing
 * here is invented to fill space, and nothing here tells the trader what to
 * do next (that's the calculator's job, before the trade; this is history).
 * $strategyVersion (nullable) is only used to state whether the trade's own
 * numbers met the strategy's own declared risk/RR — a fact comparison, not
 * an opinion.
 */
function buildEntryFacts(array $trade, ?array $strategyVersion): array {
    $facts = [];

    $riskPct = isset($trade['risk_pct']) ? (float)$trade['risk_pct'] : null;
    $riskAmt = isset($trade['risk_per_position']) ? (float)$trade['risk_per_position'] : null;
    if ($riskPct !== null && $riskAmt !== null && $riskPct > 0) {
        $tone = 'info';
        $text = 'This trade was opened with a planned risk of ' . rtrim(rtrim(number_format($riskPct, 2), '0'), '.') . '% ($' . number_format($riskAmt, 2) . ').';
        $declaredMax = $strategyVersion['max_risk_per_trade_pct'] ?? null;
        if ($declaredMax !== null && (float)$declaredMax > 0 && $riskPct > (float)$declaredMax * 1.05) {
            $tone = 'caution';
            $text .= ' This exceeded the strategy\'s declared limit of ' . rtrim(rtrim(number_format((float)$declaredMax, 2), '0'), '.') . '%.';
        }
        $facts[] = ['text' => $text, 'tone' => $tone];
    }

    $rr = isset($trade['risk_reward_tp1']) ? (float)$trade['risk_reward_tp1'] : null;
    if ($rr !== null && $rr > 0) {
        $tone = 'info';
        $text = 'The planned reward-to-risk ratio was 1 : ' . number_format($rr, 2) . '.';
        $declaredRr = $strategyVersion['backtested_avg_rr'] ?? null;
        if ($declaredRr !== null && (float)$declaredRr > 0) {
            if ($rr >= (float)$declaredRr) {
                $tone = 'positive';
                $text .= ' This met the strategy\'s declared average of 1 : ' . number_format((float)$declaredRr, 2) . '.';
            } else {
                $tone = 'caution';
                $text .= ' This was below the strategy\'s declared average of 1 : ' . number_format((float)$declaredRr, 2) . '.';
            }
        }
        $facts[] = ['text' => $text, 'tone' => $tone];
    }

    $marginPct = $trade['margin_used_pct'] ?? null;
    $marginDollars = $trade['margin_dollars'] ?? null;
    if ($marginPct !== null && $marginDollars !== null && (float)$marginDollars > 0) {
        $marginPct = (float)$marginPct;
        $available = max(0, 100 - $marginPct);
        $tone = $marginPct > 50 ? 'caution' : 'info';
        $facts[] = ['text' => 'Margin usage at entry was approximately $' . number_format((float)$marginDollars, 0) . ', leaving ' . round($available) . '% of account equity available.', 'tone' => $tone];
    }

    if (!empty($trade['prop_firm_mode']) && isset($trade['trades_remaining_buffer']) && $trade['trades_remaining_buffer'] !== null) {
        $buf = (float)$trade['trades_remaining_buffer'];
        $tone = $buf < 2 ? 'negative' : 'info';
        $facts[] = ['text' => 'At entry, roughly ' . number_format($buf, 1) . ' more trades of this size could be taken before reaching the account\'s drawdown limit.', 'tone' => $tone];
    }

    return $facts;
}

// pnlClass()/pnlFormat() now live in src/RenderHelpers.php (required globally
// by index.php) — removed from here to stop the six-file copy-paste drift.
//
// Second-precision timestamps (2026-08-20 Drafts UX refinement) — real
// drafts in this app are sometimes saved mere seconds apart (a trader
// re-running the calculator a few times before settling on a plan); the
// previous minute-only format made those look identical. fmtTimestamp()
// now always includes seconds; fmtRelative() gives the short "how long
// ago" form shown alongside it — both derived from the trade's own real
// captured_at/opened_at, never invented.
function fmtTimestamp($dt) {
    if (!$dt) return null;
    $ts = strtotime($dt);
    if (!$ts) return null;
    return date('M j, Y · g:i:s A', $ts);
}
function fmtRelative($dt) {
    if (!$dt) return null;
    $ts = strtotime($dt);
    if (!$ts) return null;
    $diff = time() - $ts;
    if ($diff < 0) $diff = 0;
    if ($diff < 5) return 'just now';
    if ($diff < 60) return $diff . 's ago';
    if ($diff < 3600) return floor($diff / 60) . 'm ago';
    if ($diff < 86400) return floor($diff / 3600) . 'h ago';
    $days = floor($diff / 86400);
    if ($days == 1) return 'yesterday';
    if ($days < 7) return $days . 'd ago';
    return date('M j', $ts);
}
$moodLabels = [1 => 'Poor', 2 => 'Below Average', 3 => 'Neutral', 4 => 'Good', 5 => 'Excellent'];
$energyLabels = [1 => 'Exhausted', 2 => 'Low', 3 => 'Moderate', 4 => 'Alert', 5 => 'Fully Focused'];
$confidenceLabels = [1 => 'Uncertain', 2 => 'Doubtful', 3 => 'Moderate', 4 => 'Confident', 5 => 'High Conviction'];
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Drafts - TraderAlign</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/traderalign/assets/tokens.css">
<link rel="icon" type="image/png" sizes="32x32" href="/traderalign/assets/favicon/favicon-32.png">
    <link rel="icon" type="image/png" sizes="16x16" href="/traderalign/assets/favicon/favicon-16.png">
    <link rel="apple-touch-icon" sizes="180x180" href="/traderalign/assets/favicon/apple-touch-icon.png">
    <link rel="shortcut icon" href="/traderalign/assets/favicon/favicon.ico">
<link rel="stylesheet" href="/traderalign/assets/css/brand.css">
<style>
*{margin:0;padding:0;box-sizing:border-box}
.dash-layout{display:flex;min-height:100vh;background:var(--background)}
.sidebar{width:4rem;background:var(--sidebar);border-right:1px solid var(--sidebar-border);display:flex;flex-direction:column;align-items:center;padding-top:1rem;gap:.25rem;position:fixed;top:0;left:0;bottom:0;z-index:40}
.sidebar a{display:flex;align-items:center;justify-content:center;width:2.75rem;height:2.75rem;border-radius:var(--radius-md);color:var(--foreground-dim);transition:background var(--t-fast),color var(--t-fast);position:relative}
.sidebar a:hover{background:var(--background-surface);color:var(--foreground)}
.sidebar a.active{background:var(--background-surface);color:var(--sidebar-primary)}
.sidebar a .tt{position:absolute;left:3.5rem;background:var(--popover);color:var(--foreground);font-size:.75rem;padding:.25rem .625rem;border-radius:var(--radius-sm);white-space:nowrap;pointer-events:none;opacity:0;transition:opacity var(--t-fast)}
.sidebar a:hover .tt{opacity:1}
.main{margin-left:4rem;flex:1;padding:2.25rem 2.5rem 4rem;max-width:1180px}
.page-title{font-size:1.6rem;font-weight:700;color:var(--foreground);letter-spacing:-.01em}
.page-sub{color:var(--foreground-muted);margin-top:.25rem;margin-bottom:.5rem;font-size:.9rem}
.back-link{font-size:.83rem;color:var(--foreground-dim);margin-bottom:1.25rem;display:inline-flex;align-items:center;gap:.3rem;transition:color var(--t-fast)}
.back-link:hover{color:var(--primary)}
.strategy-heading{font-size:1.05rem;font-weight:600;color:var(--foreground-muted);margin-bottom:.2rem}
.strategy-heading-sub{font-size:.8rem;color:var(--foreground-dim);margin-bottom:1.75rem}

/* ── Urgency banner — a plain, static fact, not a blinking alarm ───── */
.urgency-banner {
    display: flex; align-items: center; flex-wrap: wrap; gap: .65rem;
    padding: .7rem 1rem; margin-bottom: 1.5rem;
    background: color-mix(in srgb, var(--negative) 6%, var(--background-elevated));
    border: 1px solid color-mix(in srgb, var(--negative) 22%, transparent);
    border-radius: var(--radius-lg); font-size: .82rem; color: var(--foreground-muted);
}
.urgency-banner strong{color:var(--foreground)}
.ub-icon { font-size: 1rem; flex-shrink: 0; color: var(--negative); }

/* ── Group header — calm section divider with an honest two-sided stat ── */
.draft-group-header {
    display: flex; align-items: center; justify-content: space-between;
    gap: 1.25rem; padding: 1rem 1.25rem; margin: 2.25rem 0 .75rem;
    background: var(--background-elevated); border: 1px solid var(--border);
    border-left: 3px solid; border-radius: var(--radius-lg); flex-wrap: wrap;
}
.draft-group-header:first-of-type { margin-top: 0; }
.dgh-left { display: flex; align-items: center; gap: .6rem; }
.dgh-icon { font-size: .95rem; }
.dgh-label { font-size: .88rem; font-weight: 700; color: var(--foreground); letter-spacing: -.005em; }
.dgh-count {
    font-size: .68rem; font-weight: 600; font-family: var(--font-mono); color: var(--foreground-dim);
    background: var(--background-surface); padding: .15rem .5rem; border-radius: var(--radius-full);
}
.dgh-right { display: flex; flex-direction: column; align-items: flex-end; gap: .3rem; }
.dgh-stats { display: flex; align-items: baseline; gap: .7rem; }
.dgh-stat { display: flex; flex-direction: column; align-items: flex-end; gap: 1px; }
.dgh-stat-val { font-family: var(--font-mono); font-size: .95rem; font-weight: 700; line-height: 1; }
.dgh-stat-lbl { font-size: .62rem; color: var(--foreground-dim); text-transform: uppercase; letter-spacing: .04em; }
.dgh-stat-sep { width: 1px; height: 1.6rem; background: var(--border); }
.dgh-hint { font-size: .7rem; color: var(--foreground-dim); text-align: right; max-width: 26rem; }

/* ── Cards grid ─────────────────────────────────────────── */
.draft-cards-grid {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    margin-bottom: 1rem;
}

/* ── Card — flat, precise, restrained. A single colored accent bar
   communicates status; nothing pulses, glows, or animates for decoration.
   Motion is reserved for things that are actually changing (the scalper
   countdown), never applied as ambient chrome. ─────────────────────── */
.draft-card{
    position: relative;
    min-width: 0;
    width: 100%;
    background: var(--background-elevated);
    border: 1px solid var(--border);
    border-left: 3px solid var(--border);
    border-radius: var(--radius-lg);
    padding: 1.1rem 1.35rem;
    transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
.draft-card.open{ border-left-color: var(--primary); }
.draft-card.urgency-nudge   { border-left-color: color-mix(in srgb, var(--primary) 70%, var(--border)); }
.draft-card.urgency-warning { border-left-color: var(--warning); }
.draft-card.urgency-urgent  {
    border-left-color: var(--negative);
    box-shadow: 0 0 0 1px color-mix(in srgb, var(--negative) 18%, transparent);
}

.urg-badge {
    display: inline-flex; align-items: center; gap: .3rem;
    font-size: .66rem; font-weight: 600; padding: .2rem .55rem; border-radius: var(--radius-full);
}
.urg-red   { background: color-mix(in srgb, var(--negative) 14%, transparent); color: var(--negative); }
.urg-amber { background: color-mix(in srgb, var(--warning)  14%, transparent); color: var(--warning);  }
.urg-blue  { background: color-mix(in srgb, var(--primary)  14%, transparent); color: var(--primary);  }
.urg-green { background: color-mix(in srgb, var(--positive) 14%, transparent); color: var(--positive); }
.countdown { font-family: var(--font-mono); font-size: .66rem; }

.dc-status{font-size:.63rem;font-weight:600;padding:.15rem .5rem;border-radius:var(--radius-full);letter-spacing:.03em}
.dc-status.draft{background:color-mix(in srgb,var(--warning) 16%,transparent);color:var(--warning)}
.dc-status.open{background:color-mix(in srgb,var(--primary) 16%,transparent);color:var(--primary)}
.dc-status.cancelled{background:var(--background-surface);color:var(--foreground-dim)}
.dir{font-size:.75rem;font-weight:700;padding:.22rem .65rem;border-radius:var(--radius-full);letter-spacing:.02em}
.dir.long{background:color-mix(in srgb,var(--positive) 16%,transparent);color:var(--positive)}
.dir.short{background:color-mix(in srgb,var(--negative) 16%,transparent);color:var(--negative)}

/* Identity header (2026-08-20 Drafts UX refinement), always visible, makes
   every draft scannable and distinguishable without opening it. This is
   the primary fix for "several near-identical drafts look the same": a
   stable creation-order number, a precise real timestamp, and a compact
   price snapshot, capped off by one clear primary action. */
.dc-summary{display:flex;flex-direction:column;gap:.5rem}
.dc-details{margin-top:1.1rem;padding-top:1.1rem;border-top:1px solid var(--border)}
.dc-summary-top{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}
.dc-id-badge{
    font-family:var(--font-mono); font-size:.72rem; font-weight:700; letter-spacing:.03em;
    color:var(--foreground); background:var(--background-surface); border:1px solid var(--border);
    padding:.22rem .6rem; border-radius:var(--radius-sm); flex-shrink:0;
}
.dc-account-chip{
    font-size:.68rem; color:var(--foreground-dim); background:var(--background-surface);
    padding:.2rem .55rem; border-radius:var(--radius-full); white-space:nowrap; flex-shrink:0;
    margin-left:auto;
}
.dc-instrument-row{display:flex;align-items:baseline;gap:.6rem;flex-wrap:wrap}
.dc-instrument-row .inst{font-size:1.15rem;font-weight:700;color:var(--foreground);letter-spacing:-.01em}
.dc-sub{font-size:.78rem;color:var(--foreground-dim);white-space:normal;word-break:break-word}
.dc-timing-row{display:flex;align-items:baseline;gap:.4rem;flex-wrap:wrap;font-size:.76rem}
.dc-timing-item{display:inline-flex;align-items:baseline;gap:.3rem}
.dc-timing-label{color:var(--foreground-dim);text-transform:uppercase;font-size:.62rem;letter-spacing:.04em;font-weight:600}
.dc-timing-val{font-family:var(--font-mono);color:var(--foreground-muted);font-weight:600}
.dc-timing-rel{color:var(--foreground-dim);font-size:.7rem}
.dc-timing-sep{color:var(--border)}
.dc-timing-live .dc-timing-val{color:var(--primary)}
.dc-mini-price-row{display:grid;grid-template-columns:repeat(4,1fr);gap:.5rem}
.dc-mini-cell{display:flex;flex-direction:column;gap:1px;padding:.45rem .5rem;background:var(--background-surface);border-radius:var(--radius-md)}
.dc-mini-cell .mlabel{font-size:.58rem;text-transform:uppercase;letter-spacing:.05em;color:var(--foreground-dim);font-weight:600}
.dc-mini-cell .mval{font-family:var(--font-mono);font-size:.82rem;font-weight:700;color:var(--foreground);word-break:break-all}
.dc-mini-cell .mval.pos{color:var(--positive)}
.dc-mini-cell .mval.neg{color:var(--negative)}
.dc-same-setup{
    font-size:.76rem; color:var(--foreground-dim); padding:.5rem .65rem;
    background:var(--background-surface); border-radius:var(--radius-md); border:1px dashed var(--border);
}
.dc-same-setup strong{color:var(--foreground-muted);font-weight:600}
.dc-preview{font-size:.78rem;color:var(--foreground-muted);line-height:1.5}
.dc-continue-btn{
    display:inline-flex; align-items:center; justify-content:center; gap:.45rem;
    width:100%; height:2.35rem; margin-top:.15rem;
    background:var(--primary); color:#fff; border:none; border-radius:var(--radius-md);
    font-family:var(--font-sans); font-size:.82rem; font-weight:600; cursor:pointer;
    transition:opacity var(--t-fast), background var(--t-fast), color var(--t-fast);
}
.dc-continue-btn:hover{opacity:.9}
.dc-continue-btn.is-open{background:transparent;color:var(--foreground-dim);border:1px solid var(--border)}
.dc-continue-btn.is-open:hover{opacity:1;border-color:var(--primary);color:var(--primary)}
.dc-continue-arrow{font-size:.85rem;line-height:1}

/* ── Price grid — Entry / Stop Loss / Take Profit as one clean row ── */
.dc-price-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:.75rem;margin-bottom:.6rem}
.dc-price-cell{
    text-align:center; padding:.6rem .5rem;
    background:var(--background-surface); border-radius:var(--radius-md);
}
.dc-price-cell .plabel{font-size:.6rem;text-transform:uppercase;letter-spacing:.06em;color:var(--foreground-dim);font-weight:600}
.dc-price-cell .pvalue{font-family:var(--font-mono);font-size:.88rem;font-weight:700;color:var(--foreground);margin-top:3px;word-break:break-all}
.dc-price-cell .pvalue.pos{color:var(--positive)}
.dc-price-cell .pvalue.neg{color:var(--negative)}

/* ── TP ladder expand — collapsed by default when there is more than
   one target, so a 10-level ladder never wraps into a wall of chips. ── */
.tp-expand-toggle{
    display:inline-flex; align-items:center; gap:.35rem;
    background:transparent; border:1px dashed var(--border); color:var(--foreground-dim);
    font-family:var(--font-sans); font-size:.7rem; font-weight:600;
    padding:.35rem .7rem; border-radius:var(--radius-full); cursor:pointer;
    margin-bottom:.85rem; transition:border-color var(--t-fast),color var(--t-fast);
}
.tp-expand-toggle:hover{border-color:var(--primary);color:var(--primary)}
.tp-expand-toggle .chev{transition:transform var(--t-fast);display:inline-block;font-size:.6rem}
.tp-expand-toggle.is-open .chev{transform:rotate(180deg)}
.tp-expanded-grid{
    display:grid; grid-template-columns:repeat(auto-fill,minmax(84px,1fr)); gap:.5rem;
    margin:-.4rem 0 .85rem;
}
.tp-mini{
    display:flex;flex-direction:column;align-items:flex-start;gap:1px;
    padding:.4rem .55rem; background:var(--background-surface);
    border:1px solid var(--border); border-radius:var(--radius-md);
}
.tp-mini .tp-mini-n{font-size:.58rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--foreground-dim)}
.tp-mini .tp-mini-v{font-family:var(--font-mono);font-size:.78rem;font-weight:600;color:var(--foreground)}

/* ── Stat strip — risk / lots / leverage / potential / at-risk ────── */
.dc-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(105px,1fr));gap:.5rem 1rem;margin-bottom:1rem}
.dc-stat{display:flex;flex-direction:column;gap:2px;min-width:0}
.dc-stat .slabel{font-size:.63rem;color:var(--foreground-dim);text-transform:uppercase;letter-spacing:.04em}
.dc-stat .sval{font-family:var(--font-mono);color:var(--foreground);font-size:.85rem;font-weight:600;white-space:nowrap}
.dc-divider{height:1px;background:var(--border);margin:.9rem 0}

/* ── Behaviour tags — mood / energy / confidence as quiet pills ───── */
.dc-behaviour{display:flex;gap:.5rem;margin-bottom:.2rem;flex-wrap:wrap}
.dc-behaviour span{
    display:inline-flex;align-items:center;gap:4px;font-size:.7rem;color:var(--foreground-muted);
    background:var(--background-surface); padding:.25rem .6rem; border-radius:var(--radius-full);
}
.dc-behaviour span b{color:var(--foreground-dim);font-weight:600}

/* ── Insights — each calculator observation on its own line, in plain,
   natural language, colored by tone via a small leading icon rather
   than a shouting block of ALL-CAPS text glued together. ─────────── */
.dc-insights{display:flex;flex-direction:column;gap:.5rem;margin-bottom:.2rem}
.insight-row{display:flex;align-items:flex-start;gap:.55rem;font-size:.78rem;line-height:1.5;color:var(--foreground-muted)}
.insight-icon{
    flex-shrink:0; width:1.15rem; height:1.15rem; border-radius:50%;
    display:inline-flex; align-items:center; justify-content:center;
    font-size:.62rem; margin-top:.1rem;
}
.insight-positive .insight-icon{background:color-mix(in srgb,var(--positive) 20%,transparent);color:var(--positive)}
.insight-caution .insight-icon{background:color-mix(in srgb,var(--warning) 20%,transparent);color:var(--warning)}
.insight-negative .insight-icon{background:color-mix(in srgb,var(--negative) 20%,transparent);color:var(--negative)}
.insight-info .insight-icon{background:var(--background-surface);color:var(--foreground-dim)}
.insight-text strong{color:var(--foreground);font-weight:600}
.insight-more{font-size:.7rem;color:var(--foreground-dim);padding-left:1.7rem}

/* ── Footer row ─────────────────────────────────────────── */
.dc-footer {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    flex-wrap: wrap;
}
.dc-actions {
    display: flex;
    gap: 8px;
    align-items: center;
    flex-shrink: 0;
    flex-wrap: wrap;
}

/* ── Buttons — consistent sizing ──────────────────── */
.btn-xs {
    height: 1.85rem;
    padding: 0 15px;
    font-size: .72rem;
    border-radius: var(--radius-sm);
    border: none;
    cursor: pointer;
    font-weight: 600;
    font-family: var(--font-sans);
    white-space: nowrap;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 64px;
    text-align: center;
    transition: opacity .15s, background .15s, transform .1s;
}
.btn-xs:hover { opacity: 0.88; }
.btn-xs:active { transform: translateY(1px); }
.btn-confirm { background: var(--positive); color: #fff; }
.btn-lost { background: var(--negative); color: #fff; }
.btn-cancel-trade { background: transparent; color: var(--foreground-dim); border: 1px solid var(--border); }
.btn-journal { background: transparent; color: var(--primary); border: 1px solid color-mix(in srgb, var(--primary) 30%, transparent); }
.btn-confirm-live { background: var(--positive); color: #fff; }

/* ── Lifecycle enforcement action buttons (rendered by JS) — a primary
   action (danger/success, filled) always sits next to a visually secondary
   one, so "Continue Trading" never reads as an equally-weighted default. ── */
.btn-danger { background: var(--negative); color: #fff; }
.btn-success { background: var(--positive); color: #fff; }
.btn-secondary { background: transparent; color: var(--foreground-dim); border: 1px solid var(--border); }
.btn-ghost { background: transparent; color: var(--foreground-dim); border: none; text-decoration: underline; padding: 0 .5rem; }

.empty-state{text-align:center;padding:4rem;color:var(--foreground-dim)}
.empty-state .icon{font-size:3rem;margin-bottom:1rem}
.empty-state h3{color:var(--foreground);margin-bottom:.5rem}
.empty-state p{font-size:.9rem;max-width:400px;margin:0 auto 1.5rem;line-height:1.5}
.positive{color:var(--positive)}.negative{color:var(--negative)}
.mono{font-family:var(--font-mono)}
.resolve-form{display:none;margin-top:1rem;padding:1.1rem 1.2rem;background:var(--background-surface);border-radius:var(--radius-lg);border:1px solid var(--border);transition:border-color var(--t-fast)}
.resolve-form.outcome-lost{border-color:color-mix(in srgb,var(--negative) 35%,transparent)}
.resolve-form.outcome-won{border-color:color-mix(in srgb,var(--positive) 35%,transparent)}
.btn-confirm.is-loss{background:var(--negative)}
.resolve-title{font-size:.72rem;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--foreground-dim);margin-bottom:.9rem;display:flex;align-items:center;gap:.5rem}
.resolve-title .res-badge{font-size:.65rem;padding:.15rem .55rem;border-radius:var(--radius-full);text-transform:none;letter-spacing:0}
.resolve-form.outcome-won .res-badge{background:color-mix(in srgb,var(--positive) 18%,transparent);color:var(--positive)}
.resolve-form.outcome-lost .res-badge{background:color-mix(in srgb,var(--negative) 18%,transparent);color:var(--negative)}
.resolve-grid{display:grid;grid-template-columns:1fr 1fr;gap:.7rem;margin-bottom:.95rem}
.resolve-field{display:flex;flex-direction:column;gap:.35rem}
.resolve-field.full{grid-column:1 / -1}
.resolve-field label{font-size:.65rem;color:var(--foreground-dim);text-transform:uppercase;letter-spacing:.03em}
.resolve-field .input-wrap{position:relative}
.resolve-field .input-wrap .prefix{position:absolute;left:.5rem;top:50%;transform:translateY(-50%);font-size:.75rem;color:var(--foreground-dim);font-family:var(--font-mono);pointer-events:none}
.resolve-field input,.resolve-field select{width:100%;height:2.1rem;padding:0 .55rem;background:var(--background);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--foreground);font-family:var(--font-mono);font-size:.78rem;box-sizing:border-box;transition:border-color var(--t-fast)}
.resolve-field input.has-prefix{padding-left:1.15rem}
.resolve-field input:focus,.resolve-field select:focus{outline:none;border-color:var(--primary)}
.resolve-actions{display:flex;gap:.5rem;justify-content:flex-end;margin-top:.2rem}
.resolve-actions .btn-xs{height:1.95rem;padding:0 1rem;font-size:.74rem}

/* ── Account Status step ──────────────────────────────────────────── */
.acct-status-step{display:none;margin-top:.85rem;padding-top:.85rem;border-top:1px solid var(--border)}
.acct-status-step.show{display:block}
.acct-status-title{font-size:.7rem;font-weight:600;color:var(--foreground-muted);margin-bottom:.55rem}
.acct-status-options{display:flex;gap:.4rem;flex-wrap:wrap;margin-bottom:.6rem}
.acct-status-opt{font-size:.72rem;padding:.35rem .7rem;border-radius:var(--radius-full);border:1px solid var(--border);background:var(--background);color:var(--foreground-muted);cursor:pointer;transition:border-color var(--t-fast),color var(--t-fast)}
.acct-status-opt.selected{border-color:var(--primary);color:var(--primary);background:color-mix(in srgb,var(--primary) 10%,transparent)}
.acct-status-opt.selected.is-breach{border-color:var(--negative);color:var(--negative);background:color-mix(in srgb,var(--negative) 10%,transparent)}
.acct-status-opt.selected.is-pass{border-color:var(--positive);color:var(--positive);background:color-mix(in srgb,var(--positive) 10%,transparent)}

/* ── Lifecycle enforcement card — a plain, calm status card. Color
   communicates the state; the copy itself reads like a person telling
   you what would happen, not a system declaring a verdict. ─────────── */
.lifecycle-enforcement{margin-bottom:.7rem;padding:.75rem .9rem;border-radius:var(--radius-md);border:1px solid var(--border);background:var(--background-surface)}
.lifecycle-enforcement.is-breach{border-color:color-mix(in srgb,var(--negative) 45%,var(--border));background:color-mix(in srgb,var(--negative) 9%,transparent)}
.lifecycle-enforcement.is-breach .lifecycle-message strong{color:var(--negative)}
.lifecycle-enforcement.is-pass{border-color:color-mix(in srgb,var(--positive) 45%,var(--border));background:color-mix(in srgb,var(--positive) 9%,transparent)}
.lifecycle-enforcement.is-pass .lifecycle-message strong{color:var(--positive)}
/* Daily drawdown — progressive, non-locking warning tiers (50-75% used =
   caution/yellow, 75-100% = alert/orange, 100%+ = critical/red). None of
   these three ever lock the account — only overall drawdown (.is-breach)
   does that. Kept visually distinct from .is-breach even at the red tier
   so a trader never mistakes "daily loss maxed out" for a real, locked
   account breach. */
.lifecycle-enforcement.is-daily-caution{border-color:color-mix(in srgb,var(--warning) 45%,var(--border));background:color-mix(in srgb,var(--warning) 9%,transparent)}
.lifecycle-enforcement.is-daily-caution .lifecycle-message strong{color:var(--warning)}
.lifecycle-enforcement.is-daily-alert{border-color:color-mix(in srgb,var(--warning) 40%,var(--negative));background:color-mix(in srgb,var(--warning) 7%,color-mix(in srgb,var(--negative) 7%,transparent))}
.lifecycle-enforcement.is-daily-alert .lifecycle-message strong{color:color-mix(in srgb,var(--warning) 40%,var(--negative))}
.lifecycle-enforcement.is-daily-critical{border-color:color-mix(in srgb,var(--negative) 45%,var(--border));background:color-mix(in srgb,var(--negative) 7%,transparent)}
.lifecycle-enforcement.is-daily-critical .lifecycle-message strong{color:var(--negative)}
.lifecycle-message{font-size:.78rem;line-height:1.55;margin-bottom:.6rem;color:var(--foreground-muted)}
.lifecycle-actions{display:flex;gap:.4rem;flex-wrap:wrap}

.breach-reason-row{display:none;margin-bottom:.6rem}
.breach-reason-row.show{display:flex;gap:.4rem;align-items:center;flex-wrap:wrap}

/* ── "Managed Exit" (2026-08-03) — reuses the .resolve-field grid for its
   own select/input, and the same "calm card, color-coded state" language
   as .lifecycle-enforcement for its hint/error, rather than a new visual
   system. ────────────────────────────────────────────────────────────── */
.managed-exit-section{margin-bottom:.7rem;padding:.75rem .9rem;border-radius:var(--radius-md);border:1px solid var(--border);background:var(--background-surface)}
.managed-exit-title{font-size:.7rem;font-weight:700;color:var(--foreground-muted);text-transform:uppercase;letter-spacing:.03em;margin-bottom:.4rem}
.managed-exit-desc{font-size:.74rem;line-height:1.5;color:var(--foreground-dim);margin:0 0 .6rem}
.managed-exit-hint{margin-top:.5rem;font-size:.74rem;line-height:1.5;color:var(--warning);padding:.5rem .65rem;border-radius:var(--radius-sm);background:color-mix(in srgb,var(--warning) 9%,transparent);border:1px solid color-mix(in srgb,var(--warning) 22%,transparent)}
.managed-exit-error{margin-top:.4rem;font-size:.74rem;line-height:1.5;color:var(--negative);padding:.5rem .65rem;border-radius:var(--radius-sm);background:color-mix(in srgb,var(--negative) 9%,transparent);border:1px solid color-mix(in srgb,var(--negative) 22%,transparent)}
.breach-reason-row label{font-size:.68rem;color:var(--foreground-dim);text-transform:uppercase;letter-spacing:.03em}
.breach-reason-row select{height:2.1rem;padding:0 .5rem;background:var(--background);border:1px solid var(--border);border-radius:var(--radius-sm);color:var(--foreground);font-size:.78rem;font-family:inherit}

/* Toolbar: search / filter / sort (2026-08-20 Drafts UX refinement, item
   10). Calm and restrained, matching the page's existing surface/border
   language rather than introducing a new visual system. */
.draft-toolbar{
    display:flex; align-items:center; gap:.85rem; flex-wrap:wrap;
    padding:.7rem .9rem; margin-bottom:1rem;
    background:var(--background-elevated); border:1px solid var(--border); border-radius:var(--radius-lg);
}
.draft-search-wrap{position:relative; flex:1 1 220px; min-width:180px}
.search-icon{position:absolute;left:.65rem;top:50%;transform:translateY(-50%);color:var(--foreground-dim);font-size:.8rem;pointer-events:none}
.draft-search-input{
    width:100%; height:2.1rem; padding:0 .7rem 0 1.85rem;
    background:var(--background); border:1px solid var(--border); border-radius:var(--radius-sm);
    color:var(--foreground); font-family:var(--font-sans); font-size:.8rem;
    transition:border-color var(--t-fast);
}
.draft-search-input:focus{outline:none;border-color:var(--primary)}
.draft-filter-pills{display:flex;gap:.4rem;flex-wrap:wrap}
.filter-pill{
    font-family:var(--font-sans); font-size:.74rem; font-weight:600;
    padding:.35rem .75rem; border-radius:var(--radius-full); border:1px solid var(--border);
    background:transparent; color:var(--foreground-muted); cursor:pointer;
    transition:border-color var(--t-fast), color var(--t-fast), background var(--t-fast);
}
.filter-pill:hover{border-color:var(--primary); color:var(--primary)}
.filter-pill.active{border-color:var(--primary); color:var(--primary); background:color-mix(in srgb,var(--primary) 10%,transparent)}
.filter-pill-urgent.active{border-color:var(--negative); color:var(--negative); background:color-mix(in srgb,var(--negative) 10%,transparent)}
.draft-sort-wrap{display:flex; align-items:center; gap:.45rem; margin-left:auto; flex-shrink:0}
.draft-sort-wrap label{font-size:.7rem; color:var(--foreground-dim); text-transform:uppercase; letter-spacing:.03em; font-weight:600}
.draft-sort-wrap select{
    height:2.1rem; padding:0 .55rem; background:var(--background); border:1px solid var(--border);
    border-radius:var(--radius-sm); color:var(--foreground); font-size:.78rem; font-family:inherit; cursor:pointer;
}
.draft-no-results{text-align:center;padding:2.5rem 1rem;color:var(--foreground-dim);font-size:.85rem}

@media (max-width: 768px) {
    .main{padding:1.25rem}
    .resolve-grid{grid-template-columns:1fr}
    .dc-footer { flex-direction: column; align-items: flex-start; gap: 10px; }
    .dc-actions { width: 100%; justify-content: flex-start; }
    .dc-price-grid { grid-template-columns: 1fr 1fr 1fr; gap: .5rem; }
    .dc-mini-price-row { grid-template-columns: 1fr 1fr; }
    .draft-group-header { flex-direction: column; align-items: flex-start; }
    .dgh-right { align-items: flex-start; }
    .dgh-hint { text-align: left; }
    .draft-toolbar { flex-direction: column; align-items: stretch; }
    .draft-sort-wrap { margin-left: 0; justify-content: space-between; }
}
</style>
</head>
<body class="dash-layout">

<?php require __DIR__ . '/sidebar.php'; ?>

<main class="main">
    <a href="/traderalign/dashboard" class="back-link">← Back to Dashboard</a>
    <h1 class="page-title">Drafts</h1>

    <h2 class="strategy-heading">
        Drafts for <?= htmlspecialchars($strategyDisplayName) ?>
    </h2>
    <p class="strategy-heading-sub">Your saved trade ideas: <?= count($recentDrafts) ?> waiting for confirmation or resolution</p>

    <?php if ($actionError): ?>
    <div style="background:color-mix(in srgb,var(--negative) 10%,transparent);border:1px solid color-mix(in srgb,var(--negative) 25%,transparent);color:var(--negative);padding:.6rem .875rem;border-radius:var(--radius-md);font-size:.8rem;margin-bottom:1rem">
        Action failed: <?= htmlspecialchars($actionError) ?>
    </div>
    <?php endif; ?>

    <?php if (!empty($recentDrafts)): ?>
    <!-- Toolbar: search / filter / sort (2026-08-20 Drafts UX refinement,
         item 10). Fast retrieval across a growing list of drafts, without
         a heavy filter system. Search matches instrument, direction,
         setup, session and account context, all real fields already
         rendered on each card. Sort is a page-level GET param so it
         round-trips cleanly on reload; search/direction/urgent filters are
         client-side, same pattern this page already used for the urgency
         banner, now unified into one mechanism. -->
    <div class="draft-toolbar">
        <div class="draft-search-wrap">
            <span class="search-icon">⌕</span>
            <input type="text" id="draftSearchInput" class="draft-search-input" placeholder="Search drafts by instrument, session or setup…" oninput="applyDraftFilters()">
        </div>
        <div class="draft-filter-pills" id="draftFilterPills">
            <button type="button" class="filter-pill active" data-filter="all" onclick="setDraftFilter('all', this)">All</button>
            <button type="button" class="filter-pill" data-filter="long" onclick="setDraftFilter('long', this)">Long</button>
            <button type="button" class="filter-pill" data-filter="short" onclick="setDraftFilter('short', this)">Short</button>
            <?php if ($urgentCount > 0): ?>
            <button type="button" class="filter-pill filter-pill-urgent" data-filter="urgent" onclick="setDraftFilter('urgent', this)">⚠ Urgent (<?= $urgentCount ?>)</button>
            <?php endif; ?>
        </div>
        <div class="draft-sort-wrap">
            <label for="draftSortSelect">Sort</label>
            <select id="draftSortSelect" onchange="location.href = draftSortUrl(this.value)">
                <option value="recent" <?= $sortMode === 'recent' ? 'selected' : '' ?>>Recently Updated</option>
                <option value="newest" <?= $sortMode === 'newest' ? 'selected' : '' ?>>Newest Created</option>
                <option value="oldest" <?= $sortMode === 'oldest' ? 'selected' : '' ?>>Oldest Created</option>
            </select>
        </div>
    </div>
    <?php endif; ?>

    <?php if ($urgentCount > 0): ?>
    <div class="urgency-banner">
        <span class="ub-icon">⚠</span>
        <span><?= $urgentCount ?> draft<?= $urgentCount !== 1 ? 's' : '' ?> need<?= $urgentCount === 1 ? 's' : '' ?> urgent attention based on your <strong><?= htmlspecialchars(ucwords(str_replace('_', ' ', $draftTradingStyle))) ?></strong> trading style</span>
        <span style="flex:1"></span>
        <button type="button" class="btn-xs btn-cancel-trade" onclick="setDraftFilter('urgent', document.querySelector('.filter-pill[data-filter=\'urgent\']'))">View urgent drafts →</button>
    </div>
    <?php endif; ?>

    <?php if (empty($recentDrafts)): ?>
    <div class="empty-state">
        <div class="icon">📝</div>
        <h3>No saved drafts yet</h3>
        <p>Save a trade idea from the Calculator and it'll show up here, organized and timestamped, ready for when you come back to it.</p>
        <a href="/traderalign/trading-center" class="btn btn-primary btn-md">Start a Trade</a>
    </div>
    <?php else: ?>
    <div class="draft-no-results" id="draftNoResults" style="display:none">No drafts match your search or filter. <button type="button" class="btn-ghost" onclick="clearAllDraftFilters()" style="cursor:pointer">Clear filters</button></div>
    <?php
    $groups = [
        [
            'key'     => 'live',
            'label'   => 'Live - Open Positions',
            'icon'    => '●',
            'accent'  => 'var(--positive)',
            'trades'  => $groupOpen,
            'summary' => $summaryOpen,
            'hint'    => 'These trades are confirmed and running. Monitor or close.',
        ],
        [
            'key'     => 'pending',
            'label'   => 'Pending - Awaiting Confirmation',
            'icon'    => '◎',
            'accent'  => 'var(--warning)',
            'trades'  => $groupPending,
            'summary' => $summaryPending,
            'hint'    => 'Review and confirm live or cancel these setups.',
        ],
    ];

    foreach ($groups as $group):
        if (empty($group['trades'])) continue;
        $potentialFmt = '+' . number_format($group['summary']['potential'], 2);
        $atRiskFmt = '-' . number_format($group['summary']['at_risk'], 2);
    ?>

    <div class="draft-group" data-group-key="<?= $group['key'] ?>">
    <div class="draft-group-header" style="border-left-color:<?= $group['accent'] ?>">
        <div class="dgh-left">
            <span class="dgh-icon" style="color:<?= $group['accent'] ?>"><?= $group['icon'] ?></span>
            <span class="dgh-label"><?= $group['label'] ?></span>
            <span class="dgh-count"><?= $group['summary']['count'] ?> trade<?= $group['summary']['count'] !== 1 ? 's' : '' ?></span>
        </div>
        <div class="dgh-right">
            <div class="dgh-stats" title="Planned outcomes based on each trade's entry, stop and target. Not a live price feed, so this isn't running P&amp;L.">
                <div class="dgh-stat">
                    <span class="dgh-stat-val positive"><?= $potentialFmt ?></span>
                    <span class="dgh-stat-lbl">if targets hit</span>
                </div>
                <div class="dgh-stat-sep"></div>
                <div class="dgh-stat">
                    <span class="dgh-stat-val negative"><?= $atRiskFmt ?></span>
                    <span class="dgh-stat-lbl">if stopped out</span>
                </div>
            </div>
            <span class="dgh-hint"><?= $group['hint'] ?></span>
        </div>
    </div>

    <div class="draft-cards-grid">
        <?php
        // Consecutive-duplicate price collapsing (2026-08-20 Drafts UX
        // refinement, follow-up) — several real drafts in this app can be
        // literally identical (same entry/stop/target/RR, saved seconds
        // apart while a trader iterates on the same setup). Repeating the
        // full mini price grid on every single card added visual noise
        // without adding information once you'd already seen it once.
        // When a card's own price fingerprint exactly matches the card
        // immediately before it in this group's current display order, the
        // mini price row collapses to "Same setup as Draft #N" — the full
        // Entry/Stop/Take Profit detail is still always shown in full once
        // a card is expanded, this only affects the collapsed summary.
        $prevPriceFingerprint = null;
        $prevPriceDraftNum = null;
        ?>
        <?php foreach ($group['trades'] as $seqIdx => $trade):
            $urgency = $trade['_urgency'];
            $hoursOpen = $trade['_hours_open'];
            $since = $trade['captured_at'] ?? null;
            $sinceTs = $since ? strtotime($since) : null;
            $draftNum = $trade['_draft_num']; // stable creation-order identity — see computation above

            $mood = $trade['mood_before'] ?? null;
            $energy = $trade['energy_before'] ?? null;
            $confidence = $trade['confidence_before'] ?? null;
            $projectedProfit = (float)($trade['max_potential_profit'] ?? $trade['projected_pnl'] ?? 0);
            $riskAmount = (float)($trade['max_potential_loss'] ?? $trade['risk_per_position'] ?? 0);
            $createdTs = fmtTimestamp($trade['captured_at'] ?? null);
            $openedTs = fmtTimestamp($trade['opened_at'] ?? null);
            $tid = $trade['id'];
            $entryPriceVal = (float)($trade['entry_price'] ?? 0);
            $lotsVal = round((float)($trade['lots_per_position'] ?? 0), 4);
            $isProp = !empty($trade['prop_firm_mode']);

            // All populated TP levels for this trade (tp_price = TP1, then tp2_price..tp10_price)
            $tpLevels = [];
            $tpColumnMap = ['tp_price' => 1, 'tp2_price' => 2, 'tp3_price' => 3, 'tp4_price' => 4, 'tp5_price' => 5, 'tp6_price' => 6, 'tp7_price' => 7, 'tp8_price' => 8, 'tp9_price' => 9, 'tp10_price' => 10];
            foreach ($tpColumnMap as $col => $n) {
                if (isset($trade[$col]) && $trade[$col] !== null && (float)$trade[$col] > 0) {
                    $tpLevels[] = ['n' => $n, 'price' => (float)$trade[$col]];
                }
            }

            // Account data for intelligent status selection
            $accountSize = (float)($trade['account_size_at_entry'] ?? 0);
            $balanceAtEntry = (float)($trade['balance_at_entry'] ?? 0);
            $phaseMaxDdPct = (float)($trade['phase_max_dd_pct'] ?? 0);
            $phaseDailyLossPct = (float)($trade['phase_daily_loss_pct'] ?? 0);
            $phaseProfitTargetPct = (float)($trade['phase_profit_target_pct'] ?? 0);
            $propPhaseAtEntry = $trade['prop_phase_at_entry'] ?? null;
            $propFirmFunded = !empty($trade['prop_firm_funded']);
            $isFundedAccount = $propFirmFunded || $propPhaseAtEntry === 'funded';

            // Determine if this trade's challenge account is in an active challenge phase
            // (Profit Target is only a valid breach reason during challenge phases).
            // $phaseLabelForPass ("Phase 1"/"Phase 2"/"Phase 3") is used by the pass
            // message in checkLifecycleEnforcement() below (2026-08-01 Drafts UX
            // review, item 8) so it can name the real phase instead of saying
            // "the phase" generically.
            $isChallengePhase = false;
            $phaseLabelForPass = '';
            // Account context (2026-08-20 Drafts UX refinement, item 12) — a
            // trader with both Prop Firm and Live accounts must never wonder
            // which one a draft belongs to. Extended from the prop-only
            // lookup above to read every trade's own account row, purely for
            // display (account label) — the existing $isChallengePhase/
            // $phaseLabelForPass logic below is unchanged, still prop-only.
            $accountLabel = null;
            if (!empty($trade['account_id'])) {
                $acctRow = $db->selectById('trading_accounts', $trade['account_id']);
                $accountLabel = $acctRow['label'] ?? null;
                if ($isProp) {
                    $lcState = $acctRow['lifecycle_state'] ?? '';
                    if (in_array($lcState, ['phase_1_active', 'phase_2_active', 'phase_3_active'], true)) {
                        $isChallengePhase = true;
                        $phaseLabelForPass = 'Phase ' . substr($lcState, 6, 1);
                    }
                }
            }

            // Declared strategy risk/RR for this specific trade, used by
            // buildEntryFacts() below to state whether the trade's own numbers
            // met the strategy's own declared limits — a fact comparison, not
            // an opinion (2026-08-01 Drafts UX review, items 4/5).
            static $__svCache = [];
            $tradeSv = null;
            if (!empty($trade['strategy_version_id'])) {
                $svKey = $trade['strategy_version_id'];
                if (!array_key_exists($svKey, $__svCache)) {
                    $__svCache[$svKey] = $db->selectById('strategy_versions', $svKey);
                }
                $tradeSv = $__svCache[$svKey];
            }
            $entryFacts = buildEntryFacts($trade, $tradeSv);

            // Real breach/pass boundary figures for the Account Status step's
            // "mathematically breached/passed" pre-check (2026-07-28 Reports
            // finishing pass, item 4 regression fix). Only computed for
            // resolvable (status='open') prop-firm trades, cached per
            // account_id within this request since several trades can share
            // an account. Sourced from AnalyticsEngine::getChallengeMetrics()
            // — the exact same function dashboard.php's resolve_trade_id
            // handler uses to cap challenge_pnl — so the client-side pre-
            // check can never disagree with what the server will actually do.
            // No independent client-side approximation from stale per-trade
            // snapshot fields (account_size_at_entry, phase_max_dd_pct, etc.
            // — those remain above only for display/isFundedAccount purposes).
            static $__challengeMetricsCache = [];
            $hasChallengeData = false;
            $liveBalance = 0.0;
            $realBreachLevel = 0.0;
            $realTargetBalance = 0.0;
            $realDailyRemaining = 0.0;
            $realDailyLimit = 0.0;
            $realHasTarget = false;
            if ($isProp && $trade['status'] === 'open' && !empty($trade['account_id'])) {
                $cmAcctId = $trade['account_id'];
                if (!array_key_exists($cmAcctId, $__challengeMetricsCache)) {
                    try {
                        $__challengeMetricsCache[$cmAcctId] = AnalyticsEngine::getChallengeMetrics($cmAcctId);
                    } catch (\Throwable $e) {
                        $__challengeMetricsCache[$cmAcctId] = ['error' => $e->getMessage()];
                    }
                }
                $cm = $__challengeMetricsCache[$cmAcctId];
                if (!isset($cm['error'])) {
                    $hasChallengeData = true;
                    // ROOT-CAUSE FIX (2026-08-07) — was reading
                    // account_metrics.current_balance (trading_accounts.
                    // current_balance, a whole-account ledger that never
                    // resets at a phase promotion), which inflated
                    // liveBalance with carried-over profit from an earlier
                    // phase and made this pre-check fire far short of the
                    // real configured breach/pass thresholds (confirmed
                    // live: Phase Passed at +$172.48, Account Breached at
                    // -$49.98, on a fresh Phase 2 account that should only
                    // trigger at +-$400/+-8%). getChallengeMetrics()'s own
                    // phase_balance field is the single, phase-reset-aware
                    // source of truth for this exact figure — see its own
                    // doc comment in engine.php.
                    $liveBalance = (float)($cm['phase_balance'] ?? $cm['account_metrics']['current_balance'] ?? 0);
                    $realBreachLevel = (float)$cm['overall_drawdown']['breach_level_amount'];
                    $realDailyRemaining = (float)$cm['daily_drawdown']['remaining_amount'];
                    $realDailyLimit = (float)$cm['daily_drawdown']['limit_amount'];
                    $realProfitNeeded = (float)$cm['distance_to_pass']['target_profit_amount'];
                    $realStartingBalance = (float)($cm['account_metrics']['starting_balance'] ?? 0);
                    $realTargetBalance = $realStartingBalance + $realProfitNeeded;
                    $realHasTarget = $realProfitNeeded > 0;
                }
            }

            $capturedAt = $trade['captured_at'] ?? null;

            // One factual line stating what this card is and what happens
            // next — a trader shouldn't have to infer that from a wall of
            // identical-looking stats (2026-08-01 Drafts UX review).
            $storyLine = '';
            if ($trade['status'] === 'draft') {
                $storyLine = 'Not yet confirmed live. Confirm if this was executed at the broker, or cancel if it wasn\'t.';
            } elseif ($trade['status'] === 'open') {
                $styleLabel = str_replace('_', ' ', $draftTradingStyle);
                if ($urgency === 'urgent') {
                    $storyLine = 'Open longer than typical for a ' . $styleLabel . ' setup. Record the outcome when you have it.';
                } elseif ($urgency === 'warning') {
                    $storyLine = 'Running longer than usual for a ' . $styleLabel . ' setup, worth checking on this position.';
                } else {
                    $storyLine = 'Position is open. Record Won or Lost once it closes.';
                }
            }

            // Searchable text for the toolbar's search box (item 10) — real
            // fields only (instrument/direction/setup/session/account),
            // never fabricated.
            $searchParts = [$trade['instrument'], $trade['direction'], $trade['setup_name'] ?? '', $trade['session'] ?? '', $trade['prop_firm_name'] ?? '', $accountLabel ?? ''];
            $searchBlob = strtolower(trim(implode(' ', array_filter($searchParts))));
        ?>
        <div class="draft-card <?= $trade['status'] === 'open' ? 'open' : '' ?> urgency-<?= $urgency ?>"
             id="draft-card-<?= $tid ?>"
             data-since="<?= $sinceTs ?>"
             data-style="<?= htmlspecialchars($draftTradingStyle) ?>"
             data-direction="<?= htmlspecialchars($trade['direction']) ?>"
             data-search="<?= htmlspecialchars($searchBlob) ?>">

            <!-- Always-visible identity header (2026-08-20 Drafts UX
                 refinement). This is what makes every draft scannable and
                 distinguishable at a glance, even when several share the
                 exact same instrument, direction and setup. The draft
                 number is a stable creation-order ordinal; the timestamps
                 are the trade's own real captured_at/opened_at, nothing
                 here is invented. "Continue Draft" is the one clear
                 primary action; everything below it (full price detail,
                 resolve/confirm controls) is progressively disclosed, not
                 hidden away. -->
            <div class="dc-summary">
                <?php
                // Short urgency labels, not a duplicate of the exact
                // elapsed time already shown in the Saved line below (a
                // real usability regression caught from a live screenshot
                // of 12 near-identical drafts — "open 3 hours 34 minutes"
                // repeated on both the badge and the timestamp line was
                // pure duplication). Precise duration moves to a title
                // tooltip so the information isn't lost, just decluttered.
                $urgencyTitle = $sinceTs ? htmlspecialchars(formatDurationDays($hoursOpen / 24)) . ' since saved' : '';
                // "PENDING"/"LIVE" instead of a literal "DRAFT" status pill
                // — avoids repeating the word "DRAFT" right next to the
                // "DRAFT #N" identity badge, and matches the vocabulary
                // the group headers already use (Live - Open Positions /
                // Pending - Awaiting Confirmation).
                $statusDisplay = $trade['status'] === 'open' ? 'LIVE' : ($trade['status'] === 'draft' ? 'PENDING' : strtoupper($trade['status']));
                ?>
                <div class="dc-summary-top">
                    <span class="dc-id-badge">DRAFT #<?= $draftNum ?></span>
                    <span class="dir <?= $trade['direction'] ?>"><?= $trade['direction'] === 'long' ? 'LONG ▲' : 'SHORT ▼' ?></span>
                    <span class="dc-status <?= $trade['status'] ?>"><?= $statusDisplay ?></span>
                    <?php if ($urgency === 'urgent'): ?>
                        <span class="urg-badge urg-red" title="<?= $urgencyTitle ?>">● Needs Attention</span>
                    <?php elseif ($urgency === 'warning'): ?>
                        <span class="urg-badge urg-amber" title="<?= $urgencyTitle ?>">● Review Soon</span>
                    <?php elseif ($urgency === 'nudge'): ?>
                        <span class="urg-badge urg-blue" title="<?= $urgencyTitle ?>">● Aging</span>
                    <?php elseif ($trade['status'] === 'open'): ?>
                        <span class="urg-badge urg-green">
                            <?php if ($draftTradingStyle === 'scalper' && $sinceTs): ?>
                                <span class="countdown" data-since="<?= $sinceTs ?>" data-limit="<?= $thresholds['warning'] * 3600 ?>">calculating...</span>
                            <?php else: ?>
                                ● Active
                            <?php endif; ?>
                        </span>
                    <?php endif; ?>
                </div>

                <div class="dc-instrument-row">
                    <span class="inst"><?= htmlspecialchars(strtoupper($trade['instrument'])) ?></span>
                    <?php
                    $subParts = [];
                    if (!empty($trade['setup_name'])) $subParts[] = $trade['setup_name'];
                    if (!empty($trade['session'])) $subParts[] = $trade['session'];
                    ?>
                    <?php if (!empty($subParts) || !empty($trade['prop_firm_name']) || !empty($trade['prop_firm_phase'])): ?>
                    <span class="dc-sub">
                        <?= htmlspecialchars(implode(' · ', $subParts)) ?>
                        <?php if (!empty($trade['prop_firm_name'])): ?>
                        <?= !empty($subParts) ? ' · ' : '' ?><span style="color:var(--primary)"><?= htmlspecialchars($trade['prop_firm_name']) ?></span>
                        <?php endif; ?>
                        <?php if (!empty($trade['prop_firm_phase'])): ?>
                        · <?= htmlspecialchars(ucfirst(str_replace('_', ' ', $trade['prop_firm_phase']))) ?>
                        <?php endif; ?>
                    </span>
                    <?php endif; ?>
                    <span class="dc-account-chip"><?= $isProp ? 'Prop Firm' : 'Live' ?><?= $accountLabel ? ' · ' . htmlspecialchars($accountLabel) : '' ?></span>
                </div>

                <div class="dc-timing-row">
                    <span class="dc-timing-item">
                        <span class="dc-timing-label">Saved</span>
                        <span class="dc-timing-val"><?= htmlspecialchars($createdTs ?? '-') ?></span>
                        <?php if ($capturedAt): ?><span class="dc-timing-rel">(<?= htmlspecialchars(fmtRelative($capturedAt)) ?>)</span><?php endif; ?>
                    </span>
                    <?php if ($trade['status'] === 'open' && $openedTs): ?>
                    <span class="dc-timing-sep">·</span>
                    <span class="dc-timing-item dc-timing-live">
                        <span class="dc-timing-label">Confirmed live</span>
                        <span class="dc-timing-val"><?= htmlspecialchars($openedTs) ?></span>
                        <span class="dc-timing-rel">(<?= htmlspecialchars(fmtRelative($trade['opened_at'])) ?>)</span>
                    </span>
                    <?php endif; ?>
                </div>

                <?php
                $priceFingerprint = implode('|', [
                    round((float)$trade['entry_price'], 5),
                    round((float)$trade['sl_price'], 5),
                    round((float)($trade['risk_reward_tp1'] ?? 0), 4),
                    implode(',', array_map(fn($tp) => round($tp['price'], 5), $tpLevels)),
                ]);
                $isSamePriceAsPrev = $prevPriceFingerprint !== null && $priceFingerprint === $prevPriceFingerprint;
                ?>
                <?php if ($isSamePriceAsPrev): ?>
                <div class="dc-same-setup">Same setup as <strong>Draft #<?= $prevPriceDraftNum ?></strong>. Identical entry, stop and target.</div>
                <?php else: ?>
                <div class="dc-mini-price-row">
                    <div class="dc-mini-cell"><span class="mlabel">Entry</span><span class="mval"><?= number_format((float)$trade['entry_price'], 5) ?></span></div>
                    <div class="dc-mini-cell"><span class="mlabel">Stop</span><span class="mval neg"><?= number_format((float)$trade['sl_price'], 5) ?></span></div>
                    <div class="dc-mini-cell"><span class="mlabel">Target<?= count($tpLevels) > 1 ? ' 1' : '' ?></span><span class="mval pos"><?= !empty($tpLevels) ? number_format($tpLevels[0]['price'], 5) : '-' ?></span></div>
                    <div class="dc-mini-cell"><span class="mlabel">R:R</span><span class="mval"><?= !empty($trade['risk_reward_tp1']) ? '1:' . number_format((float)$trade['risk_reward_tp1'], 2) : '-' ?></span></div>
                </div>
                <?php endif; ?>
                <?php $prevPriceFingerprint = $priceFingerprint; $prevPriceDraftNum = $draftNum; ?>

                <?php if ($storyLine): ?>
                <div class="dc-preview"><?= htmlspecialchars($storyLine) ?></div>
                <?php endif; ?>

                <button type="button" class="dc-continue-btn" onclick="toggleDraftDetails('<?= $tid ?>')" id="continuebtn-<?= $tid ?>" data-open-label="<?= $trade['status'] === 'open' ? 'Manage Position' : 'Continue Draft' ?>">
                    <span class="dc-continue-label" id="continuelabel-<?= $tid ?>"><?= $trade['status'] === 'open' ? 'Manage Position' : 'Continue Draft' ?></span>
                    <span class="dc-continue-arrow" id="continuearrow-<?= $tid ?>">→</span>
                </button>
            </div>

            <div class="dc-details" id="dcdetails-<?= $tid ?>" style="display:none">

            <div class="dc-price-grid">
                <div class="dc-price-cell"><p class="plabel">Entry</p><p class="pvalue"><?= number_format((float)$trade['entry_price'], 5) ?></p></div>
                <div class="dc-price-cell"><p class="plabel">Stop Loss</p><p class="pvalue neg"><?= number_format((float)$trade['sl_price'], 5) ?></p></div>
                <div class="dc-price-cell">
                    <p class="plabel">Take Profit<?= count($tpLevels) > 1 ? ' (TP1)' : '' ?><?= !empty($trade['risk_reward_tp1']) ? ' · ' . number_format((float)$trade['risk_reward_tp1'], 2) . 'R' : '' ?></p>
                    <p class="pvalue<?= !empty($tpLevels) ? ' pos' : '' ?>"><?= !empty($tpLevels) ? number_format($tpLevels[0]['price'], 5) : '-' ?></p>
                </div>
            </div>

            <?php if (count($tpLevels) > 1): ?>
            <button type="button" class="tp-expand-toggle" onclick="toggleTpLevels('<?= $tid ?>')" id="tp-toggle-<?= $tid ?>" data-remaining="<?= count($tpLevels) - 1 ?>">
                <span id="tp-toggle-label-<?= $tid ?>">+<?= count($tpLevels) - 1 ?> more target<?= count($tpLevels) - 1 !== 1 ? 's' : '' ?></span>
                <span class="chev">▾</span>
            </button>
            <div class="tp-expanded-grid" id="tp-expanded-<?= $tid ?>" style="display:none">
                <?php foreach (array_slice($tpLevels, 1) as $tp): ?>
                <div class="tp-mini">
                    <span class="tp-mini-n">TP<?= $tp['n'] ?></span>
                    <span class="tp-mini-v"><?= number_format($tp['price'], 5) ?></span>
                </div>
                <?php endforeach; ?>
            </div>
            <?php endif; ?>

            <div class="dc-stats">
                <div class="dc-stat"><span class="slabel">Risk</span><span class="sval"><?= round((float)($trade['risk_pct'] ?? 0), 2) ?>%</span></div>
                <div class="dc-stat"><span class="slabel">Lots</span><span class="sval"><?= round((float)($trade['lots_per_position'] ?? 0), 4) ?></span></div>
                <?php if (!empty($trade['leverage'])): ?>
                <div class="dc-stat"><span class="slabel">Leverage</span><span class="sval"><?= htmlspecialchars($trade['leverage']) ?></span></div>
                <?php endif; ?>
                <div class="dc-stat" title="What this trade is planned to earn if it reaches its take-profit. Not live floating P&amp;L."><span class="slabel">Potential</span><span class="sval positive"><?= pnlFormat($projectedProfit) ?></span></div>
                <div class="dc-stat" title="What this trade is planned to lose if it's stopped out."><span class="slabel">At Risk</span><span class="sval negative">-<?= number_format($riskAmount, 2) ?></span></div>
            </div>

            <?php if ($mood || $energy || $confidence): ?>
            <div class="dc-divider"></div>
            <div class="dc-behaviour">
                <?php if ($mood): ?><span>Mood <b><?= $moodLabels[$mood] ?></b></span><?php endif; ?>
                <?php if ($energy): ?><span>Energy <b><?= $energyLabels[$energy] ?></b></span><?php endif; ?>
                <?php if ($confidence): ?><span>Confidence <b><?= $confidenceLabels[$confidence] ?></b></span><?php endif; ?>
            </div>
            <?php endif; ?>

            <?php if (!empty($entryFacts)): ?>
            <div class="dc-divider"></div>
            <div class="dc-insights">
                <?php foreach ($entryFacts as $f):
                    $factIcon = ['positive' => '✓', 'caution' => '⚠', 'negative' => '⚠', 'info' => 'ℹ'][$f['tone']] ?? 'ℹ';
                ?>
                <div class="insight-row insight-<?= $f['tone'] ?>">
                    <span class="insight-icon"><?= $factIcon ?></span>
                    <span class="insight-text"><?= htmlspecialchars($f['text']) ?></span>
                </div>
                <?php endforeach; ?>
            </div>
            <?php endif; ?>

            <div class="dc-divider"></div>
            <div class="dc-footer">
                <span style="flex:1"></span>
                <div class="dc-actions">
                    <?php if ($trade['status'] === 'draft'): ?>
                    <form method="POST" action="/traderalign/drafts<?= $selectedStrategy ? '?strategy_id=' . htmlspecialchars($selectedStrategy) : '' ?>" style="display:inline">
                        <?= Csrf::field() ?>
                        <input type="hidden" name="confirm_live_id" value="<?= $tid ?>">
                        <button type="submit" class="btn-xs btn-confirm-live">Confirm Live</button>
                    </form>
                    <form method="POST" action="/traderalign/drafts<?= $selectedStrategy ? '?strategy_id=' . htmlspecialchars($selectedStrategy) : '' ?>" style="display:inline" onsubmit="return confirm('Cancel this trade? It will be marked as not executed and removed from your active trades. This will not affect your account balance.');">
                        <?= Csrf::field() ?>
                        <input type="hidden" name="cancel_trade_id" value="<?= $tid ?>">
                        <button type="submit" class="btn-xs btn-cancel-trade">Cancel Trade</button>
                    </form>
                    <?php elseif ($trade['status'] === 'open'): ?>
                    <form method="POST" action="/traderalign/drafts<?= $selectedStrategy ? '?strategy_id=' . htmlspecialchars($selectedStrategy) : '' ?>" id="cancelform-<?= $tid ?>" style="display:none">
                        <?= Csrf::field() ?>
                        <input type="hidden" name="cancel_trade_id" value="<?= $tid ?>">
                    </form>
                    <button class="btn-xs btn-confirm" onclick="resolveTrade('<?= $tid ?>','won',<?= $projectedProfit ?>,<?= $entryPriceVal ?>,<?= $lotsVal ?>,<?= (float)($trade['tp_price']??0) ?>,<?= (float)($trade['sl_price']??0) ?>)">Won</button>
                    <button class="btn-xs btn-lost" onclick="resolveTrade('<?= $tid ?>','lost',<?= -$riskAmount ?>,<?= $entryPriceVal ?>,<?= $lotsVal ?>,<?= (float)($trade['tp_price']??0) ?>,<?= (float)($trade['sl_price']??0) ?>)">Lost</button>
                    <button class="btn-xs btn-cancel-trade" onclick="cancelOpenTrade('<?= $tid ?>')">Cancel</button>
                    <?php endif; ?>
                </div>
            </div>

            <?php if ($trade['status'] === 'open'): ?>
            <div class="resolve-form" id="resolve-<?= $tid ?>">
                <form method="POST" action="/traderalign/dashboard">
                    <?= Csrf::field() ?>
                    <input type="hidden" name="resolve_trade_id" value="<?= $tid ?>">
                    <input type="hidden" name="outcome" id="outcome-<?= $tid ?>">
                    <input type="hidden" name="account_status" id="acctstatus-<?= $tid ?>" value="still_active">
                    <input type="hidden" name="breach_reason" id="breachreason-<?= $tid ?>" value="">
                    <input type="hidden" id="acctsize-<?= $tid ?>" value="<?= $accountSize ?>">
                    <input type="hidden" id="balanceentry-<?= $tid ?>" value="<?= $balanceAtEntry ?>">
                    <input type="hidden" id="maxdd-<?= $tid ?>" value="<?= $phaseMaxDdPct ?>">
                    <input type="hidden" id="dailyloss-<?= $tid ?>" value="<?= $phaseDailyLossPct ?>">
                    <input type="hidden" id="profittarget-<?= $tid ?>" value="<?= $phaseProfitTargetPct ?>">
                    <input type="hidden" id="isfunded-<?= $tid ?>" value="<?= $isFundedAccount ? '1' : '0' ?>">
                    <!-- Real breach/pass boundary figures (AnalyticsEngine::getChallengeMetrics(),
                         same source dashboard.php's resolve_trade_id uses to cap challenge_pnl) —
                         see the "Real breach/pass boundary figures" comment above this trade loop. -->
                    <input type="hidden" id="haschallengedata-<?= $tid ?>" value="<?= $hasChallengeData ? '1' : '0' ?>">
                    <input type="hidden" id="livebalance-<?= $tid ?>" value="<?= $liveBalance ?>">
                    <input type="hidden" id="breachlevel-<?= $tid ?>" value="<?= $realBreachLevel ?>">
                    <input type="hidden" id="targetbalance-<?= $tid ?>" value="<?= $realTargetBalance ?>">
                    <input type="hidden" id="dailyremaining-<?= $tid ?>" value="<?= $realDailyRemaining ?>">
                    <input type="hidden" id="dailylimit-<?= $tid ?>" value="<?= $realDailyLimit ?>">
                    <input type="hidden" id="hastarget-<?= $tid ?>" value="<?= $realHasTarget ? '1' : '0' ?>">
                    <!-- Only phase_1_active/phase_2_active/phase_3_active accounts have a real
                         profit target — funded/instant-funding accounts never do (see the
                         $isChallengePhase computation above this trade loop). -->
                    <input type="hidden" id="ischallengephase-<?= $tid ?>" value="<?= $isChallengePhase ? '1' : '0' ?>">
                    <input type="hidden" id="phaselabel-<?= $tid ?>" value="<?= htmlspecialchars($phaseLabelForPass) ?>">
                    <!-- "Managed Exit" workflow (2026-08-03) — exit_classification/
                         declared_r_multiple are real submitted fields (unlike the
                         block above, which is client-side-only context); set by
                         applyManagedExit()/applyManagedExitCustom() below, read
                         and re-validated server-side in dashboard.php's
                         resolve_trade_id handler. -->
                    <input type="hidden" name="exit_classification" id="exitclass-<?= $tid ?>" value="">
                    <input type="hidden" name="declared_r_multiple" id="declaredr-<?= $tid ?>" value="">

                    <div class="resolve-title">
                        Resolve Trade
                        <span class="res-badge" id="badge-<?= $tid ?>">-</span>
                    </div>

                    <div class="resolve-grid">
                        <div class="resolve-field">
                            <label for="pnl-<?= $tid ?>">Actual P&amp;L</label>
                            <div class="input-wrap">
                                <span class="prefix">$</span>
                                <input type="number" name="actual_pnl" id="pnl-<?= $tid ?>" step="0.01" required class="has-prefix" oninput="checkLifecycleEnforcement('<?= $tid ?>')">
                            </div>
                        </div>
                        <div class="resolve-field">
                            <label for="lots-<?= $tid ?>">Actual Lot Size</label>
                            <input type="number" name="actual_lots" id="lots-<?= $tid ?>" step="0.0001" required>
                        </div>
                        <div class="resolve-field">
                            <label for="entry-<?= $tid ?>">Actual Entry Price</label>
                            <input type="number" name="actual_entry_price" id="entry-<?= $tid ?>" step="0.00001" required oninput="checkBreakEvenHint('<?= $tid ?>')">
                        </div>
                        <?php if (empty($tpLevels)): ?>
                        <div class="resolve-field">
                            <label for="exit-<?= $tid ?>">Actual Exit Price</label>
                            <input type="number" name="actual_exit_price" id="exit-<?= $tid ?>" step="0.00001" placeholder="Enter exit price" required>
                        </div>
                        <?php else: ?>
                        <div class="resolve-field" id="exitgroup-<?= $tid ?>" style="display:none">
                            <label for="exit-<?= $tid ?>">Actual Exit Price</label>
                            <input type="number" name="actual_exit_price" id="exit-<?= $tid ?>" step="0.00001" placeholder="Enter exit price" value="<?= number_format((float)$trade['sl_price'], 5) ?>">
                        </div>
                        <?php foreach ($tpLevels as $tp): ?>
                        <div class="resolve-field tp-row" data-tid="<?= $tid ?>">
                            <label for="exit-tp<?= $tp['n'] ?>-<?= $tid ?>">TP<?= $tp['n'] ?> Actual Exit <span style="text-transform:none;letter-spacing:0;color:var(--foreground-dim)">(planned <?= number_format($tp['price'], 5) ?>)</span></label>
                            <input type="number" id="exit-tp<?= $tp['n'] ?>-<?= $tid ?>" step="0.00001" placeholder="Enter exit price" value="<?= number_format($tp['price'], 5) ?>" oninput="syncTpExit('<?= $tid ?>')">
                            <label style="display:flex;align-items:center;gap:.3rem;text-transform:none;letter-spacing:0;font-size:.68rem;color:var(--foreground-dim)">
                                <input type="checkbox" id="hit-tp<?= $tp['n'] ?>-<?= $tid ?>" style="width:auto;height:auto" onclick="markTpHit('<?= $tid ?>', <?= $tp['n'] ?>, <?= $tp['price'] ?>, this)"> Hit
                            </label>
                        </div>
                        <?php endforeach; ?>
                        <?php endif; ?>
                        <div class="resolve-field">
                            <label for="sl-<?= $tid ?>">Actual Stop Loss</label>
                            <input type="number" name="actual_sl_price" id="sl-<?= $tid ?>" step="0.00001" placeholder="Actual SL" value="<?= number_format((float)$trade['sl_price'], 5) ?>" oninput="checkBreakEvenHint('<?= $tid ?>')">
                        </div>
                        <div class="resolve-field">
                            <label for="actinstr-<?= $tid ?>" title="Confirm or correct the instrument actually traded. Pre-filled from the plan.">Actual Instrument</label>
                            <input type="text" name="actual_instrument" id="actinstr-<?= $tid ?>" value="<?= htmlspecialchars($trade['instrument']) ?>">
                        </div>
                        <div class="resolve-field">
                            <label for="acttf-<?= $tid ?>" title="Which chart timeframe this trade was actually taken on (optional).">Actual Timeframe</label>
                            <select name="actual_timeframe" id="acttf-<?= $tid ?>">
                                <option value="">Not captured</option>
                                <?php foreach (timeframeOptions() as $tf): ?>
                                <option value="<?= $tf ?>"><?= $tf ?></option>
                                <?php endforeach; ?>
                            </select>
                        </div>
                    </div>

                    <?php
                    // "Managed Exit" workflow (2026-08-03) — a real declaration of
                    // Break Even or an early managed exit (partial profit before the
                    // planned Full TP), for exits that don't match any configured
                    // TP/SL level (the case the existing TP-ladder "Hit" checkbox
                    // mechanism has no answer for). Not gated to prop accounts —
                    // this concept applies to any trade. Preset R values are
                    // generated in 0.5R steps STRICTLY LESS THAN the planned RR (a
                    // value equal to it is the Full TP, not a managed exit) — an
                    // impossible option can never even be selected client-side.
                    $managedExitPlannedRr = !empty($trade['risk_reward_tp1']) ? (float)$trade['risk_reward_tp1'] : 0.0;
                    $managedExitPresets = [];
                    if ($managedExitPlannedRr > 0) {
                        for ($r = 0.5; $r < $managedExitPlannedRr - 0.0001; $r += 0.5) {
                            $managedExitPresets[] = round($r, 2);
                        }
                    }
                    ?>
                    <div class="managed-exit-section" id="managedexit-<?= $tid ?>">
                        <div class="managed-exit-title">Managed Exit</div>
                        <p class="managed-exit-desc">Record how you actually managed this trade if you closed before the full planned target: Break Even, a partial profit, or a custom point. This only prefills the Exit Price and P&amp;L above. They stay fully editable, and remain the real source of truth.</p>
                        <div class="resolve-field full">
                            <label for="managedexitsel-<?= $tid ?>">Managed Exit</label>
                            <select id="managedexitsel-<?= $tid ?>" onchange="applyManagedExit('<?= $tid ?>', this.value, <?= $entryPriceVal ?>, <?= (float)($trade['sl_price'] ?? 0) ?>, '<?= $trade['direction'] ?>', <?= $riskAmount ?>)">
                                <option value="">Use exact entry/exit above</option>
                                <option value="breakeven">Break Even</option>
                                <?php foreach ($managedExitPresets as $r): ?>
                                <option value="<?= $r ?>">+<?= number_format($r, 1) ?>R Managed Exit</option>
                                <?php endforeach; ?>
                                <option value="custom">Custom R…</option>
                            </select>
                        </div>
                        <div class="resolve-field full" id="managedexitcustomwrap-<?= $tid ?>" style="display:none">
                            <label for="managedexitcustom-<?= $tid ?>">Custom R<?= $managedExitPlannedRr > 0 ? ' (must be below the planned ' . number_format($managedExitPlannedRr, 2) . 'R target)' : '' ?></label>
                            <input type="number" id="managedexitcustom-<?= $tid ?>" step="0.01" min="0.01"<?= $managedExitPlannedRr > 0 ? ' max="' . number_format(max($managedExitPlannedRr - 0.01, 0.01), 2) . '"' : '' ?>
                                oninput="applyManagedExitCustom('<?= $tid ?>', this.value, <?= $entryPriceVal ?>, <?= (float)($trade['sl_price'] ?? 0) ?>, '<?= $trade['direction'] ?>', <?= $riskAmount ?>, <?= $managedExitPlannedRr ?>)">
                            <div class="managed-exit-error" id="managedexiterror-<?= $tid ?>" style="display:none"></div>
                        </div>
                        <div class="managed-exit-hint" id="managedexithint-<?= $tid ?>" style="display:none"></div>
                    </div>

                    <?php if ($isProp): ?>
                    <!-- ── Lifecycle Enforcement — mathematical breach/pass detection ── -->
                    <div class="lifecycle-enforcement" id="lifecycle-<?= $tid ?>" style="display:none">
                        <div class="lifecycle-message" id="lifecycle-msg-<?= $tid ?>"></div>
                        <div class="lifecycle-actions" id="lifecycle-actions-<?= $tid ?>"></div>
                    </div>
                    <?php endif; ?>

                    <?php if ($isProp): ?>
                    <!-- ── Account Status step — mirrors dashboard.php, prop firm trades only ── -->
                    <div class="acct-status-step" id="acctstep-<?= $tid ?>">
                        <div class="acct-status-title">What happened to the account after this trade?</div>
                        <div class="acct-status-options">
                            <span class="acct-status-opt selected" data-status="still_active" onclick="setAcctStatus('<?= $tid ?>','still_active',this)">Still Active</span>
                            <span class="acct-status-opt is-pass" data-status="passed_phase" onclick="setAcctStatus('<?= $tid ?>','passed_phase',this)" id="passopt-<?= $tid ?>" style="display:none">Passed Phase</span>
                            <span class="acct-status-opt is-breach" data-status="breached" onclick="setAcctStatus('<?= $tid ?>','breached',this)">Breached Account</span>
                        </div>
                        <div class="breach-reason-row" id="breachrow-<?= $tid ?>">
                            <label for="breachselect-<?= $tid ?>">Reason</label>
                            <select id="breachselect-<?= $tid ?>" onchange="document.getElementById('breachreason-<?= $tid ?>').value=this.value; handleBreachReasonChange('<?= $tid ?>')">
                                <option value="">Select reason...</option>
                                <optgroup label="Mandatory">
                                    <option value="MAXIMUM_DRAWDOWN">Maximum Drawdown</option>
                                    <option value="DAILY_LOSS_LIMIT">Daily Loss Limit</option>
                                    <?php if ($isChallengePhase): ?>
                                    <option value="PROFIT_TARGET">Profit Target</option>
                                    <?php endif; ?>
                                </optgroup>
                                <optgroup label="Optional">
                                    <option value="CONSISTENCY_RULE">Consistency Rule</option>
                                    <option value="RULE_VIOLATION">Rule Violation</option>
                                    <option value="NEWS_TRADING_RESTRICTION">News Trading Restriction</option>
                                    <option value="MANUAL_TRADING_ERROR">Manual Trading Error</option>
                                    <option value="OTHER">Other</option>
                                </optgroup>
                            </select>
                            <input type="text" id="breachother-<?= $tid ?>" placeholder="Specify reason..." style="display:none" oninput="updateBreachOtherText('<?= $tid ?>')">
                        </div>
                    </div>
                    <?php endif; ?>

                    <div class="resolve-actions">
                        <button type="button" class="btn-xs btn-cancel-trade" onclick="hideResolve('<?= $tid ?>')">Close</button>
                        <button type="submit" class="btn-xs btn-confirm" id="okbtn-<?= $tid ?>">Save Resolution</button>
                    </div>
                </form>
            </div>
            <?php endif; ?>
            </div><!-- /.dc-details -->
        </div>
        <?php endforeach; ?>
    </div>
    </div>

    <?php endforeach; ?>
    <?php endif; ?>
</main>

<script>
// Trader's real display name, for the personalized breach/pass messages
// below (2026-08-01 Drafts UX review, item 11) — escaped via escapeHtml()
// everywhere it's injected through innerHTML, since display_name is
// user-supplied text.
var TRADER_NAME = <?= json_encode($displayName) ?>;
function escapeHtml(str) {
    var div = document.createElement('div');
    div.textContent = str == null ? '' : String(str);
    return div.innerHTML;
}

/* Continue Draft / expand-collapse (2026-08-20 Drafts UX refinement, item
   5). The one clear primary action per card. Everything below the
   identity header (full price detail, entry facts, resolve/confirm
   controls) is progressively disclosed here rather than always shown, so
   a page full of drafts stays scannable instead of a stack of giant forms. */
function toggleDraftDetails(id) {
    var details = document.getElementById('dcdetails-' + id);
    var btn = document.getElementById('continuebtn-' + id);
    var label = document.getElementById('continuelabel-' + id);
    var arrow = document.getElementById('continuearrow-' + id);
    if (!details) return;
    var wasOpen = details.style.display !== 'none';
    details.style.display = wasOpen ? 'none' : 'block';
    if (btn) btn.classList.toggle('is-open', !wasOpen);
    if (label) label.textContent = wasOpen ? (btn.getAttribute('data-open-label') || 'Continue Draft') : 'Hide Details';
    if (arrow) arrow.textContent = wasOpen ? '→' : '↑';
    if (!wasOpen) {
        details.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
    }
}

/* Search / filter / sort toolbar (2026-08-20 Drafts UX refinement, item
   10). One unified mechanism: the urgency banner's "View urgent drafts"
   button and the toolbar's own Urgent pill both drive this same function,
   so there's a single source of truth for what's currently filtered
   rather than two competing systems. */
var currentDraftFilter = 'all';
function setDraftFilter(kind, el) {
    currentDraftFilter = kind;
    document.querySelectorAll('.filter-pill').forEach(function(p) { p.classList.remove('active'); });
    if (el) el.classList.add('active');
    applyDraftFilters();
    if (kind === 'urgent') {
        var first = document.querySelector('.draft-card.urgency-urgent');
        if (first) first.scrollIntoView({ behavior: 'smooth', block: 'center' });
    }
}
function clearAllDraftFilters() {
    var input = document.getElementById('draftSearchInput');
    if (input) input.value = '';
    setDraftFilter('all', document.querySelector('.filter-pill[data-filter="all"]'));
}
function applyDraftFilters() {
    var q = ((document.getElementById('draftSearchInput') || {}).value || '').trim().toLowerCase();
    document.querySelectorAll('.draft-card').forEach(function(card) {
        var searchable = (card.getAttribute('data-search') || '').toLowerCase();
        var direction = card.getAttribute('data-direction') || '';
        var matchesQuery = !q || searchable.indexOf(q) !== -1;
        var matchesFilter = true;
        if (currentDraftFilter === 'long') matchesFilter = direction === 'long';
        else if (currentDraftFilter === 'short') matchesFilter = direction === 'short';
        else if (currentDraftFilter === 'urgent') matchesFilter = card.classList.contains('urgency-urgent');
        card.style.display = (matchesQuery && matchesFilter) ? '' : 'none';
    });
    document.querySelectorAll('.draft-group').forEach(function(g) {
        var anyVisible = Array.prototype.some.call(g.querySelectorAll('.draft-card'), function(c) { return c.style.display !== 'none'; });
        g.style.display = anyVisible ? '' : 'none';
    });
    var anyCardVisible = Array.prototype.some.call(document.querySelectorAll('.draft-card'), function(c) { return c.style.display !== 'none'; });
    var noResults = document.getElementById('draftNoResults');
    if (noResults) noResults.style.display = anyCardVisible ? 'none' : 'block';
}
function draftSortUrl(sortVal) {
    var url = new URL(window.location.href);
    url.searchParams.set('sort', sortVal);
    return url.toString();
}

// Cancel an open (never-should-have-counted) trade — distinct from
// recording a real Lost outcome. Shown alongside Won/Lost, not buried
// behind a separate workflow (2026-08-01 Drafts UX review, item 10).
function cancelOpenTrade(id) {
    var ok = confirm('Cancel this trade? It will be removed from your active trades and marked as cancelled. This will NOT affect your account balance.\n\nOnly do this if the trade was entered in error or never should have been opened. If it actually closed, use Won or Lost instead to record the real outcome.');
    if (!ok) return;
    var form = document.getElementById('cancelform-' + id);
    if (form) form.submit();
}

function toggleTpLevels(id) {
    var grid = document.getElementById('tp-expanded-' + id);
    var btn = document.getElementById('tp-toggle-' + id);
    var label = document.getElementById('tp-toggle-label-' + id);
    if (!grid || !btn || !label) return;
    var remaining = btn.getAttribute('data-remaining') || '0';
    var isOpen = grid.style.display !== 'none';
    grid.style.display = isOpen ? 'none' : 'grid';
    btn.classList.toggle('is-open', !isOpen);
    label.textContent = isOpen
        ? ('+' + remaining + ' more target' + (remaining !== '1' ? 's' : ''))
        : 'Hide extra targets';
}

function resolveTrade(id, outcome, autoPnl, autoEntry, autoLots, tpPrice, slPrice) {
    document.getElementById('outcome-' + id).value = outcome;
    document.getElementById('pnl-' + id).value = autoPnl.toFixed(2);
    document.getElementById('entry-' + id).value = autoEntry.toFixed(5);
    document.getElementById('lots-' + id).value = autoLots.toFixed(4);
    document.getElementById('sl-' + id).value = slPrice ? slPrice.toFixed(5) : '';

    var exitPrice = outcome === 'won' ? tpPrice : slPrice;
    document.getElementById('exit-' + id).value = exitPrice ? exitPrice.toFixed(5) : '';

    // Show the plain Actual Exit Price field for a loss (pre-filled with SL,
    // same as the original behaviour); show the per-TP ladder for a win.
    var exitGroup = document.getElementById('exitgroup-' + id);
    var tpRows = document.querySelectorAll('.tp-row[data-tid="' + id + '"]');
    if (exitGroup && tpRows.length) {
        exitGroup.style.display = outcome === 'lost' ? 'flex' : 'none';
        tpRows.forEach(function(row) { row.style.display = outcome === 'lost' ? 'none' : 'flex'; });
    }

    // Reset any per-TP Hit checkboxes from a previous resolution attempt,
    // then default-select TP1 on a win (trader can still click Hit on a
    // different TP row if a later level was actually reached). TP inputs
    // stay at their pre-filled planned prices — only the checkbox state
    // and the shared exit field get reset here.
    document.querySelectorAll('input[id^="hit-tp"][id$="-' + id + '"]').forEach(function(cb) { cb.checked = false; });
    if (outcome === 'won') {
        var firstHitBox = document.getElementById('hit-tp1-' + id);
        var firstTpInput = document.getElementById('exit-tp1-' + id);
        if (firstHitBox && firstTpInput) {
            firstHitBox.checked = true;
            if (!firstTpInput.value) firstTpInput.value = exitPrice ? exitPrice.toFixed(5) : '';
        }
    }

    var form = document.getElementById('resolve-' + id);
    var okBtn = document.getElementById('okbtn-' + id);
    var badge = document.getElementById('badge-' + id);

    form.style.display = 'block';
    form.classList.remove('outcome-won', 'outcome-lost');
    okBtn.classList.remove('is-loss');

    if (outcome === 'lost') {
        form.classList.add('outcome-lost');
        okBtn.classList.add('is-loss');
        badge.textContent = 'LOST';
    } else {
        form.classList.add('outcome-won');
        badge.textContent = 'WON';
    }

    // Reveal the Account Status step (second step) now that Won/Lost is picked.
    // Use intelligent default selection based on account rules and trade result.
    var step = document.getElementById('acctstep-' + id);
    if (step) {
        step.classList.add('show');
        // Reset breach reason
        var reasonHidden = document.getElementById('breachreason-' + id);
        if (reasonHidden) reasonHidden.value = '';
        var row = document.getElementById('breachrow-' + id);
        if (row) row.classList.remove('show');
        var sel = document.getElementById('breachselect-' + id);
        if (sel) sel.value = '';
        var otherInput = document.getElementById('breachother-' + id);
        if (otherInput) {
            otherInput.style.display = 'none';
            otherInput.value = '';
        }
        
        // Intelligently select the best default account status
        intelligentlySelectAccountStatus(id, autoPnl);

        // Enforce lifecycle math on the auto-filled P&L too — Won/Lost sets
        // pnl-<id>.value programmatically, which does NOT fire the input
        // field's oninput handler, so without this call a mathematically
        // breached/passed account's status options were never locked and a
        // trader could freely pick a different status. checkLifecycleEnforcement
        // reads the value we just set above and disables/auto-selects as needed.
        checkLifecycleEnforcement(id);
    }

    var focusTarget = (outcome === 'lost') ? document.getElementById('exit-' + id) : (document.getElementById('exit-tp1-' + id) || document.getElementById('exit-' + id));
    if (focusTarget) focusTarget.focus();
}
function hideResolve(id) {
    var form = document.getElementById('resolve-' + id);
    form.style.display = 'none';
    form.classList.remove('outcome-won', 'outcome-lost');
    document.getElementById('okbtn-' + id).classList.remove('is-loss');
}
function markTpHit(id, tpNum, tpPrice, checkboxEl) {
    // Only one TP can be "the" hit level — uncheck any other TP row for this trade.
    document.querySelectorAll('input[id^="hit-tp"][id$="-' + id + '"]').forEach(function(cb) {
        if (cb !== checkboxEl) cb.checked = false;
    });

    var input = document.getElementById('exit-tp' + tpNum + '-' + id);
    if (checkboxEl.checked) {
        if (input && !input.value) input.value = tpPrice.toFixed(5);
        document.getElementById('exit-' + id).value = input ? input.value : tpPrice.toFixed(5);

        document.getElementById('outcome-' + id).value = 'won';
        var badge = document.getElementById('badge-' + id);
        if (badge) badge.textContent = 'WON';
        var form = document.getElementById('resolve-' + id);
        form.classList.remove('outcome-lost');
        form.classList.add('outcome-won');
        document.getElementById('okbtn-' + id).classList.remove('is-loss');
    } else {
        document.getElementById('exit-' + id).value = '';
    }
}
function syncTpExit(id) {
    // Keep the hidden actual_exit_price field in sync if the trader edits
    // the exit input on whichever TP row is currently marked Hit.
    var checked = document.querySelector('input[id^="hit-tp"][id$="-' + id + '"]:checked');
    if (!checked) return;
    var m = checked.id.match(/^hit-tp(\d+)-/);
    if (!m) return;
    var input = document.getElementById('exit-tp' + m[1] + '-' + id);
    if (input) document.getElementById('exit-' + id).value = input.value;
}

/* ── "Managed Exit" workflow (2026-08-03) ─────────────────────────────
   The R multiple is ONLY a prefill helper — it fills Exit Price/P&L
   exactly ONCE, on this onchange event, the same one-shot pattern
   resolveTrade() already uses for Won/Lost. Once filled, the trader owns
   those fields; nothing here keeps recomputing in the background. Actual
   Entry/Stop Loss/Exit Price/P&L remain the real source of truth — this
   never substitutes its own numbers for what the trader actually enters,
   it only offers a faster starting point. */
function applyManagedExit(id, value, plannedEntry, plannedSl, direction, riskAmount) {
    var exitClassEl = document.getElementById('exitclass-' + id);
    var declaredREl = document.getElementById('declaredr-' + id);
    var customWrap = document.getElementById('managedexitcustomwrap-' + id);
    var errorEl = document.getElementById('managedexiterror-' + id);
    if (errorEl) { errorEl.style.display = 'none'; errorEl.textContent = ''; }

    if (value === '') {
        exitClassEl.value = ''; declaredREl.value = '';
        if (customWrap) customWrap.style.display = 'none';
        return;
    }
    if (value === 'custom') {
        exitClassEl.value = ''; declaredREl.value = ''; // set by applyManagedExitCustom() once a valid number is entered
        if (customWrap) customWrap.style.display = '';
        return;
    }
    if (customWrap) customWrap.style.display = 'none';

    var entryEl = document.getElementById('entry-' + id);
    var exitEl = document.getElementById('exit-' + id);
    var slEl = document.getElementById('sl-' + id);
    var pnlEl = document.getElementById('pnl-' + id);

    if (value === 'breakeven') {
        // The deterministic rule (Actual SL == Actual Entry) is what the
        // server ultimately checks — this just expresses that same rule
        // through the fields it's based on, so the form is already
        // internally consistent before it's even submitted.
        exitClassEl.value = 'breakeven'; declaredREl.value = '';
        var entryVal = plannedEntry.toFixed(5);
        if (entryEl && !entryEl.value) entryEl.value = entryVal;
        var useEntry = (entryEl && entryEl.value) ? entryEl.value : entryVal;
        if (exitEl) exitEl.value = useEntry;
        if (slEl) slEl.value = useEntry;
        if (pnlEl) pnlEl.value = '0.00';
        checkBreakEvenHint(id);
        checkLifecycleEnforcement(id);
        return;
    }

    var r = parseFloat(value);
    if (isNaN(r) || r <= 0) return;
    applyManagedExitR(id, r, plannedEntry, plannedSl, direction, riskAmount);
}

function applyManagedExitR(id, r, plannedEntry, plannedSl, direction, riskAmount) {
    document.getElementById('exitclass-' + id).value = 'managed_exit';
    document.getElementById('declaredr-' + id).value = r.toFixed(3);

    // Same "scale two already-known real numbers" technique used
    // everywhere else in this app — never a new pip/contract-value
    // formula. riskDistance/riskAmount are the trade's own already-
    // computed planned figures.
    var riskDistance = Math.abs(plannedEntry - plannedSl);
    var impliedExit = direction === 'short' ? (plannedEntry - riskDistance * r) : (plannedEntry + riskDistance * r);
    var impliedPnl = riskAmount * r;

    var exitEl = document.getElementById('exit-' + id);
    var pnlEl = document.getElementById('pnl-' + id);
    if (exitEl) exitEl.value = impliedExit.toFixed(5);
    if (pnlEl) pnlEl.value = impliedPnl.toFixed(2);
    checkLifecycleEnforcement(id);
}

function applyManagedExitCustom(id, rawValue, plannedEntry, plannedSl, direction, riskAmount, plannedRr) {
    var errorEl = document.getElementById('managedexiterror-' + id);
    var exitClassEl = document.getElementById('exitclass-' + id);
    var declaredREl = document.getElementById('declaredr-' + id);
    var r = parseFloat(rawValue);

    if (rawValue === '' || isNaN(r) || r <= 0) {
        exitClassEl.value = ''; declaredREl.value = '';
        if (errorEl) errorEl.style.display = 'none';
        return;
    }
    // Strictly less than the planned RR — a value equal to or above it is
    // the Full TP, not a Managed Exit. Never clamped: an out-of-range
    // value is rejected here (and again, independently, on the server) —
    // never silently capped to the maximum.
    if (plannedRr > 0 && r >= plannedRr) {
        exitClassEl.value = ''; declaredREl.value = '';
        if (errorEl) {
            errorEl.style.display = '';
            errorEl.textContent = 'Invalid Managed Exit: this trade\'s planned target is ' + plannedRr.toFixed(2) + 'R. You entered ' + r.toFixed(2) + 'R, which ' + (r === plannedRr ? 'equals the planned Full TP, not a Managed Exit' : 'exceeds the planned target') + '. Please choose a value below the planned target, or edit the actual trade prices if your trade genuinely exceeded the original plan.';
        }
        return;
    }
    if (errorEl) errorEl.style.display = 'none';
    applyManagedExitR(id, r, plannedEntry, plannedSl, direction, riskAmount);
}

/* Live, non-blocking hint — fires on Entry/SL input regardless of whether
   the Managed Exit dropdown was ever touched, so a trader who manually
   moves their stop to entry always sees the deterministic rule coming,
   never a silent surprise at save time. This is advisory only: the actual
   authoritative check happens server-side in dashboard.php's
   resolve_trade_id handler, from the real submitted values, and always
   overrides whatever the Managed Exit dropdown says. */
function checkBreakEvenHint(id) {
    var entryEl = document.getElementById('entry-' + id);
    var slEl = document.getElementById('sl-' + id);
    var hintEl = document.getElementById('managedexithint-' + id);
    if (!entryEl || !slEl || !hintEl) return;
    var entryVal = parseFloat(entryEl.value);
    var slVal = parseFloat(slEl.value);
    if (!isNaN(entryVal) && !isNaN(slVal) && entryVal !== 0 && Math.abs(slVal - entryVal) <= Math.abs(entryVal) * 0.0005) {
        hintEl.style.display = '';
        hintEl.textContent = 'Note: Your Actual Stop Loss now equals your Entry Price. This trade will be recorded as Break Even, regardless of any Managed Exit selection above.';
    } else {
        hintEl.style.display = 'none';
    }
}

// Final safety net before submit — if a Managed Exit was selected but no
// valid R ever landed in the hidden field (e.g. Custom R left invalid),
// block the save rather than silently submitting an incomplete
// declaration. The real, authoritative bound-check still happens
// server-side regardless.
document.addEventListener('submit', function (e) {
    var form = e.target;
    if (!(form instanceof HTMLFormElement)) return;
    var exitClassInput = form.querySelector('input[name="exit_classification"]');
    if (!exitClassInput || exitClassInput.value !== 'managed_exit') return;
    var declaredRInput = form.querySelector('input[name="declared_r_multiple"]');
    var r = declaredRInput ? parseFloat(declaredRInput.value) : NaN;
    if (isNaN(r) || r <= 0) {
        e.preventDefault();
        alert('Please select a valid Managed Exit value before saving, or choose "Use exact entry/exit above" to skip it.');
    }
}, true);

function intelligentlySelectAccountStatus(id, tradePnl) {
    // Real mathematics only (2026-07-28 Reports finishing pass, item 4
    // regression fix) — these hidden fields carry the exact same
    // breach_level_amount / target_profit_amount / daily_drawdown.remaining_amount
    // figures AnalyticsEngine::getChallengeMetrics() computed at page-render
    // time, the same function dashboard.php's resolve_trade_id handler uses
    // to actually cap challenge_pnl. No independent client-side approximation
    // from stale per-trade snapshot percentages (see haschallengedata-<id>:
    // when real data isn't available — e.g. no challenge_accounts row — this
    // never guesses, it just leaves the trade at 'still_active').
    var hasData = document.getElementById('haschallengedata-' + id).value === '1';
    var recommendedStatus = 'still_active';

    if (hasData) {
        var liveBalance = parseFloat(document.getElementById('livebalance-' + id).value) || 0;
        var breachLevel = parseFloat(document.getElementById('breachlevel-' + id).value) || 0;
        var targetBalance = parseFloat(document.getElementById('targetbalance-' + id).value) || 0;
        var hasTarget = document.getElementById('hastarget-' + id).value === '1';
        var isChallengePhase = document.getElementById('ischallengephase-' + id).value === '1';

        // Projected balance after this trade, from the account's real
        // current balance — not the trade's own possibly-stale
        // balance_at_entry snapshot.
        var projectedBalance = liveBalance + tradePnl;

        // Breach lock = only the OVERALL max-drawdown boundary. Daily
        // drawdown is a warning, never a recommended status change — see
        // checkLifecycleEnforcement() for the warning itself.
        if (projectedBalance <= breachLevel) {
            recommendedStatus = 'breached';
        } else if (isChallengePhase && hasTarget && projectedBalance >= targetBalance) {
            // Pass lock = only real challenge phases (phase_1_active/
            // phase_2_active/phase_3_active). Funded and instant-funding
            // accounts have no profit target and never mathematically pass.
            recommendedStatus = 'passed_phase';
        }
    }

    // Apply the intelligent selection
    var hidden = document.getElementById('acctstatus-' + id);
    if (hidden) hidden.value = recommendedStatus;
    
    var step = document.getElementById('acctstep-' + id);
    if (step) {
        step.querySelectorAll('.acct-status-opt').forEach(function(el) {
            el.classList.toggle('selected', el.getAttribute('data-status') === recommendedStatus);
        });
    }
    
    // Show breach reason row if breached
    var row = document.getElementById('breachrow-' + id);
    if (recommendedStatus === 'breached') {
        if (row) row.classList.add('show');
    } else {
        if (row) row.classList.remove('show');
    }
}

function setAcctStatus(id, status, el) {
    document.getElementById('acctstatus-' + id).value = status;
    var step = el.closest('.acct-status-step');
    step.querySelectorAll('.acct-status-opt').forEach(function(opt) {
        opt.classList.toggle('selected', opt === el);
    });
    var row = document.getElementById('breachrow-' + id);
    if (status === 'breached') {
        row.classList.add('show');
    } else {
        row.classList.remove('show');
        document.getElementById('breachreason-' + id).value = '';
        var sel = document.getElementById('breachselect-' + id);
        if (sel) sel.value = '';
        var otherInput = document.getElementById('breachother-' + id);
        if (otherInput) {
            otherInput.style.display = 'none';
            otherInput.value = '';
        }
    }
}

function handleBreachReasonChange(id) {
    var select = document.getElementById('breachselect-' + id);
    var otherInput = document.getElementById('breachother-' + id);
    if (!select || !otherInput) return;
    if (select.value === 'OTHER') {
        otherInput.style.display = 'inline-block';
        updateBreachOtherText(id);
    } else {
        otherInput.style.display = 'none';
        otherInput.value = '';
    }
}

function updateBreachOtherText(id) {
    var otherInput = document.getElementById('breachother-' + id);
    var reasonHidden = document.getElementById('breachreason-' + id);
    if (otherInput && reasonHidden) {
        reasonHidden.value = otherInput.value;
    }
}

function checkLifecycleEnforcement(id) {
    var pnl = parseFloat(document.getElementById('pnl-' + id).value) || 0;
    var enforcementDiv = document.getElementById('lifecycle-' + id);
    var msgDiv = document.getElementById('lifecycle-msg-' + id);
    var actionsDiv = document.getElementById('lifecycle-actions-' + id);
    if (!enforcementDiv || !msgDiv || !actionsDiv) return;

    if (isNaN(pnl)) {
        hideAccountStatusOption(id, 'passed_phase');
        hideLifecycleEnforcement(id);
        enableAllAccountStatusOptions(id);
        return;
    }

    // Real mathematics only (2026-07-28 Reports finishing pass, item 4
    // regression fix) — see the matching comment in
    // intelligentlySelectAccountStatus() above. Without real challenge data
    // (haschallengedata-<id> === '0'), never guess a breach/pass — leave the
    // trader free to pick "Still Active".
    var hasDataEl = document.getElementById('haschallengedata-' + id);
    var hasData = hasDataEl && hasDataEl.value === '1';
    if (!hasData) {
        hideAccountStatusOption(id, 'passed_phase');
        hideLifecycleEnforcement(id);
        enableAllAccountStatusOptions(id);
        return;
    }

    var liveBalance = parseFloat(document.getElementById('livebalance-' + id).value) || 0;
    var breachLevel = parseFloat(document.getElementById('breachlevel-' + id).value) || 0;
    var dailyRemaining = parseFloat(document.getElementById('dailyremaining-' + id).value) || 0;
    var dailyLimit = parseFloat(document.getElementById('dailylimit-' + id).value) || 0;
    var targetBalance = parseFloat(document.getElementById('targetbalance-' + id).value) || 0;
    var hasTarget = document.getElementById('hastarget-' + id).value === '1';
    var isChallengePhase = document.getElementById('ischallengephase-' + id).value === '1';

    // Projected balance from the account's real current balance, not the
    // trade's own possibly-stale balance_at_entry snapshot.
    var projectedBalance = liveBalance + pnl;

    // Check for mathematical breach — OVERALL max drawdown only. Daily
    // drawdown is handled separately below as a non-locking warning; it
    // must never show "Mathematical Breach Detected" or disable any
    // account-status option.
    if (projectedBalance <= breachLevel) {
        var excessBreach = breachLevel - projectedBalance;
        // The account can already be past the breach line BEFORE this trade
        // (e.g. the trader picked "Continue Trading" on an earlier breach
        // instead of confirming it) — in that case a winning/breakeven trade
        // still leaves the balance below the line, but it didn't CAUSE the
        // breach, so it shouldn't read as "this result breaches the
        // account." Only a losing trade gets that framing; anything else
        // gets a neutral "still beyond the limit from earlier activity" one.
        if (pnl < 0) {
            msgDiv.innerHTML = '<strong>Unfortunately, ' + escapeHtml(TRADER_NAME) + ', this result places the account beyond its maximum permitted drawdown.</strong><br>The balance would land $' + excessBreach.toFixed(2) + ' past the account\'s maximum drawdown limit, and the account will be classified as breached. Confirm below, or continue trading only if there\'s a specific reason this doesn\'t apply.';
        } else {
            msgDiv.innerHTML = '<strong>This trade did not cause a breach, but the account\'s balance is still beyond its maximum permitted drawdown.</strong><br>Even with this result, the balance sits $' + excessBreach.toFixed(2) + ' past the drawdown limit, carried over from earlier trades. Confirm the breach to formally close out the account, or continue trading only if there\'s a specific reason this doesn\'t apply.';
        }
        actionsDiv.innerHTML = '<button type="button" class="btn-xs btn-danger" onclick="confirmLifecycleBreach(\'' + id + '\')">Confirm Breach</button> <button type="button" class="btn-xs btn-secondary" onclick="continueTrading(\'' + id + '\',\'breached\')">Continue Trading (Not Recommended)</button>';
        enforcementDiv.style.display = 'block';
        enforcementDiv.classList.add('is-breach');
        enforcementDiv.classList.remove('is-pass', 'is-daily-caution', 'is-daily-alert', 'is-daily-critical');

        // Disable invalid options
        disableAccountStatusOption(id, 'still_active', 'This trade would breach the account');
        disableAccountStatusOption(id, 'passed_phase', 'This trade would breach the account');
        enableAccountStatusOption(id, 'breached');
        hideAccountStatusOption(id, 'passed_phase');

        // Auto-select breached
        setAcctStatus(id, 'breached', document.querySelector('#acctstep-' + id + ' .acct-status-opt[data-status="breached"]'));
        return;
    }

    // Daily drawdown — progressive, non-locking warning tiers. Never locks
    // the account, never disables an account-status option, and never
    // shows "Mathematical Breach Detected" — only OVERALL drawdown (above)
    // does that. Prop firms reset their daily counter at different times
    // of day, so this deliberately never tells a trader to "stop for the
    // day" — it always points them at their own firm's reset timer instead.
    var dailyWarningShown = false;
    if (dailyLimit > 0) {
        // usedBeforeTrade: today's realized loss so far (already nets out
        // any other trades closed earlier today), floored at 0 for a
        // currently-flat-or-profitable day — same convention
        // getChallengeMetrics()'s daily_drawdown figures already use.
        var usedBeforeTrade = Math.max(0, dailyLimit - dailyRemaining);
        // This trade's effect on that figure: a loss adds to it, a win
        // reduces it (floored at 0 — a win can't produce negative "loss used").
        var usedAfterTrade = pnl < 0 ? (usedBeforeTrade + Math.abs(pnl)) : Math.max(0, usedBeforeTrade - pnl);
        var usedPct = (usedAfterTrade / dailyLimit) * 100;

        if (usedPct >= 50) {
            dailyWarningShown = true;
            enforcementDiv.style.display = 'block';
            actionsDiv.innerHTML = '<button type="button" class="btn-xs btn-secondary" onclick="hideLifecycleEnforcement(\'' + id + '\')">Dismiss</button>';
            enforcementDiv.classList.remove('is-breach', 'is-pass', 'is-daily-caution', 'is-daily-alert', 'is-daily-critical');

            if (usedPct >= 100) {
                msgDiv.innerHTML = '<strong>🔴 Daily Loss Limit Reached</strong><br>Daily loss limit reached ($' + usedAfterTrade.toFixed(2) + ' of $' + dailyLimit.toFixed(2) + '). This account may be breached depending on your prop firm\'s rules. Check your reset timer.';
                enforcementDiv.classList.add('is-daily-critical');
            } else if (usedPct >= 75) {
                msgDiv.innerHTML = '<strong>⚠️ Approaching Daily Loss Limit</strong><br>You\'re approaching your daily loss limit ($' + usedAfterTrade.toFixed(2) + ' of $' + dailyLimit.toFixed(2) + '). Check your prop firm\'s daily reset time; you may be able to continue after reset.';
                enforcementDiv.classList.add('is-daily-alert');
            } else {
                msgDiv.innerHTML = '<strong>⚠️ Daily Loss Limit: Trade Carefully</strong><br>You\'ve used over half your daily loss limit ($' + usedAfterTrade.toFixed(2) + ' of $' + dailyLimit.toFixed(2) + '). Trade carefully.';
                enforcementDiv.classList.add('is-daily-caution');
            }

            enableAllAccountStatusOptions(id);
            hideAccountStatusOption(id, 'passed_phase');
            // Fall through — a daily-loss warning doesn't stop the pass
            // check below from still running for this same trade.
        }
    }

    // Check for mathematical pass — only real challenge phases
    // (phase_1_active/phase_2_active/phase_3_active) with a real declared
    // profit target. Funded and instant-funding accounts never have a
    // profit target and can never mathematically "pass".
    if (isChallengePhase && hasTarget && projectedBalance >= targetBalance) {
        var excessProfit = projectedBalance - targetBalance;
        var phaseLabelEl = document.getElementById('phaselabel-' + id);
        var phaseLabel = (phaseLabelEl && phaseLabelEl.value) ? phaseLabelEl.value : 'this phase';
        // Symmetric to the breach fix above: the balance can already be past
        // the profit target BEFORE this trade (earlier wins the trader chose
        // to keep trading through instead of confirming). A losing trade
        // that still leaves the balance above target didn't "clear" anything
        // — it just didn't undo an earlier pass — so it must never read as a
        // congratulatory message tied to THIS result. Only pnl > 0 gets that
        // framing; a loss (or breakeven) gets a neutral, factual one.
        if (pnl > 0) {
            msgDiv.innerHTML = '<strong>Congratulations, ' + escapeHtml(TRADER_NAME) + ', you have cleared ' + escapeHtml(phaseLabel) + '\'s profit target.</strong><br>This result clears the target by $' + excessProfit.toFixed(2) + '. Confirming below marks ' + escapeHtml(phaseLabel) + ' as complete. Promote the account to the next phase from Trading Center afterward to preserve this milestone.';
        } else {
            msgDiv.innerHTML = '<strong>This trade resulted in a loss, but ' + escapeHtml(phaseLabel) + '\'s profit target remains cleared.</strong><br>The balance is still $' + excessProfit.toFixed(2) + ' above the target from earlier trades, even after this loss. You can confirm the phase as passed based on overall performance, or continue trading.';
        }
        actionsDiv.innerHTML = '<button type="button" class="btn-xs btn-success" onclick="confirmLifecyclePass(\'' + id + '\')">Confirm Pass - Mark Complete</button> <button type="button" class="btn-xs btn-secondary" onclick="continueTrading(\'' + id + '\',\'passed_phase\')">Continue Trading (Not Recommended)</button>';
        enforcementDiv.style.display = 'block';
        enforcementDiv.classList.add('is-pass');
        enforcementDiv.classList.remove('is-breach', 'is-daily-caution', 'is-daily-alert', 'is-daily-critical');

        // Disable invalid options
        disableAccountStatusOption(id, 'still_active', 'This trade clears the profit target');
        disableAccountStatusOption(id, 'breached', 'This trade clears the profit target');
        enableAccountStatusOption(id, 'passed_phase');
        showAccountStatusOption(id, 'passed_phase');

        // Auto-select passed_phase
        setAcctStatus(id, 'passed_phase', document.querySelector('#acctstep-' + id + ' .acct-status-opt[data-status="passed_phase"]'));
        return;
    }

    // No mathematical boundary crossed — reset "Passed Phase" back to
    // hidden. Leave the daily-loss warning banner (if any) on screen
    // instead of yanking it away the instant this function re-runs —
    // the trader dismisses it explicitly via the Dismiss button.
    hideAccountStatusOption(id, 'passed_phase');
    if (!dailyWarningShown) {
        hideLifecycleEnforcement(id);
    }
    enableAllAccountStatusOptions(id);
}

function hideLifecycleEnforcement(id) {
    var enforcementDiv = document.getElementById('lifecycle-' + id);
    if (enforcementDiv) {
        enforcementDiv.style.display = 'none';
        enforcementDiv.classList.remove('is-breach', 'is-pass', 'is-daily-caution', 'is-daily-alert', 'is-daily-critical');
    }
}

function enableAllAccountStatusOptions(id) {
    var step = document.getElementById('acctstep-' + id);
    if (!step) return;
    step.querySelectorAll('.acct-status-opt').forEach(function(el) {
        el.style.pointerEvents = '';
        el.style.opacity = '';
        el.title = '';
    });
}

function disableAccountStatusOption(id, status, reason) {
    var step = document.getElementById('acctstep-' + id);
    if (!step) return;
    var opt = step.querySelector('.acct-status-opt[data-status="' + status + '"]');
    if (opt) {
        opt.style.pointerEvents = 'none';
        opt.style.opacity = '0.4';
        opt.title = reason || 'Option disabled due to lifecycle enforcement';
    }
}

function enableAccountStatusOption(id, status) {
    var step = document.getElementById('acctstep-' + id);
    if (!step) return;
    var opt = step.querySelector('.acct-status-opt[data-status="' + status + '"]');
    if (opt) {
        opt.style.pointerEvents = '';
        opt.style.opacity = '';
        opt.title = '';
    }
}

// "Passed Phase" is hidden by default for challenge phases and only revealed
// once the math confirms projected_balance >= profit_target — this stops
// traders from clicking it early and polluting stats with fake passes.
// Funded/instant accounts never call showAccountStatusOption for this status,
// so it stays hidden (its default state, set in the PHP markup) for them.
function showAccountStatusOption(id, status) {
    var step = document.getElementById('acctstep-' + id);
    if (!step) return;
    var opt = step.querySelector('.acct-status-opt[data-status="' + status + '"]');
    if (opt) opt.style.display = '';
}

function hideAccountStatusOption(id, status) {
    var step = document.getElementById('acctstep-' + id);
    if (!step) return;
    var opt = step.querySelector('.acct-status-opt[data-status="' + status + '"]');
    if (opt) opt.style.display = 'none';
}

function confirmLifecycleBreach(id) {
    hideLifecycleEnforcement(id);
    // Breached is already selected and enabled
    // Show breach reason row
    var row = document.getElementById('breachrow-' + id);
    if (row) row.classList.add('show');
}

function confirmLifecyclePass(id) {
    hideLifecycleEnforcement(id);
    // Passed phase is already selected and enabled
}

function continueTrading(id, mathStatus) {
    // "Continue Trading" never restores "Still Active" — the math already
    // determined the outcome. This only asks WHY the trader is continuing,
    // then locks the account to the mathematically correct status.
    var msgDiv = document.getElementById('lifecycle-msg-' + id);
    var actionsDiv = document.getElementById('lifecycle-actions-' + id);
    if (!msgDiv || !actionsDiv) return;

    // Freeze/hide the default breach-reason selector while the dedicated
    // Continue Trading reason input is showing, so only one reason input
    // is ever visible at a time.
    var defaultReasonRow = document.getElementById('breachrow-' + id);
    if (defaultReasonRow) defaultReasonRow.classList.remove('show');

    msgDiv.innerHTML = '<strong>Why are you continuing to trade this account?</strong>';
    actionsDiv.innerHTML =
        '<select id="continueReasonSelect-' + id + '" onchange="document.getElementById(\'continueReasonOther-' + id + '\').style.display = this.value===\'OTHER\' ? \'inline-block\' : \'none\';">' +
        '<option value="">Select a reason...</option>' +
        '<option value="Prop firm changed rules">Prop firm changed rules</option>' +
        '<option value="Increased drawdown limit">Increased drawdown limit</option>' +
        '<option value="Personal decision">Personal decision</option>' +
        '<option value="OTHER">Other</option>' +
        '</select> ' +
        '<input type="text" id="continueReasonOther-' + id + '" placeholder="Describe reason..." style="display:none">' +
        '<button type="button" class="btn-xs btn-secondary" onclick="submitContinueReason(\'' + id + '\',\'' + mathStatus + '\')">Confirm &amp; Continue</button> ' +
        '<button type="button" class="btn-xs btn-ghost" onclick="checkLifecycleEnforcement(\'' + id + '\')">Back</button>';
}

function submitContinueReason(id, mathStatus) {
    var sel = document.getElementById('continueReasonSelect-' + id);
    if (!sel) return;
    var reason = sel.value;
    if (reason === 'OTHER') {
        var other = document.getElementById('continueReasonOther-' + id);
        reason = other ? other.value.trim() : '';
    }
    if (!reason) {
        alert('Please select or enter a reason for continuing to trade.');
        return;
    }

    // Lock in the ONLY mathematically valid status. "Still Active" and the
    // other boundary status stay disabled — the trader is choosing to keep
    // trading, not changing what happened.
    var correctOpt = document.querySelector('#acctstep-' + id + ' .acct-status-opt[data-status="' + mathStatus + '"]');
    setAcctStatus(id, mathStatus, correctOpt);
    disableAccountStatusOption(id, 'still_active', mathStatus === 'breached' ? 'This trade would breach the account' : 'This trade clears the profit target');
    if (mathStatus === 'breached') {
        disableAccountStatusOption(id, 'passed_phase', 'This trade would breach the account');
        enableAccountStatusOption(id, 'breached');
        document.getElementById('breachreason-' + id).value = reason;
        // setAcctStatus reveals the manual breach-reason row; we've already
        // captured the reason above, so keep the UI uncluttered.
        var row = document.getElementById('breachrow-' + id);
        if (row) row.classList.remove('show');
    } else {
        disableAccountStatusOption(id, 'breached', 'This trade clears the profit target');
        enableAccountStatusOption(id, 'passed_phase');
    }

    hideLifecycleEnforcement(id);
}

// Require confirmation before submit based on account status — mirrors dashboard.php
document.addEventListener('submit', function(e) {
    var form = e.target;
    if (!form || !form.querySelector) return;
    var acctStatusInput = form.querySelector('input[name="account_status"]');
    if (!acctStatusInput) return;
    
    var status = acctStatusInput.value;
    var confirmationMessage = '';
    
    switch(status) {
        case 'still_active':
            confirmationMessage = 'Are you sure you want to keep this account as Still Active?';
            break;
        case 'passed_phase':
            confirmationMessage = 'Are you sure you want to mark this account as Passed Phase? This will complete the current phase and create the next account.';
            break;
        case 'breached':
            var reasonInput = form.querySelector('input[name="breach_reason"]');
            if (!reasonInput || !reasonInput.value) {
                e.preventDefault();
                alert('Please select a reason for the breach before confirming.');
                return;
            }
            confirmationMessage = 'Are you sure you want to mark this account as Breached? This action will permanently close this account lifecycle.';
            break;
        default:
            return; // No confirmation needed for unknown status
    }
    
    if (confirmationMessage && !confirm(confirmationMessage)) {
        e.preventDefault();
    }
}, true);

(function() {
    function formatCountdown(secondsLeft) {
        if (secondsLeft <= 0) return 'Time exceeded';
        var m = Math.floor(secondsLeft / 60);
        var s = secondsLeft % 60;
        return m + 'm ' + String(s).padStart(2, '0') + 's remaining';
    }

    function updateCountdowns() {
        document.querySelectorAll('.countdown[data-since]').forEach(function(el) {
            var since = parseInt(el.dataset.since, 10);
            var limit = parseInt(el.dataset.limit, 10);
            var now = Math.floor(Date.now() / 1000);
            var elapsed = now - since;
            var remaining = limit - elapsed;
            el.textContent = formatCountdown(remaining);
            
            var badge = el.closest('.urg-badge');
            if (badge && remaining <= 0) {
                badge.className = 'urg-badge urg-red';
                el.textContent = 'Time exceeded, close or review';
                el.closest('.draft-card')?.classList.add('urgency-urgent');
                el.closest('.draft-card')?.classList.remove('urgency-none');
            } else if (badge && remaining <= 600) {
                badge.className = 'urg-badge urg-amber';
            }
        });
    }

    updateCountdowns();
    setInterval(updateCountdowns, 30000);
})();
</script>
</body>
</html>