<?php
/**
 * Trading Center — the pre-trade operational hub, and the first step of
 * the redesigned three-module workflow:
 *
 *   Trading Center (account selection) → Strategies (strategy + checklist)
 *   → Calculator (execution)
 *
 * public/checklist.php (the old combined account+strategy+checklist page)
 * was deleted in the 2026-08-01 Trading Center/Strategies redesign — its
 * account-selection responsibilities live here; strategy selection and the
 * checklist itself moved to public/strategies.php. This file owns account
 * selection, account summaries, account management, and promotion only —
 * never strategy or checklist concerns (the "Strategies" tile links to the
 * existing, untouched /strategy editor for strategy CRUD; each account's
 * "Continue to Strategies →" button links to /strategies?account_id=X for
 * the actual pre-trade flow).
 *
 * Single-file, GET-param-driven subviews — the same convention already
 * established by public/journal.php (?workspace=) in this codebase, rather
 * than exploding into many small route files.
 *
 * URL scheme:
 *   /trading-center
 *   ?hub=accounts
 *   ?hub=accounts&kind=prop|live
 *   ?hub=accounts&kind=prop&view=center
 *   ?hub=accounts&kind=prop&view=center&challenge=<type>
 *   ?hub=accounts&kind=prop&view=center&challenge=<type>&stage=<stage>[&account_id=X]
 *   ?hub=accounts&kind=live&view=center&exec=<model>[&account_id=X]
 *
 * Account creation and live-account deposit/withdraw/close are deliberately
 * NOT rendered by this file (2026-08-01 architectural-separation refactor —
 * see CLAUDE.md). They're independent pages: /add-prop-account,
 * /add-live-account, /manage-live-accounts. ?hub=accounts&kind=live&view=manage
 * is a legacy shape that 302-redirects straight to /manage-live-accounts
 * (see the top of the request-state block below) rather than rendering
 * anything here — kept only so old bookmarks/links don't 404.
 *
 * The account picker (search / recently-active-first / first-5 + Show
 * More) is NOT reimplemented here — every bucket below calls
 * apFetchAccounts()/apRenderAccountPicker() from src/AccountPicker.php,
 * the same shared component public/journal.php's own "Right Now"
 * workspace uses (see that file's renderRnAccountPicker(), which now
 * delegates to the exact same apRenderAccountPicker()).
 */

require_once __DIR__ . '/../src/PropFirmRuleEngine.php';
require_once __DIR__ . '/../src/AccountSummaryView.php';

$userId = $auth->requireAuth();

// ── Validation Workflow — data scoping, NOT a redesign (2026-08-16/17
// workflow-isolation-leakage pass) ──────────────────────────────────────
// Explicit correction from Kevin mid-session: Trading Center's page
// structure/UI must stay IDENTICAL across both workflows — Quick Access,
// the Prop Firms/Live Accounts hub, challenge-type/phase browsing, the
// broker grid, Account Home, promotion, Additional Details are all
// unchanged, for both workflows, same functions, same look. What must
// change is only the underlying DATA those same components read: every
// trade-derived figure (Total Trades, and the last-activity timestamp that
// drives Quick Access/broker-grid ordering) must reflect the CURRENT
// workflow's trades, not an unconditional main-workflow-only or fully
// unscoped read. Accounts themselves are correctly NOT filtered by
// workflow here — accounts are real, shared objects (a validation trade is
// a real trade on a real account, same as everywhere else in this app;
// see CLAUDE.md's "accounts are universal" principle) and a validating
// user must still be able to pick ANY of their real accounts, including
// one with zero validation trades yet, to journal their very first
// validation trade on. $tcWorkflowState is threaded into
// apFetchAccounts()/apAccountTradeCount() below (both in
// src/AccountPicker.php) so their trade-derived reads scope correctly.
$tcWorkflowState = ValidationService::getWorkflowState($userId, $db);

// ── Request state ───────────────────────────────────────────────────────
$VALID_CHALLENGE_TYPES = ['one_step', 'two_step', 'three_step', 'instant_funding'];

$hub = $_GET['hub'] ?? null;
$kind = in_array($_GET['kind'] ?? '', ['prop', 'live'], true) ? $_GET['kind'] : null;
$view = $_GET['view'] ?? null;
$challenge = in_array($_GET['challenge'] ?? '', $VALID_CHALLENGE_TYPES, true) ? $_GET['challenge'] : null;
$stage = $_GET['stage'] ?? null;
// Broker grouping (Live Accounts) replaced execution-model grouping this
// session — see the LIVE ACCOUNTS section below for the full reasoning.
// A raw, trimmed string (not an allow-listed enum like $challenge) because
// broker names are real trader-entered data, not a fixed set.
$broker = isset($_GET['broker']) ? trim((string)$_GET['broker']) : null;
if ($broker === '') $broker = null;
$accountId = $_GET['account_id'] ?? null;

// Architectural-separation refactor: "Manage Live Accounts" is no longer a
// picker+Account-Home flow owned by this file — it's the standalone
// manage-live-accounts.php page (deposit/withdraw/close tools only, no
// prop-firm UI, no Add Account form). Old ?view=manage links/bookmarks
// redirect there rather than 404ing or rendering a dead branch.
if ($hub === 'accounts' && $kind === 'live' && $view === 'manage') {
    $target = '/traderalign/manage-live-accounts' . ($accountId ? ('?account_id=' . urlencode($accountId)) : '');
    header('Location: ' . $target);
    exit;
}

// Live Accounts redesign: the old 3-tile "Add Live Account / Manage Live
// Accounts / Trading Center" chooser page (kind=live, view=null) is gone —
// clicking "Live Accounts" now lands straight on the broker-grouped Trading
// Center view below (both "Add" and "Manage" now live there too, as small
// utility links rather than big competing tiles). Old bookmarks/links to the
// bare kind=live URL redirect straight through instead of rendering a now-
// nonexistent page.
if ($hub === 'accounts' && $kind === 'live' && $view === null) {
    header('Location: ' . tcUrl(['hub' => 'accounts', 'kind' => 'live', 'view' => 'center']));
    exit;
}

// Old execution-model-scoped URLs (?exec=...) predate the switch to broker
// grouping. An account_id can still be honored directly (redirect straight
// to its real broker so the deep link keeps working); without one there's
// no honest broker to redirect into, so it drops to the broker grid root.
if ($hub === 'accounts' && $kind === 'live' && $view === 'center' && isset($_GET['exec'])) {
    $target = ['hub' => 'accounts', 'kind' => 'live', 'view' => 'center'];
    if ($accountId) {
        $row = $db->raw("SELECT label FROM trading_accounts WHERE id = :id AND user_id = :uid AND workflow_context = :wfctx AND deleted_at IS NULL", [':id' => $accountId, ':uid' => $userId, ':wfctx' => $tcWorkflowState]);
        if ($row) { $target['broker'] = tcParseBroker($row[0]['label']); $target['account_id'] = $accountId; }
    }
    header('Location: ' . tcUrl($target));
    exit;
}

// "Add Account / Continue Attempt" merge: the per-challenge-type stage grid
// no longer renders a standalone "Continue Existing Attempt" tile (see the
// tile-grid branch below) — that job now lives on add-prop-account.php,
// scoped via ?challenge=X&mode=continue, alongside the "add a new one"
// form. A bare ?stage=continue link with NO account_id (an old bookmark, or
// the Phase 1/2/3 hint text before this session) redirects there instead of
// rendering a tile-less picker page. ?stage=continue WITH an account_id is
// untouched — that's the fast path Quick Access's single-account shortcuts
// and add-prop-account.php's own picker both rely on, and it still lands
// directly on this file's real Account Home/Promote flow, unchanged.
if ($hub === 'accounts' && $kind === 'prop' && $view === 'center' && $challenge !== null && $stage === 'continue' && !$accountId) {
    header('Location: /traderalign/add-prop-account?challenge=' . urlencode($challenge) . '&mode=continue');
    exit;
}


/**
 * The broker name a live account belongs to — parsed from the same
 * "{Broker} - {Nickname}" label convention api/accounts/create.php has
 * always written live account labels in. A label with no " - " separator
 * (hand-edited, or predating that convention) just becomes its own
 * single-account "broker" group rather than an artificial "Uncategorized"
 * bucket — every account always parses to *something* real, never a
 * fabricated default.
 */
function tcParseBroker(string $label): string {
    return trim(explode(' - ', $label, 2)[0]);
}

function tcUrl(array $params): string {
    $params = array_filter($params, fn($v) => $v !== null && $v !== '');
    return '/traderalign/trading-center' . (empty($params) ? '' : ('?' . http_build_query($params)));
}

/**
 * Every active bucket (Phase 1/2/3, Funded, Instant Funded), plus Continue
 * Existing Attempt and Breached, per challenge type. Still the full,
 * authoritative bucket list — used for direct-URL routing (?stage=continue
 * still works, see the request-state block below), Quick Access grouping,
 * and the "accounts that completed a phase appear under..." hint text.
 * 'continue's lifecycle_in now delegates to apPromotableLifecycleStates()
 * (src/AccountPicker.php) rather than repeating the same states here —
 * that's also what public/add-prop-account.php's new Continue Existing
 * Attempt picker queries, so the two can never drift apart.
 *
 * NOT the same as the rendered stage TILE GRID below (kind=prop, view=
 * center, challenge=X, stage=null) — that grid deliberately omits the
 * 'continue' entry for one_step/two_step/three_step and shows a combined
 * "Add or Continue" tile (→ add-prop-account.php?challenge=X) in its place;
 * instant_funding's grid is untouched (it has no 'continue' entry here
 * either, so there was nothing to remove from it).
 */
function tcStageButtonsForChallenge(string $challengeType): array {
    return match ($challengeType) {
        'one_step' => [
            ['stage' => 'phase_1', 'label' => 'Phase 1', 'lifecycle_in' => ['phase_1_active']],
            ['stage' => 'funded', 'label' => 'Funded', 'lifecycle_in' => ['funded_active']],
            ['stage' => 'continue', 'label' => 'Continue Existing Attempt', 'lifecycle_in' => apPromotableLifecycleStates('one_step')],
            ['stage' => 'breached', 'label' => 'Breached', 'lifecycle_in' => ['breached']],
        ],
        'two_step' => [
            ['stage' => 'phase_1', 'label' => 'Phase 1', 'lifecycle_in' => ['phase_1_active']],
            ['stage' => 'phase_2', 'label' => 'Phase 2', 'lifecycle_in' => ['phase_2_active']],
            ['stage' => 'continue', 'label' => 'Continue Existing Attempt', 'lifecycle_in' => apPromotableLifecycleStates('two_step')],
            ['stage' => 'funded', 'label' => 'Funded', 'lifecycle_in' => ['funded_active']],
            ['stage' => 'breached', 'label' => 'Breached', 'lifecycle_in' => ['breached']],
        ],
        'three_step' => [
            ['stage' => 'phase_1', 'label' => 'Phase 1', 'lifecycle_in' => ['phase_1_active']],
            ['stage' => 'phase_2', 'label' => 'Phase 2', 'lifecycle_in' => ['phase_2_active']],
            ['stage' => 'phase_3', 'label' => 'Phase 3', 'lifecycle_in' => ['phase_3_active']],
            ['stage' => 'continue', 'label' => 'Continue Existing Attempt', 'lifecycle_in' => apPromotableLifecycleStates('three_step')],
            ['stage' => 'funded', 'label' => 'Funded', 'lifecycle_in' => ['funded_active']],
            ['stage' => 'breached', 'label' => 'Breached', 'lifecycle_in' => ['breached']],
        ],
        'instant_funding' => [
            ['stage' => 'instant_funded', 'label' => 'Instant Funded', 'lifecycle_in' => ['funded_active']],
            ['stage' => 'breached', 'label' => 'Breached', 'lifecycle_in' => ['breached']],
        ],
        default => [],
    };
}

$stageDef = null;
if ($challenge !== null && $stage !== null) {
    foreach (tcStageButtonsForChallenge($challenge) as $btn) {
        if ($btn['stage'] === $stage) { $stageDef = $btn; break; }
    }
}

// ── Breadcrumb ───────────────────────────────────────────────────────────
$crumbs = [['Trading Center', $hub === null ? null : tcUrl([])]];
if ($hub === 'accounts') {
    $crumbs[] = ['Accounts', $kind === null ? null : tcUrl(['hub' => 'accounts'])];
    if ($kind === 'prop') {
        $crumbs[] = ['Prop Firms', $view === null ? null : tcUrl(['hub' => 'accounts', 'kind' => 'prop'])];
        if ($view === 'center') {
            $crumbs[] = ['Trading Center', $challenge === null ? null : tcUrl(['hub' => 'accounts', 'kind' => 'prop', 'view' => 'center'])];
            if ($challenge !== null) {
                $crumbs[] = [apChallengeTypeLabel($challenge), $stage === null ? null : tcUrl(['hub' => 'accounts', 'kind' => 'prop', 'view' => 'center', 'challenge' => $challenge])];
                if ($stageDef !== null) {
                    $crumbs[] = [$stageDef['label'], $accountId === null ? null : tcUrl(['hub' => 'accounts', 'kind' => 'prop', 'view' => 'center', 'challenge' => $challenge, 'stage' => $stage])];
                    if ($accountId !== null) $crumbs[] = ['Account Home', null];
                }
            }
        }
    } elseif ($kind === 'live') {
        // No separate "Live Accounts" hub page anymore — it and "Trading
        // Center" are the same destination now, so this is one crumb, not
        // two (unlike Prop Firms, which still has a real intermediate page).
        $crumbs[] = ['Live Accounts', $broker === null ? null : tcUrl(['hub' => 'accounts', 'kind' => 'live', 'view' => 'center'])];
        if ($broker !== null) {
            $crumbs[] = [$broker, $accountId === null ? null : tcUrl(['hub' => 'accounts', 'kind' => 'live', 'view' => 'center', 'broker' => $broker])];
            if ($accountId !== null) $crumbs[] = ['Account Home', null];
        }
    }
}

function tcRenderBreadcrumb(array $crumbs): void {
    ?>
    <div class="tc-breadcrumb">
        <?php foreach ($crumbs as $i => $c): [$label, $url] = $c; ?>
            <?php if ($i > 0): ?><span class="tc-breadcrumb-sep">/</span><?php endif; ?>
            <?php if ($url): ?><a href="<?= htmlspecialchars($url) ?>"><?= htmlspecialchars($label) ?></a>
            <?php else: ?><span class="tc-breadcrumb-current"><?= htmlspecialchars($label) ?></span><?php endif; ?>
        <?php endforeach; ?>
    </div>
    <?php
}

/**
 * @param array $tiles [['label'=>,'sub'=>,'url'=>,'count'=>?int,'icon'=>?string,
 *   'glow_count'=>?int,'tooltip'=>?string], ...]
 * glow_count renders a small pulsing "🔥 N Ready" badge in the tile's corner
 * when > 0 — used by the "Add or Continue Attempt" tile below to surface
 * "something here needs your attention" without a second, separately-
 * clickable element nested inside the tile's own <a> (invalid HTML, and not
 * needed — see tcBuildAddOrContinueTile()'s own doc-comment for why the
 * whole tile's destination already adapts to this count instead).
 * tooltip renders as a plain native title="" attribute on the tile — for a
 * fact worth stating in full but not worth a permanent visible line (e.g.
 * the Strategies tile's own reminder that a loaded account is tradeable
 * from there too, not just an analytics-context switcher). No icon, no
 * visible change to the tile at rest — deliberately invisible until
 * hovered, so it adds real information without adding clutter.
 */
function tcRenderTileGrid(array $tiles): void {
    ?>
    <div class="tc-tile-grid">
        <?php foreach ($tiles as $t): ?>
        <a class="tc-tile<?= !empty($t['glow_count']) ? ' tc-tile-glowing' : '' ?>" href="<?= htmlspecialchars($t['url']) ?>"<?= !empty($t['tooltip']) ? ' title="' . htmlspecialchars($t['tooltip']) . '"' : '' ?>>
            <?php if (!empty($t['glow_count'])): ?>
            <span class="tc-tile-glow-badge"><span class="tc-tile-glow-dot"></span>🔥 <?= (int)$t['glow_count'] ?> Ready to Promote</span>
            <?php endif; ?>
            <span class="tc-tile-icon"><?= $t['icon'] ?? '▸' ?></span>
            <span class="tc-tile-label"><?= htmlspecialchars($t['label']) ?></span>
            <?php if (!empty($t['sub'])): ?><span class="tc-tile-sub"><?= htmlspecialchars($t['sub']) ?></span><?php endif; ?>
            <?php if (isset($t['count'])): ?><span class="tc-tile-count"><?= (int)$t['count'] ?> account<?= $t['count'] === 1 ? '' : 's' ?></span><?php endif; ?>
        </a>
        <?php endforeach; ?>
    </div>
    <?php
}

/**
 * The combined "Add or Continue Attempt" tile — replaces the old standalone
 * "Continue Existing Attempt" stage-grid tile for one_step/two_step/
 * three_step (instant_funding is untouched: it never had a 'continue'
 * bucket to replace, and still doesn't get this tile — see the challenge-
 * type stage-grid branch below, which only calls this for the other three).
 *
 * The tile's destination is context-aware rather than fixed: with nothing
 * ready to promote, it opens add-prop-account.php straight into the "Start
 * New Challenge Attempt" form (challenge type pre-filled); the moment at
 * least one account of this type has completed a phase, the SAME tile
 * starts glowing (🔥 N Ready to Promote) and its default destination
 * switches to that page's Continue Existing Attempt panel instead — because
 * that's clearly the more urgent of the two actions once something is
 * actually waiting. Either way, both actions are always one click away on
 * the landing page itself (its own mode selector), so nothing is ever
 * hidden — only which one greets you first changes.
 */
function tcBuildAddOrContinueTile(string $challenge, int $promotableCount, string $url): array {
    return [
        'label' => 'Add or Continue Attempt',
        'sub' => $promotableCount > 0
            ? ($promotableCount === 1 ? '1 account ready to promote' : $promotableCount . ' accounts ready to promote')
            : 'Start a new evaluation, or promote one once it\'s ready',
        'url' => $url . ($promotableCount > 0 ? '&mode=continue' : ''),
        'icon' => $promotableCount > 0 ? '🔥' : '➕',
        'glow_count' => $promotableCount,
    ];
}

function tcEmptyState(string $message, ?string $actionLabel = null, ?string $actionUrl = null): void {
    ?>
    <div class="tc-empty">
        <p><?= htmlspecialchars($message) ?></p>
        <?php if ($actionLabel && $actionUrl): ?><a class="btn btn-outline btn-md" href="<?= htmlspecialchars($actionUrl) ?>"><?= htmlspecialchars($actionLabel) ?></a><?php endif; ?>
    </div>
    <?php
}

/** Sort key for "how recently was this account actually traded" — falls back to when it was created for an account that's never had a trade yet, so untouched-but-new accounts still rank ahead of untouched-and-old ones rather than tying at zero. Used by the broker grid's own "most actively traded first" ordering (tcBuildBrokerGroups()) — NOT by Quick Access, which orders by selection history instead (see tcSelectionKey() below). */
function tcActivityKey(array $account): string {
    return (string)($account['last_activity'] ?? $account['created_at'] ?? '');
}

/**
 * Sort key for "how recently was this account explicitly SELECTED" — what
 * Quick Access orders by. Sourced from trading_accounts.last_selected_seq
 * (a real, atomically-incremented monotonic counter, stamped by
 * apRecordAccountSelection() — see that function's own doc-comment,
 * src/AccountPicker.php, for why this is last_selected_seq and NOT
 * last_selected_at: the timestamp alone ties when two selections land
 * within the same second, which broke strict MRU ordering). Zero-padded to
 * a fixed 20-digit string so the strcmp()-based sorts everywhere this key
 * is used compare correctly regardless of how many digits the real sequence
 * number has (plain numeric strings of different lengths sort wrong under
 * strcmp — "9" > "10" lexicographically — padding avoids that entirely).
 * Callers have already filtered out accounts with no value here (see
 * tcBuildQuickAccessCards()) — an unselected account has no business being
 * ranked at all, so there's no fallback the way tcActivityKey() has.
 */
function tcSelectionKey(array $account): string {
    return sprintf('%020d', (int)($account['last_selected_seq'] ?? 0));
}

/**
 * Quick Access — the actual usability fix this session is about. The
 * Accounts hub (kind === null, below) has always forced every visit through
 * Prop Firms/Live Accounts → Trading Center → challenge type or execution
 * model → stage → account picker before landing anywhere real. This builds
 * a short, personalized list of direct shortcuts to whatever the trader
 * actually has open right now, computed fresh from their own real accounts
 * (apFetchAccounts() — the same fetch every other bucket on this page
 * already uses, not a new query shape) rather than a fixed/generic menu.
 *
 * Card identity, 2026-08-18 (second fix, same day) — every card is EXACTLY
 * one account_id, full stop, prop or live alike. The prior version grouped
 * prop-firm accounts by (challenge type, stage) — e.g. "1 Step Challenge ·
 * Phase 1" — and collapsed any bucket with more than one real account into
 * a single generic "N accounts / Choose Account →" card. This was a real,
 * reported bug, not a design choice: Kevin's two most-recently-selected
 * accounts ("final 5K" and "test trailing 5K", both real one_step/phase_1
 * accounts, confirmed via trading_accounts.last_selected_seq to be his #1
 * and #2 most recent selections) were BOTH being swallowed into one shared
 * "1 Step Challenge · Phase 1 · 2 accounts" bucket card — so instead of two
 * direct shortcuts to the exact accounts he'd just been trading, Quick
 * Access showed one generic category tile that dead-ended into a "choose
 * an account" picker, exactly contradicting what a "quick access shortcut"
 * is for. Category/stage BROWSING still exists — unchanged — everywhere
 * else on this page (the challenge-type tile grid, the per-stage picker);
 * it is simply never used for Quick Access, per explicit instruction.
 *
 * Ordering, 2026-08-18 (first fix, same day): for Main Workflow, ordering
 * (and inclusion) is driven purely by trading_accounts.last_selected_seq —
 * the trader's actual account-SELECTION history (see
 * apRecordAccountSelection()/apLastSelectedAccountId(),
 * src/AccountPicker.php) — never by trade activity or account age. Kevin
 * reported a real, dormant account ("depo - yes") showing up in Quick
 * Access despite never having been opened, because the prior version
 * sorted by last_activity (falling back to created_at for a never-traded
 * account). An account that has never been explicitly selected is excluded
 * entirely, not just ranked low — "Quick Access" is a shortcut list to what
 * the trader has actually been working with, not a general account
 * browser.
 *
 * Validation Workflow is explicitly EXEMPT from that selection-history
 * requirement (same day, follow-up clarification) — see the
 * $usesSelectionHistory branch inside this function.
 *
 * Returns at most $limit cards, most-recent-first (by whichever key this
 * workflow uses). Returns an empty array for a Main Workflow trader who
 * hasn't explicitly selected any account yet, or a Validation Workflow
 * trader with no accounts in this workflow at all — the caller renders
 * nothing in that case, so a brand-new account's Accounts hub looks exactly
 * like it always has.
 */
function tcBuildQuickAccessCards(string $userId, ScopedDb $db, int $limit = 6, string $workflowState = 'main'): array {
    $propAccounts = apFetchAccounts($userId, $db, ['account_type' => 'prop', 'workflow_state' => $workflowState]);
    $liveAccounts = apFetchAccounts($userId, $db, ['account_type' => 'personal', 'lifecycle_not_in' => ['closed'], 'workflow_state' => $workflowState]);

    // Validation Workflow exception (2026-08-18, explicit clarification):
    // Validation only ever has ONE active strategy and is meant to be a
    // small, low-friction context — its account(s) must always surface here
    // as a persistent shortcut, never gated behind "has this been
    // explicitly selected before" the way Main Workflow's selection-history
    // requirement works. So for Validation, every account of this workflow
    // is eligible unconditionally, ordered by trade activity (the same
    // ordering this whole feature used before 2026-08-18) instead of being
    // filtered/sorted by selection history.
    $usesSelectionHistory = $workflowState !== 'validation';
    if ($usesSelectionHistory) {
        // Only accounts the trader has actually selected at least once —
        // see this function's own doc-comment above.
        $propAccounts = array_values(array_filter($propAccounts, fn($a) => !empty($a['last_selected_seq'])));
        $liveAccounts = array_values(array_filter($liveAccounts, fn($a) => !empty($a['last_selected_seq'])));
    }
    $sortKeyFn = $usesSelectionHistory ? 'tcSelectionKey' : 'tcActivityKey';

    // One card per real account_id — never a challenge-type/stage bucket
    // card. Every prop account that resolves to a real, non-breached stage
    // gets its own direct shortcut, exactly like the live-account loop
    // below already does — see this function's own doc-comment above for
    // why the old grouped version was a real bug, not a design choice.
    $cards = [];
    foreach ($propAccounts as $a) {
        $challengeType = $a['challenge_type'];
        $stageDef = null;
        foreach (tcStageButtonsForChallenge($challengeType) as $btn) {
            if (in_array($a['lifecycle_state'] ?? '', $btn['lifecycle_in'], true)) { $stageDef = $btn; break; }
        }
        if ($stageDef === null) continue; // lifecycle_state outside every known bucket — nothing honest to link to
        // Breached accounts never belong in a "fast lane to trade" list —
        // they still browse normally under their challenge type's own
        // Breached tile (unchanged), just never surfaced here as if this
        // were a shortcut worth taking.
        if ($stageDef['stage'] === 'breached') continue;
        $icon = match ($stageDef['stage']) {
            'continue' => '⏫', 'funded', 'instant_funded' => '🏆', default => '🪜',
        };
        $cards[] = [
            'icon' => $icon, 'title' => $a['label'],
            'sub' => apChallengeTypeLabel($challengeType) . ' · ' . $stageDef['label'],
            'cta' => 'Continue Trading →', 'badge' => apLifecycleGlowBadge($a['lifecycle_state'] ?? null),
            'url' => tcUrl(['hub' => 'accounts', 'kind' => 'prop', 'view' => 'center', 'challenge' => $challengeType, 'stage' => $stageDef['stage'], 'account_id' => $a['id']]),
            'activity' => $sortKeyFn($a),
        ];
    }
    foreach ($liveAccounts as $a) {
        $cards[] = [
            'icon' => '💼', 'title' => $a['label'], 'sub' => 'Live Account',
            'cta' => 'Continue Trading →', 'badge' => apLifecycleGlowBadge($a['lifecycle_state'] ?? null),
            // account_id alone is enough for Account Home to resolve this
            // account regardless of broker (see the LIVE ACCOUNTS branch
            // below) — no need to also compute/pass its broker here.
            'url' => tcUrl(['hub' => 'accounts', 'kind' => 'live', 'view' => 'center', 'account_id' => $a['id']]),
            'activity' => $sortKeyFn($a),
        ];
    }

    // Most-recent-first, by whichever key this workflow uses (selection
    // history for Main, trade activity for Validation — see above). Every
    // card in $cards already carries a real 'activity' value: Main's source
    // arrays were pre-filtered to only selected accounts, and Validation's
    // tcActivityKey() always has a value (falls back to created_at).
    usort($cards, fn($x, $y) => strcmp($y['activity'], $x['activity']));
    return array_slice($cards, 0, $limit);
}

function tcRenderQuickAccess(array $cards): void {
    if (empty($cards)) return;
    ?>
    <div class="tc-quick-section">
        <div class="tc-quick-head">
            <span class="tc-quick-icon">⚡</span>
            <div>
                <div class="tc-quick-title">Quick Access</div>
                <div class="tc-quick-sub">Straight to what you're most likely trading right now. The full browse-by-category flow is still below.</div>
            </div>
        </div>
        <div class="tc-quick-grid">
            <?php foreach ($cards as $c): ?>
            <a class="tc-quick-card" href="<?= htmlspecialchars($c['url']) ?>">
                <div class="tc-quick-card-top">
                    <span class="tc-quick-card-icon"><?= $c['icon'] ?></span>
                    <?= $c['badge'] ?? '' ?>
                </div>
                <div class="tc-quick-card-title" title="<?= htmlspecialchars($c['title']) ?>"><?= htmlspecialchars($c['title']) ?></div>
                <div class="tc-quick-card-sub"><?= htmlspecialchars($c['sub']) ?></div>
                <div class="tc-quick-card-cta"><?= htmlspecialchars($c['cta']) ?></div>
            </a>
            <?php endforeach; ?>
        </div>
    </div>
    <?php
}

/**
 * Live Accounts — broker grouping. Replaces the old execution-model
 * grouping (ECN/STP/Standard/Raw Spread/Not Categorized) entirely: in real
 * use almost no account ever has execution_model set (it's an optional,
 * manually-entered Additional Details field — see tcRenderAdditionalDetails
 * below, unchanged), so that grid mostly showed one "Not Categorized" tile
 * holding every real account — exactly the "scattered, no real value"
 * problem reported. Broker is parsed from every live account's own real
 * label (tcParseBroker(), above) — a value that's always present, never a
 * fallback bucket.
 *
 * Sorted by how recently each broker's accounts were actually traded (same
 * MAX(last_activity) technique tcBuildQuickAccessCards() already uses for
 * the Accounts hub) — "most actively traded first," per spec — and capped
 * to $limit tiles. Anything beyond the cap isn't hidden or lost: every
 * account, from every broker, stays reachable through the "Find Any
 * Account" picker rendered above the grid (tcRenderTileGrid call site,
 * below) — the exact same apRenderAccountPicker() search/first-5/Show More
 * widget used everywhere else in this app, just handed the FULL account
 * list instead of a pre-grouped one, so nothing needs a second search
 * implementation.
 *
 * @return array{groups: array, overflow: int} groups = up to $limit
 *   ['broker'=>,'count'=>,'activity'=>,'url'=>], sorted most-active-first;
 *   overflow = how many additional brokers exist beyond the cap (0 = none).
 */
function tcBuildBrokerGroups(array $liveAccounts, int $limit = 10): array {
    $byBroker = [];
    foreach ($liveAccounts as $a) {
        $broker = tcParseBroker($a['label']);
        $byBroker[$broker]['broker'] ??= $broker;
        $byBroker[$broker]['accounts'][] = $a;
    }
    $groups = [];
    foreach ($byBroker as $b) {
        $activity = '';
        foreach ($b['accounts'] as $a) { $activity = max($activity, tcActivityKey($a)); }
        $groups[] = ['broker' => $b['broker'], 'count' => count($b['accounts']), 'activity' => $activity];
    }
    usort($groups, fn($x, $y) => strcmp($y['activity'], $x['activity']));
    $overflow = max(0, count($groups) - $limit);
    return ['groups' => array_slice($groups, 0, $limit), 'overflow' => $overflow];
}

/**
 * A deterministic, pleasant color per broker name — same crc32-hash-into-a-
 * fixed-palette technique already established for phrase variety elsewhere
 * in this app (src/TraderProfileNarrative.php's pick()), applied here to
 * pick a color instead of a sentence. Same broker always gets the same
 * color across renders/sessions (a real, if secondary, usability cue — "the
 * gold one is always HFM"), without needing a stored color column anywhere.
 */
function tcBrokerColor(string $broker): string {
    $palette = ['#60A5FA', '#26A69A', '#E0A64A', '#A78BFA', '#EF5350', '#34D399', '#F472B6', '#38BDF8'];
    return $palette[crc32($broker) % count($palette)];
}

function tcBrokerInitials(string $broker): string {
    $words = preg_split('/\s+/', trim($broker));
    $words = array_filter($words, fn($w) => $w !== '');
    if (empty($words)) return '?';
    if (count($words) === 1) return mb_strtoupper(mb_substr($words[0], 0, 2));
    return mb_strtoupper(mb_substr($words[0], 0, 1) . mb_substr(end($words), 0, 1));
}

function tcRenderBrokerGrid(array $groups): void {
    ?>
    <div class="tc-broker-grid">
        <?php foreach ($groups as $g): $color = tcBrokerColor($g['broker']); ?>
        <a class="tc-broker-tile" href="<?= htmlspecialchars(tcUrl(['hub' => 'accounts', 'kind' => 'live', 'view' => 'center', 'broker' => $g['broker']])) ?>">
            <span class="tc-broker-avatar" style="--broker-color:<?= $color ?>"><?= htmlspecialchars(tcBrokerInitials($g['broker'])) ?></span>
            <span class="tc-broker-name" title="<?= htmlspecialchars($g['broker']) ?>"><?= htmlspecialchars($g['broker']) ?></span>
            <span class="tc-broker-count"><?= $g['count'] ?> account<?= $g['count'] === 1 ? '' : 's' ?></span>
        </a>
        <?php endforeach; ?>
    </div>
    <?php
}

// tcRenderAdditionalDetails() moved 2026-08-18 to
// src/AccountSummaryView.php's renderAccountAdditionalDetails() — now
// shared verbatim with Journal's "View Account Summary" modal, which
// previously had its own separate, drifted account-details rendering.
// See that file's own header comment for the full account.

/**
 * Account Home — the operating page for one selected prop-firm account.
 * Every stage bucket (Phase 1/2/3, Funded, Instant Funded, Continue
 * Existing Attempt, Breached) renders through this ONE function so every
 * account category behaves identically: same fields, same "Start Trade"
 * CTA, same promotion affordance whenever the account is actually eligible
 * (lifecycle_state contains "_complete") regardless of which bucket it was
 * reached from.
 */
function tcRenderPropAccountSummary(array $account, ScopedDb $db, PDO $pdo, string $userId, ?string $backUrl = null, string $workflowState = 'main'): void {
    $lifecycleState = $account['lifecycle_state'] ?? '';
    $isBreached = $lifecycleState === 'breached';
    $isPromotable = str_contains($lifecycleState, '_complete');
    ?>
    <?php if ($backUrl): ?>
    <a href="<?= htmlspecialchars($backUrl) ?>" class="tc-back-link">← Choose a different account</a>
    <?php endif; ?>
    <div class="tc-summary-card">
        <?php renderAccountSummarySections($account, 'prop', $db, $pdo, $userId, $workflowState); ?>

        <div class="tc-summary-actions">
            <?php if ($isBreached): ?>
            <!-- Deliberately NOT the primary/bright CTA a healthy account
                 gets — present, since the workflow itself isn't blocked, but
                 never styled to invite it. See tc-breach-notice above for
                 the one place this account's status is actually explained. -->
            <a class="btn btn-outline btn-md" href="/traderalign/strategies?account_id=<?= urlencode($account['id']) ?>">Continue Anyway →</a>
            <?php elseif ($isPromotable): ?>
            <!-- Same de-emphasis for a passed phase — Promote Account (above)
                 is the intended next step now, not more trading at a stage
                 that's already complete. -->
            <a class="btn btn-outline btn-md" href="/traderalign/strategies?account_id=<?= urlencode($account['id']) ?>">Continue to Strategies →</a>
            <?php else: ?>
            <a class="btn btn-primary btn-lg" href="/traderalign/strategies?account_id=<?= urlencode($account['id']) ?>">Continue to Strategies →</a>
            <?php endif; ?>
        </div>
    </div>
    <?php
}

/** Account Home for a live/personal account — same shared shell as the prop version above, reached identically from the broker-grouped Trading Center below. */
function tcRenderLiveAccountSummary(array $account, ScopedDb $db, PDO $pdo, string $userId, ?string $backUrl = null, string $workflowState = 'main'): void {
    ?>
    <?php if ($backUrl): ?>
    <a href="<?= htmlspecialchars($backUrl) ?>" class="tc-back-link">← Choose a different account</a>
    <?php endif; ?>
    <div class="tc-summary-card">
        <?php renderAccountSummarySections($account, 'live', $db, $pdo, $userId, $workflowState); ?>

        <div class="tc-summary-actions">
            <a class="btn btn-outline btn-md" href="/traderalign/manage-live-accounts?account_id=<?= urlencode($account['id']) ?>">Manage Deposits, Withdrawals &amp; Closures →</a>
            <a class="btn btn-primary btn-lg" href="/traderalign/strategies?account_id=<?= urlencode($account['id']) ?>">Continue to Strategies →</a>
        </div>
    </div>
    <?php
}

// ── Shared head/shell ────────────────────────────────────────────────────
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Trading Center - 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">
<link rel="stylesheet" href="/traderalign/assets/css/account-picker.css">
<script src="/traderalign/assets/js/account-picker.js"></script>
<link rel="stylesheet" href="/traderalign/assets/css/account-summary.css?v=<?= @filemtime(__DIR__ . '/../assets/css/account-summary.css') ?: time() ?>">
<script src="/traderalign/assets/js/account-summary.js"></script>
<style>
.dash-layout{display:flex;min-height:100vh}
.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);z-index:50}
.sidebar a:hover .tt{opacity:1}
.main{margin-left:4rem;flex:1;padding:2rem;max-width:64rem}
body{background:var(--background);margin:0;font-family:var(--font-sans);color:var(--foreground)}

.tc-breadcrumb{font-size:.82rem;color:var(--foreground-dim);margin-bottom:1.25rem;display:flex;flex-wrap:wrap;gap:.35rem;align-items:center}
.tc-breadcrumb a{color:var(--foreground-dim);text-decoration:none}
.tc-breadcrumb a:hover{color:var(--primary)}
.tc-breadcrumb-sep{opacity:.5}
.tc-breadcrumb-current{color:var(--foreground);font-weight:700}

.tc-page-title{font-size:1.5rem;font-weight:800;color:var(--foreground);margin-bottom:.25rem}
.tc-page-sub{color:var(--foreground-muted);font-size:.9rem;margin-bottom:1.75rem;line-height:1.5;max-width:42rem}

.tc-section-label{font-size:.72rem;font-weight:800;text-transform:uppercase;letter-spacing:.08em;color:var(--foreground-dim);margin:0 0 .85rem;display:flex;align-items:center;gap:.65rem}
.tc-section-label::after{content:'';flex:1;height:1px;background:var(--border)}

/* Quick Access — the "fast lane" shortcut cards, visually distinct from the
   plain .tc-tile browse grid below it: gradient card surface, colored left
   accent, and a small glowing header chip, so it reads as personalized/
   dynamic rather than another static menu layer. */
.tc-quick-section{margin-bottom:2rem;padding:1.35rem 1.35rem 1.5rem;border-radius:var(--radius-xl);background:linear-gradient(160deg,color-mix(in srgb,var(--primary) 7%,var(--background-elevated)),var(--background-elevated));border:1px solid var(--border);box-shadow:var(--shadow-md)}
.tc-quick-head{display:flex;align-items:center;gap:.8rem;margin-bottom:1.15rem}
.tc-quick-icon{width:2.4rem;height:2.4rem;flex-shrink:0;border-radius:var(--radius-lg);background:linear-gradient(155deg,color-mix(in srgb,var(--primary) 34%,var(--background-elevated)),var(--background-elevated));border:1px solid color-mix(in srgb,var(--primary) 38%,var(--border));display:flex;align-items:center;justify-content:center;font-size:1.15rem;box-shadow:0 4px 14px color-mix(in srgb,var(--primary) 20%,transparent)}
.tc-quick-title{font-size:1.05rem;font-weight:800;color:var(--foreground)}
.tc-quick-sub{font-size:.8rem;color:var(--foreground-dim);margin-top:.15rem;max-width:36rem}
.tc-quick-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(14.5rem,1fr));gap:.85rem}
.tc-quick-card{position:relative;display:flex;flex-direction:column;gap:.5rem;background:var(--background-surface);border:1px solid var(--border);border-radius:var(--radius-lg);padding:1.1rem 1.1rem 1rem 1.3rem;text-decoration:none;color:var(--foreground);transition:all .18s cubic-bezier(.4,0,.2,1);overflow:hidden}
.tc-quick-card::before{content:'';position:absolute;left:0;top:0;bottom:0;width:3px;background:linear-gradient(180deg,var(--primary),color-mix(in srgb,var(--primary) 35%,var(--positive)))}
.tc-quick-card:hover{border-color:color-mix(in srgb,var(--primary) 45%,var(--border));transform:translateY(-2px);box-shadow:0 12px 26px -10px color-mix(in srgb,var(--primary) 45%,transparent)}
.tc-quick-card-top{display:flex;align-items:center;justify-content:space-between;gap:.5rem}
.tc-quick-card-icon{font-size:1.25rem;line-height:1}
.tc-quick-card-top .ap-glow-badge{font-size:.64rem;padding:.28rem .6rem;gap:.3rem}
.tc-quick-card-title{font-size:.92rem;font-weight:800;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tc-quick-card-sub{font-size:.74rem;color:var(--foreground-dim)}
.tc-quick-card-cta{font-size:.76rem;font-weight:700;color:var(--primary);margin-top:.2rem}

.tc-tile-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));gap:1rem;margin-bottom:1.5rem}
.tc-tile{display:flex;flex-direction:column;gap:.4rem;background:var(--background-elevated);border:1px solid var(--border);border-radius:var(--radius-lg);padding:1.5rem;text-decoration:none;color:var(--foreground);transition:border-color var(--t-fast),transform var(--t-fast)}
.tc-tile:hover{border-color:var(--primary);transform:translateY(-2px)}
.tc-tile-icon{font-size:1.6rem}
.tc-tile-label{font-size:1.05rem;font-weight:800}
.tc-tile-sub{font-size:.82rem;color:var(--foreground-dim)}
.tc-tile-count{font-size:.72rem;font-weight:700;color:var(--primary);margin-top:.35rem}

/* Glowing "N Ready to Promote" tile — the Add-or-Continue-Attempt tile's
   attention state. Warm/gold, distinct from the plain-blue hover state
   every other tile uses, and reuses account-picker.css's @keyframes
   rn-pulse (already linked on this page) for the badge's dot. */
.tc-tile-glowing{border-color:color-mix(in srgb,var(--rn-gold,#E0A64A) 45%,var(--border));box-shadow:0 0 0 1px color-mix(in srgb,var(--rn-gold,#E0A64A) 22%,transparent),0 8px 22px -10px color-mix(in srgb,var(--rn-gold,#E0A64A) 45%,transparent);animation:tc-glow-pulse 2.6s ease-in-out infinite}
.tc-tile-glowing:hover{border-color:var(--rn-gold,#E0A64A)}
@keyframes tc-glow-pulse{0%,100%{box-shadow:0 0 0 1px color-mix(in srgb,var(--rn-gold,#E0A64A) 22%,transparent),0 8px 22px -10px color-mix(in srgb,var(--rn-gold,#E0A64A) 45%,transparent)}50%{box-shadow:0 0 0 1px color-mix(in srgb,var(--rn-gold,#E0A64A) 50%,transparent),0 10px 28px -6px color-mix(in srgb,var(--rn-gold,#E0A64A) 60%,transparent)}}
.tc-tile-glow-badge{display:inline-flex;align-items:center;gap:.4rem;align-self:flex-start;background:color-mix(in srgb,var(--rn-gold,#E0A64A) 18%,transparent);color:var(--rn-gold,#E0A64A);font-size:.66rem;font-weight:800;letter-spacing:.02em;padding:.3rem .65rem;border-radius:var(--radius-full);border:1px solid color-mix(in srgb,var(--rn-gold,#E0A64A) 35%,transparent)}
.tc-tile-glow-dot{width:.4rem;height:.4rem;border-radius:50%;background:currentColor;animation:rn-pulse 1.4s infinite}

/* Small, secondary "add any type" fallback link on the challenge-type
   picker — deliberately not tile-styled, so the scoped Add-or-Continue tile
   one level down (inside a specific challenge type) reads as the preferred
   path, not this generic one. */
.tc-add-any{margin:-.5rem 0 1.5rem}
.tc-add-any a{font-size:.8rem;color:var(--foreground-dim);text-decoration:none;border-bottom:1px dotted var(--foreground-dim);padding-bottom:1px;transition:color var(--t-fast),border-color var(--t-fast)}
.tc-add-any a:hover{color:var(--primary);border-color:var(--primary)}
/* Live Accounts' own two utility links reuse the exact same small/secondary
   treatment above, just with a middle dot separator and no negative margin
   (that -.5rem was tuned for sitting directly under the prop challenge-type
   grid; here it follows the broker grid/empty-state with normal spacing). */
.tc-live-utility{margin:1.75rem 0 0;display:flex;align-items:center;gap:.6rem;flex-wrap:wrap}
.tc-add-any-sep{color:var(--border)}

/* Live Accounts — "Find Any Account" search, sitting above the broker grid.
   Same rn-account-picker widget as everywhere else in the app; widened here
   since it's the page's primary fast-path, not a secondary control. */
.tc-live-find{margin-bottom:1.75rem}
.tc-live-find .rn-account-picker-btn{width:100%;min-width:0;background:var(--background-elevated);border-color:color-mix(in srgb,var(--primary) 25%,var(--border));padding:.75rem 1rem}
.tc-live-find .rn-account-picker-current{font-weight:700;color:var(--foreground-dim)}
.tc-live-find .rn-account-picker-panel{width:100%;left:0;right:auto}

/* Broker grid — colored initials avatar per broker (deterministic, see
   tcBrokerColor()) instead of a repeated generic icon, so 6 tiles in a row
   actually look distinguishable from each other at a glance. */
.tc-broker-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(13rem,1fr));gap:1rem;margin-bottom:1rem}
.tc-broker-tile{display:flex;flex-direction:column;align-items:flex-start;gap:.65rem;background:var(--background-elevated);border:1px solid var(--border);border-radius:var(--radius-lg);padding:1.35rem 1.25rem;text-decoration:none;color:var(--foreground);transition:all var(--t-fast)}
.tc-broker-tile:hover{border-color:color-mix(in srgb,var(--primary) 45%,var(--border));transform:translateY(-2px);box-shadow:0 10px 24px -12px rgba(0,0,0,.4)}
.tc-broker-avatar{width:2.75rem;height:2.75rem;border-radius:var(--radius-lg);display:flex;align-items:center;justify-content:center;font-size:.9rem;font-weight:800;letter-spacing:.02em;color:#0A0A0B;background:linear-gradient(155deg,var(--broker-color),color-mix(in srgb,var(--broker-color) 70%,#000));box-shadow:0 4px 14px color-mix(in srgb,var(--broker-color) 35%,transparent)}
.tc-broker-name{font-size:1rem;font-weight:800;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tc-broker-count{font-size:.74rem;font-weight:700;color:var(--primary)}

.tc-empty{background:var(--background-elevated);border:1px dashed var(--border);border-radius:var(--radius-lg);padding:2rem;text-align:center;color:var(--foreground-dim);margin-bottom:1.5rem}
.tc-empty p{margin-bottom:1rem}

.tc-hint{font-size:.78rem;color:var(--foreground-dim);margin-top:.75rem;padding:.6rem .8rem;background:var(--background-elevated);border-radius:var(--radius-md);border:1px solid var(--border)}

.tc-picker-block{margin-bottom:1.5rem}

/* .tc-summary-head, .tc-summary-title, .tc-summary-sub, .tc-summary-section,
   .tc-summary-grid, .tc-row, .tc-edit-btn, .tc-reveal-btn, .tc-modal- (all),
   .tc-checkbox-label, .tc-promo-row, .tc-promo-arrow, .tc-promo-error,
   .tc-breach- (all) moved 2026-08-18 to assets/css/account-summary.css - now
   shared with Journal's "View Account Summary" modal. .tc-summary-card
   (this page's own wrapper), .tc-back-link, and .tc-summary-actions stay
   here since they're rendered by this page directly, never inside the
   shared component itself. Do not put a literal star-slash sequence
   inside this comment's prose (e.g. writing a class name immediately
   followed by a "/" list separator) - it prematurely closes this comment
   and silently invalidates the very next CSS rule, which is exactly what
   broke .tc-summary-card's own max-width/padding before this fix. */
.tc-summary-card{background:var(--background-elevated);border:1px solid var(--border);border-radius:var(--radius-lg);padding:1.15rem 1.35rem;max-width:36rem}
.tc-summary-actions{display:flex;gap:.6rem;flex-wrap:wrap;margin-top:1rem}
.tc-back-link{display:inline-block;font-size:.78rem;color:var(--foreground-dim);text-decoration:none;margin-bottom:.75rem}
.tc-back-link:hover{color:var(--primary)}

.tc-promo-card{background:var(--background-elevated);border:1px solid var(--border);border-radius:var(--radius-lg);padding:1.15rem 1.35rem;max-width:30rem}

.tc-exec-tooltip{cursor:help;border-bottom:1px dotted var(--foreground-dim)}
</style>
</head>
<body class="dash-layout">
<?php include __DIR__ . '/sidebar.php'; ?>
<main class="main">
<?php tcRenderBreadcrumb($crumbs); ?>

<?php
// ── Root: Trading Center ─────────────────────────────────────────────────
if ($hub === null) {
    echo '<div class="tc-page-title">Trading Center</div>';
    echo '<div class="tc-page-sub">Your central trading workspace. Manage your active account and strategy, with your selections carried across TraderAlign and reflected throughout your trading analytics and computed stats.</div>';
    tcRenderTileGrid([
        ['label' => 'Accounts', 'sub' => 'Prop firm challenges and live accounts', 'url' => tcUrl(['hub' => 'accounts']), 'icon' => '🏦'],
        // Points at /strategies with NO account_id — that page's own
        // no-account branch is a strategy switcher (pick a strategy, jump
        // straight into Dashboard with it active), not the strategy EDITOR
        // (/strategy) the old version of this tile used to link to — see
        // public/strategies.php for why that distinction matters.
        [
            'label' => 'Strategies', 'sub' => 'Switch which strategy you\'re viewing across the app',
            'url' => '/traderalign/strategies', 'icon' => '📈',
        ],
    ]);

// ── Accounts hub ─────────────────────────────────────────────────────────
} elseif ($hub === 'accounts' && $kind === null) {
    echo '<div class="tc-page-title">Accounts</div>';
    echo '<div class="tc-page-sub">Choose which kind of account you want to work with.</div>';

    $quickCards = tcBuildQuickAccessCards($userId, $db, 6, $tcWorkflowState);
    tcRenderQuickAccess($quickCards);

    if (!empty($quickCards)) echo '<div class="tc-section-label">Browse by Type</div>';
    tcRenderTileGrid([
        ['label' => 'Prop Firms', 'sub' => 'Challenge accounts and funded stages', 'url' => tcUrl(['hub' => 'accounts', 'kind' => 'prop']), 'icon' => '🏛️'],
        ['label' => 'Live Accounts', 'sub' => 'Your own personal trading accounts, grouped by broker', 'url' => tcUrl(['hub' => 'accounts', 'kind' => 'live', 'view' => 'center']), 'icon' => '💼'],
    ]);

// ══════════════════════════════════════════ PROP FIRMS ══════════════════
} elseif ($hub === 'accounts' && $kind === 'prop' && $view === null) {
    echo '<div class="tc-page-title">Prop Firms</div>';
    echo '<div class="tc-page-sub">Head into the Trading Center to work with your challenge accounts. Adding a new one or continuing an existing attempt both live there now, scoped to the challenge type you pick.</div>';
    tcRenderTileGrid([
        ['label' => 'Trading Center', 'sub' => 'Browse by challenge type: add, continue, or manage any account', 'url' => tcUrl(['hub' => 'accounts', 'kind' => 'prop', 'view' => 'center']), 'icon' => '🎯'],
    ]);

} elseif ($hub === 'accounts' && $kind === 'prop' && $view === 'center' && $challenge === null) {
    echo '<div class="tc-page-title">Prop Firms - Trading Center</div>';
    echo '<div class="tc-page-sub">Pick a challenge type to see its phases, funded accounts, and any account needing a promotion.</div>';
    // 2026-08-17 account-level workflow isolation: this count query bypassed
    // apFetchAccounts() entirely (its own direct raw query) — was the real,
    // live cause of the challenge-type tiles still showing real Main-workflow
    // counts (8/60/4/6) inside a fresh Validation Workflow session with zero
    // accounts actually created there. Scoped the same way as every other
    // account query now, via trading_accounts.workflow_context.
    $typeCounts = [];
    foreach ($db->raw(
        "SELECT ca.challenge_type, COUNT(*) c FROM trading_accounts ta
         JOIN challenge_accounts ca ON ca.account_id = ta.id AND ca.deleted_at IS NULL
         WHERE ta.user_id = :uid AND ta.deleted_at IS NULL AND ta.is_active = 1 AND ta.workflow_context = :wfctx
         GROUP BY ca.challenge_type", [':uid' => $userId, ':wfctx' => $tcWorkflowState]
    ) as $row) { $typeCounts[$row['challenge_type']] = (int)$row['c']; }
    $tiles = [];
    foreach ($VALID_CHALLENGE_TYPES as $ct) {
        $tiles[] = [
            'label' => apChallengeTypeLabel($ct),
            'url' => tcUrl(['hub' => 'accounts', 'kind' => 'prop', 'view' => 'center', 'challenge' => $ct]),
            'icon' => '🪜',
            'count' => $typeCounts[$ct] ?? 0,
        ];
    }
    tcRenderTileGrid($tiles);
    // General "add any type" entry point — the per-challenge-type stage grid
    // (below) has its own scoped "Add or Continue Attempt" tile for
    // one_step/two_step/three_step, but instant_funding deliberately keeps
    // its stage grid untouched (no such tile there), and a trader who hasn't
    // picked a type yet still needs a way in. Kept small/secondary on
    // purpose — the scoped tile one level down is the preferred path.
    echo '<div class="tc-add-any"><a href="/traderalign/add-prop-account">+ Add a new prop firm account (any type)</a></div>';

} elseif ($hub === 'accounts' && $kind === 'prop' && $view === 'center' && $challenge !== null && $stage === null) {
    echo '<div class="tc-page-title">' . htmlspecialchars(apChallengeTypeLabel($challenge)) . '</div>';
    echo '<div class="tc-page-sub">Pick a stage to see the accounts in it.</div>';
    // Same workflow_context scoping as the root hub's $typeCounts above.
    $stageCounts = [];
    foreach ($db->raw(
        "SELECT ta.lifecycle_state, COUNT(*) c FROM trading_accounts ta
         JOIN challenge_accounts ca ON ca.account_id = ta.id AND ca.deleted_at IS NULL
         WHERE ta.user_id = :uid AND ta.deleted_at IS NULL AND ta.is_active = 1 AND ca.challenge_type = :ctype AND ta.workflow_context = :wfctx
         GROUP BY ta.lifecycle_state", [':uid' => $userId, ':ctype' => $challenge, ':wfctx' => $tcWorkflowState]
    ) as $row) { $stageCounts[$row['lifecycle_state'] ?? ''] = (int)$row['c']; }
    $tiles = [];
    foreach (tcStageButtonsForChallenge($challenge) as $btn) {
        // 'continue' is no longer its own navigable tile for one/two/three_step
        // — replaced below by the combined "Add or Continue Attempt" tile.
        // instant_funding never had a 'continue' entry to begin with, so this
        // skip is a no-op for it — its grid renders exactly as before.
        if ($btn['stage'] === 'continue') continue;
        $count = 0;
        foreach ($btn['lifecycle_in'] as $state) { $count += $stageCounts[$state] ?? 0; }
        $icon = match ($btn['stage']) {
            'breached' => '⛔', 'funded' => '🏆', 'instant_funded' => '🏆', default => '🪜',
        };
        $tiles[] = [
            'label' => $btn['label'],
            'url' => tcUrl(['hub' => 'accounts', 'kind' => 'prop', 'view' => 'center', 'challenge' => $challenge, 'stage' => $btn['stage']]),
            'icon' => $icon,
            'count' => $count,
        ];
    }
    if ($challenge !== 'instant_funding') {
        $promotableCount = 0;
        foreach (apPromotableLifecycleStates($challenge) as $state) { $promotableCount += $stageCounts[$state] ?? 0; }
        $tiles[] = tcBuildAddOrContinueTile($challenge, $promotableCount, '/traderalign/add-prop-account?challenge=' . urlencode($challenge));
    }
    tcRenderTileGrid($tiles);

} elseif ($hub === 'accounts' && $kind === 'prop' && $view === 'center' && $challenge !== null && $stageDef !== null) {
    // Every stage bucket (Phase 1/2/3, Funded, Instant Funded, Continue
    // Existing Attempt, Breached) shares this ONE code path, so every
    // account category behaves identically: same picker, same Account Home
    // (tcRenderPropAccountSummary handles the promotable/breached variants
    // internally), same "Start Trade" CTA, never a dead end.
    $accounts = apFetchAccounts($userId, $db, ['account_type' => 'prop', 'challenge_type' => $challenge, 'lifecycle_in' => $stageDef['lifecycle_in'], 'workflow_state' => $tcWorkflowState]);
    $selected = null;
    if ($accountId) { foreach ($accounts as $a) { if ($a['id'] === $accountId) { $selected = $a; break; } } }
    $bucketUrl = tcUrl(['hub' => 'accounts', 'kind' => 'prop', 'view' => 'center', 'challenge' => $challenge, 'stage' => $stage]);

    if ($selected) {
        // Real, explicit selection event — $accountId always came from the
        // URL here (a picker/Quick Access link click, or a reload of an
        // already-selected account), never a session/DB fallback the way
        // Strategies' own resolution can. Feeds Quick Access's ordering and
        // Strategies' cross-login persistence — see apRecordAccountSelection().
        apRecordAccountSelection($userId, $selected['id'], $db);
        // Account Home — the picker is intentionally hidden once an account is chosen.
        tcRenderPropAccountSummary($selected, $db, $pdo, $userId, $bucketUrl, $tcWorkflowState);
    } else {
        echo '<div class="tc-page-title">' . htmlspecialchars(apChallengeTypeLabel($challenge)) . ' - ' . htmlspecialchars($stageDef['label']) . '</div>';
        if ($stageDef['stage'] === 'continue') {
            echo '<div class="tc-page-sub">Accounts that completed a phase and are ready to promote to the next stage.</div>';
        }
        if ($accountId) {
            echo '<div class="tc-hint">That account could not be found in this view. Choose one below.</div>';
        }
        if (empty($accounts)) {
            $emptyMessage = match ($stageDef['stage']) {
                'continue' => 'No accounts are ready to promote right now.',
                'breached' => 'No breached accounts of this type. Good discipline so far.',
                default => 'No accounts are currently at this stage.',
            };
            if (in_array($stageDef['stage'], ['continue', 'breached'], true)) {
                tcEmptyState($emptyMessage);
            } else {
                tcEmptyState($emptyMessage, 'Add Account', '/traderalign/add-prop-account?challenge=' . urlencode($challenge));
            }
        } else {
            echo '<div class="tc-picker-block">';
            apRenderAccountPicker(
                $accounts, $accountId, '/traderalign/trading-center',
                ['hub' => 'accounts', 'kind' => 'prop', 'view' => 'center', 'challenge' => $challenge, 'stage' => $stage],
                'account_id',
                fn(array $a) => $stageDef['stage'] === 'breached' ? 'Breached' : apHumanizeLifecycleState($a['lifecycle_state'] ?? null),
                null,
                // Continue Existing Attempt is the promotion entry point — it
                // should land the trader straight on the search/list, not
                // behind a "Select account ▾" click. Every other bucket
                // (Phase 1/2/3, Funded, Breached) keeps the default collapsed
                // picker, unchanged.
                $stageDef['stage'] === 'continue'
            );
            echo '</div>';
        }
        if (in_array($stageDef['stage'], ['phase_1', 'phase_2', 'phase_3'], true)) {
            echo '<div class="tc-hint">Accounts that completed this phase are ready to promote from <a href="' . htmlspecialchars('/traderalign/add-prop-account?challenge=' . urlencode($challenge) . '&mode=continue') . '" style="color:var(--primary)">Add or Continue Attempt</a>.</div>';
        }
    }

// ══════════════════════════════════════════ LIVE ACCOUNTS ═══════════════
// Single fetch (every active, non-closed personal account), shared by every
// live branch below — Account Home lookup ALWAYS checks the full list
// regardless of $broker, so an account_id resolves correctly even from a
// stale/mismatched or omitted broker param (e.g. the "Find Any Account"
// picker's own links, which don't carry a broker param at all). This is
// what lets that one search box reach every account honestly, not just the
// ones in the top 10 broker tiles.
} elseif ($hub === 'accounts' && $kind === 'live' && $view === 'center') {
    $allLive = apFetchAccounts($userId, $db, ['account_type' => 'personal', 'lifecycle_not_in' => ['closed'], 'workflow_state' => $tcWorkflowState]);
    $selected = null;
    if ($accountId) { foreach ($allLive as $a) { if ($a['id'] === $accountId) { $selected = $a; break; } } }

    if ($selected) {
        // See the matching comment on the prop-firm branch above.
        apRecordAccountSelection($userId, $selected['id'], $db);
        $backUrl = tcUrl(['hub' => 'accounts', 'kind' => 'live', 'view' => 'center', 'broker' => tcParseBroker($selected['label'])]);
        tcRenderLiveAccountSummary($selected, $db, $pdo, $userId, $backUrl, $tcWorkflowState);

    } elseif ($broker !== null) {
        // A specific broker's accounts — mirrors a prop challenge type's
        // stage grid, but flat (brokers don't have phases): search + full
        // list right away (expanded, not the collapsed dropdown), since
        // landing here already means "show me this broker's accounts."
        $brokerAccounts = array_values(array_filter($allLive, fn($a) => tcParseBroker($a['label']) === $broker));
        echo '<div class="tc-page-title">' . htmlspecialchars($broker) . '</div>';
        if ($accountId) {
            echo '<div class="tc-hint">That account could not be found. Choose one below.</div>';
        }
        if (empty($brokerAccounts)) {
            tcEmptyState('No active accounts for this broker right now.', '← All Live Accounts', tcUrl(['hub' => 'accounts', 'kind' => 'live', 'view' => 'center']));
        } else {
            echo '<div class="tc-picker-block">';
            apRenderAccountPicker(
                $brokerAccounts, $accountId, '/traderalign/trading-center',
                ['hub' => 'accounts', 'kind' => 'live', 'view' => 'center', 'broker' => $broker],
                'account_id',
                fn(array $a) => apHumanizeLifecycleState($a['lifecycle_state'] ?? null),
                null,
                true
            );
            echo '</div>';
        }

    } else {
        // Broker grid root — the page "Live Accounts" now lands on directly.
        $brokerData = tcBuildBrokerGroups($allLive, 10);
        echo '<div class="tc-page-title">Live Accounts</div>';
        echo '<div class="tc-page-sub">Grouped by broker, your most actively traded ones first. Search below reaches every account, including any broker not shown here.</div>';

        if (!empty($allLive)) {
            echo '<div class="tc-picker-block tc-live-find">';
            apRenderAccountPicker(
                $allLive, null, '/traderalign/trading-center',
                ['hub' => 'accounts', 'kind' => 'live', 'view' => 'center'],
                'account_id',
                fn(array $a) => tcParseBroker($a['label']) . ' · ' . apHumanizeLifecycleState($a['lifecycle_state'] ?? null),
                null, false, '🔍 Find any live account…'
            );
            echo '</div>';
        }

        if (empty($brokerData['groups'])) {
            tcEmptyState('No live accounts yet.', '+ Add Live Account', '/traderalign/add-live-account');
        } else {
            tcRenderBrokerGrid($brokerData['groups']);
            if ($brokerData['overflow'] > 0) {
                echo '<div class="tc-hint">+' . $brokerData['overflow'] . ' more broker' . ($brokerData['overflow'] === 1 ? '' : 's') . '. Use the search above to find accounts there.</div>';
            }
        }

        echo '<div class="tc-add-any tc-live-utility">'
           . '<a href="/traderalign/add-live-account">+ Add Live Account</a>'
           . '<span class="tc-add-any-sep">·</span>'
           . '<a href="/traderalign/manage-live-accounts">⚙ Manage All Accounts (Deposits/Withdrawals/Closures)</a>'
           . '</div>';
    }

} else {
    echo '<div class="tc-page-title">Trading Center</div>';
    tcEmptyState('That page doesn\'t exist.', '← Back to Trading Center', tcUrl([]));
}
?>

</main>
<script>
// tcOpenMetaModal/tcCloseMetaModal/tcSaveMeta/tcRevealSecret moved
// 2026-08-18 to assets/js/account-summary.js — now shared verbatim with
// Journal's "View Account Summary" modal. See src/AccountSummaryView.php's
// own header comment for the full account.
function tcPromoteAccount(accountId, currentLifecycleState, challengeType) {
    var errEl = document.getElementById('tc-promo-error');
    errEl.style.display = 'none';
    var btn = document.getElementById('tc-promote-btn');
    btn.disabled = true;
    btn.textContent = 'Promoting…';
    fetch('/traderalign/api/accounts/create', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ mode: 'promote', account_id: accountId, current_lifecycle_state: currentLifecycleState })
    }).then(function (r) { return r.json(); }).then(function (data) {
        if (data && data.success) {
            var stageMap = { 'phase_2_active': 'phase_2', 'phase_3_active': 'phase_3', 'funded_active': 'funded' };
            var nextStage = stageMap[data.lifecycle_state] || 'continue';
            window.location.href = '/traderalign/trading-center?hub=accounts&kind=prop&view=center&challenge=' + encodeURIComponent(challengeType) + '&stage=' + nextStage + '&account_id=' + encodeURIComponent(accountId);
        } else {
            btn.disabled = false;
            btn.textContent = 'Promote Account →';
            errEl.textContent = (data && data.error) ? data.error : 'Could not promote this account. Try again.';
            errEl.style.display = 'block';
        }
    }).catch(function () {
        btn.disabled = false;
        btn.textContent = 'Promote Account';
        errEl.textContent = 'Could not promote this account. Try again.';
        errEl.style.display = 'block';
    });
}
</script>
</body>
</html>
