<script lang="ts">
  import { onMount } from 'svelte';

  // ── State (same names as template binds) ──
  let activeView = $state<'transactions' | 'budget'>('transactions');
  let activeTab = $state('all');
  let accountsOpen = $state(false);
  let selected = $state<Set<string>>(new Set());
  let lastCategory = $state('');
  let bulkCategoryOpen = $state(false);
  let focusedRowId = $state('');
  let loading = $state(true);
  let saving = $state(false);

  // ── Data (populated from API) ──
  let budgetGroups = $state<{ name: string; categories: { name: string; budgeted: number; spent: number; available: number }[] }[]>([]);
  let categories = $state<string[]>([]);
  let categoryMap = $state<Record<string, string>>({}); // name → id
  let accounts = $state<{ name: string; balance: number; positive: boolean; id?: string }[]>([]);
  let offBudgetAccounts = $state<{ name: string; balance: number; positive: boolean; id?: string }[]>([]);
  let suggestedTransfers = $state<any[]>([]);
  let transactions = $state<{ id: string; date: string; payee: string; note: string; account: string; category: string; categoryType: string; amount: number; categoryId?: string; accountId?: string }[]>([]);

  // ── Pagination ──
  let hasMore = $state(true);
  let loadingMore = $state(false);
  let activeAccountId = $state<string | null>(null);
  const PAGE_SIZE = 100;

  // ── Header stats ──
  let headerSpending = $state('...');
  let headerIncome = $state('...');
  let currentMonthLabel = $state('');

  // Sort categories with last-used first
  let sortedCategories = $derived(() => {
    if (!lastCategory) return categories;
    const rest = categories.filter(c => c !== lastCategory);
    return [lastCategory, ...rest];
  });

  let canTransfer = $derived(selected.size === 2);

  const filteredTransactions = $derived(() => {
    if (activeTab === 'uncategorized') return transactions.filter(t => t.categoryType === 'uncat');
    if (activeTab === 'categorized') return transactions.filter(t => t.categoryType !== 'uncat');
    return transactions;
  });

  let totalUncatCount = $state(0);
  const uncatCount = $derived(activeAccountId
    ? transactions.filter(t => t.categoryType === 'uncat').length
    : totalUncatCount
  );

  // ── API helper ──
  async function api(path: string, opts: RequestInit = {}) {
    const res = await fetch(`/api/budget${path}`, { credentials: 'include', ...opts });
    if (!res.ok) throw new Error(`${res.status}`);
    return res.json();
  }

  // ── Load data ──
  async function loadAccounts() {
    try {
      const data = await api('/accounts');
      const onBudget: typeof accounts = [];
      const offBudget: typeof offBudgetAccounts = [];
      for (const a of data) {
        if (a.closed) continue;
        const entry = {
          name: a.name,
          balance: Math.round(a.balanceDollars),
          positive: a.balanceDollars >= 0,
          id: a.id
        };
        if (a.offbudget) offBudget.push(entry);
        else onBudget.push(entry);
      }
      accounts = onBudget;
      offBudgetAccounts = offBudget;
    } catch { /* silent */ }
  }

  async function loadCategories() {
    try {
      const data = await api('/categories');
      const allCats: string[] = [];
      const map: Record<string, string> = {};
      for (const group of data) {
        for (const cat of group.categories) {
          if (cat.name !== 'Starting Balances') {
            allCats.push(cat.name);
            map[cat.name] = cat.id;
          }
        }
      }
      categories = allCats;
      categoryMap = map;
      if (!lastCategory && allCats.length > 0) lastCategory = allCats[0];
    } catch { /* silent */ }
  }

  function mapTransaction(t: any) {
    return {
      id: t.id,
      date: formatDateShort(t.date),
      payee: t.payeeName || t.payee || '',
      note: t.notes || '',
      account: t.accountName || '',
      accountId: t.accountId || '',
      category: t.categoryName || '',
      categoryType: t.transfer_id ? 'transfer' : (t.categoryName ? 'normal' : 'uncat'),
      amount: t.amountDollars || 0,
      categoryId: t.categoryId || ''
    };
  }

  async function loadTransactions(append = false) {
    if (loadingMore) return;
    if (!append) { loading = true; transactions = []; hasMore = true; }
    else loadingMore = true;

    try {
      let data: any[];
      if (activeAccountId) {
        // Per-account: supports offset/limit pagination
        const offset = append ? transactions.length : 0;
        const resp = await api(`/transactions?accountId=${activeAccountId}&limit=${PAGE_SIZE}&offset=${offset}`);
        data = resp.transactions || resp || [];
      } else {
        // All accounts: /recent doesn't support offset
        // Load more when viewing uncategorized to capture all of them
        const limit = activeTab === 'uncategorized'
          ? Math.max(500, (append ? transactions.length + PAGE_SIZE : PAGE_SIZE))
          : (append ? transactions.length + PAGE_SIZE : PAGE_SIZE);
        data = await api(`/transactions/recent?limit=${limit}`);
        if (append) {
          const existingIds = new Set(transactions.map(t => t.id));
          data = data.filter((t: any) => !existingIds.has(t.id));
        }
      }

      const mapped = (Array.isArray(data) ? data : []).map(mapTransaction);

      if (append) {
        transactions = [...transactions, ...mapped];
      } else {
        transactions = mapped;
      }
      hasMore = mapped.length >= PAGE_SIZE;
    } catch { /* silent */ }
    finally { loading = false; loadingMore = false; }
  }

  function loadMore() {
    if (hasMore && !loadingMore) loadTransactions(true);
  }

  function selectAccount(accountId: string | null) {
    activeAccountId = accountId;
    loadTransactions();
  }

  async function loadBudget() {
    try {
      const now = new Date();
      const month = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}`;
      const data = await api(`/budget/${month}`);
      budgetGroups = (data.categoryGroups || [])
        .filter((g: any) => g.categories?.length > 0)
        .map((g: any) => ({
          name: g.name,
          categories: g.categories
            .filter((c: any) => c.name !== 'Starting Balances')
            .map((c: any) => ({
              name: c.name,
              budgeted: Math.round(c.budgeted / 100),
              spent: Math.round(Math.abs(c.spent) / 100),
              available: Math.round(c.balance / 100)
            }))
        }))
        .filter((g: any) => g.categories.length > 0);
    } catch { /* silent */ }
  }

  async function loadSuggested() {
    try {
      const data = await api('/suggested-transfers');
      suggestedTransfers = data.map((s: any) => ({
        id: s.from.id + '-' + s.to.id,
        from: { account: s.from.account, payee: s.from.payee },
        to: { account: s.to.account, payee: s.to.payee },
        amount: s.amountDollars,
        confidence: s.confidence,
        fromId: s.from.id,
        toId: s.to.id
      }));
    } catch { /* silent */ }
  }

  // ── Actions ──
  async function categorize(id: string, category: string) {
    if (!category) return;
    lastCategory = category;
    let catId = categoryMap[category];
    // If category ID not found, refresh category map and retry
    if (!catId) {
      await loadCategories();
      catId = categoryMap[category];
      if (!catId) return; // Still not found, bail
    }
    // Save previous state for revert
    const prev = transactions.find(t => t.id === id);
    const prevCat = prev?.category || '';
    const prevType = prev?.categoryType || 'uncat';
    // Optimistic update
    const wasUncat = prevType === 'uncat';
    transactions = transactions.map(t =>
      t.id === id ? { ...t, category, categoryType: 'normal', categoryId: catId } : t
    );
    if (wasUncat) totalUncatCount = Math.max(0, totalUncatCount - 1);
    // Persist to backend
    try {
      await api(`/transactions/${id}`, {
        method: 'PATCH',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ category: catId })
      });
    } catch {
      // Revert on failure
      transactions = transactions.map(t =>
        t.id === id ? { ...t, category: prevCat, categoryType: prevType, categoryId: '' } : t
      );
      if (wasUncat) totalUncatCount++;
    }
  }

  async function bulkCategorize(category: string) {
    if (!category) return;
    lastCategory = category;
    const catId = categoryMap[category];
    const ids = Array.from(selected).filter(id => {
      const t = transactions.find(tx => tx.id === id);
      return t && t.categoryType === 'uncat';
    });

    // Optimistic update
    transactions = transactions.map(t =>
      ids.includes(t.id) ? { ...t, category, categoryType: 'normal', categoryId: catId } : t
    );
    totalUncatCount = Math.max(0, totalUncatCount - ids.length);
    selected = new Set();
    bulkCategoryOpen = false;

    // Persist each to backend
    for (const id of ids) {
      try {
        await api(`/transactions/${id}`, {
          method: 'PATCH',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ category: catId })
        });
      } catch { /* silent - optimistic already applied */ }
    }
  }

  async function makeTransfer(fromId: string, toId: string) {
    saving = true;
    try {
      await api('/make-transfer', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ transactionId1: fromId, transactionId2: toId })
      });
      // Reload transactions to reflect transfer state
      await loadTransactions();
    } catch { /* silent */ }
    finally { saving = false; }
  }

  async function linkSelectedAsTransfer() {
    if (selected.size !== 2) return;
    const ids = Array.from(selected);
    await makeTransfer(ids[0], ids[1]);
    selected = new Set();
  }

  async function linkSuggestedTransfer(suggestion: any) {
    await makeTransfer(suggestion.fromId, suggestion.toId);
    suggestedTransfers = suggestedTransfers.filter(s => s.id !== suggestion.id);
  }

  function dismissSuggestion(id: string) {
    suggestedTransfers = suggestedTransfers.filter(s => s.id !== id);
  }

  function toggleSelect(id: string) {
    const next = new Set(selected);
    if (next.has(id)) next.delete(id); else next.add(id);
    selected = next;
  }

  function handleRowKeydown(e: KeyboardEvent, txnId: string) {
    if (e.key === 'c' || e.key === 'C') {
      e.preventDefault();
      const row = (e.target as HTMLElement).closest('.txn-row');
      const select = row?.querySelector('.cat-select') as HTMLSelectElement | null;
      if (select) { select.focus(); select.click(); }
    }
  }

  // ── Formatters ──
  function formatDateShort(dateStr: string): string {
    const d = new Date(dateStr + 'T00:00:00');
    return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
  }

  function formatBudgetAmount(amount: number): string {
    return '$' + Math.abs(amount).toLocaleString('en-US');
  }

  function formatBalance(balance: number): string {
    const abs = Math.abs(balance);
    return (balance < 0 ? '-' : '') + '$' + abs.toLocaleString('en-US');
  }

  function formatAmount(amount: number): string {
    const abs = Math.abs(amount);
    const formatted = '$' + abs.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
    return amount >= 0 ? '+' + formatted : '-' + formatted;
  }

  async function loadSummary() {
    try {
      const [summary, uncat] = await Promise.all([
        api('/summary'),
        api('/uncategorized-count')
      ]);
      headerSpending = '$' + Math.abs(summary.spendingDollars || 0).toLocaleString('en-US');
      headerIncome = '$' + Math.abs(summary.incomeDollars || 0).toLocaleString('en-US');
      totalUncatCount = uncat.count || 0;
      const m = summary.month || '';
      if (m) {
        const d = new Date(m + '-01');
        currentMonthLabel = d.toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
      }
    } catch { /* silent */ }
  }

  // ── Init ──
  onMount(async () => {
    await Promise.all([loadAccounts(), loadCategories(), loadTransactions(), loadBudget(), loadSuggested(), loadSummary()]);
    loading = false;
  });
</script>

<div class="budget-page">
  <div class="budget-layout">
    <!-- Desktop sidebar -->
    <aside class="budget-sidebar desktop-only">
      <div class="sidebar-header">Budget</div>
      <div class="sidebar-nav">
        <button class="sidebar-nav-item" class:active={activeView === 'transactions' && !activeAccountId} onclick={() => { activeView = 'transactions'; selectAccount(null); }}>
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>
          All Transactions
          {#if uncatCount > 0}<span class="sidebar-badge">{uncatCount}</span>{/if}
        </button>
        <button class="sidebar-nav-item" class:active={activeView === 'budget'} onclick={() => activeView = 'budget'}>
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M2 10h20"/></svg>
          Budget
        </button>
      </div>
      <div class="sidebar-accounts">
        <div class="acct-group-header"><span>Budget</span><span class="acct-group-total positive">{formatBalance(accounts.reduce((s, a) => s + a.balance, 0))}</span></div>
        {#each accounts as acct}
          <button class="acct-row" class:active={activeAccountId === acct.id} onclick={() => { activeView = 'transactions'; selectAccount(acct.id || null); }}><span class="acct-name">{acct.name}</span><span class="acct-bal" class:positive={acct.positive} class:negative={!acct.positive}>{formatBalance(acct.balance)}</span></button>
        {/each}
        <div class="acct-group-header" style="margin-top:8px"><span>Off Budget</span><span class="acct-group-total">{formatBalance(offBudgetAccounts.reduce((s, a) => s + a.balance, 0))}</span></div>
        {#each offBudgetAccounts as acct}
          <button class="acct-row" class:active={activeAccountId === acct.id} onclick={() => { activeView = 'transactions'; selectAccount(acct.id || null); }}><span class="acct-name">{acct.name}</span><span class="acct-bal positive">{formatBalance(acct.balance)}</span></button>
        {/each}
      </div>
    </aside>

    <!-- Main workspace -->
    <div class="budget-main">
      <div class="budget-header">
        <div>
          {#if activeAccountId}
            <button class="back-to-all" onclick={() => selectAccount(null)}>
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 18l-6-6 6-6"/></svg>
              All Transactions
            </button>
            <div class="budget-title">{accounts.find(a => a.id === activeAccountId)?.name || offBudgetAccounts.find(a => a.id === activeAccountId)?.name || 'Account'}</div>
          {:else}
            <div class="budget-label">Budget</div>
            <div class="budget-title">{currentMonthLabel} · <strong>{headerSpending}</strong> spent</div>
            <div class="budget-meta">{headerIncome} income · {uncatCount} uncategorized</div>
          {/if}
        </div>
        <button class="accounts-trigger mobile-only" onclick={() => accountsOpen = !accountsOpen}>
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M2 10h20"/></svg>
          Accounts
          <svg viewBox="0 0 10 6" fill="none" class="chevron" class:open={accountsOpen}><path d="M1 1l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
        </button>
      </div>

      <!-- Mobile view toggle -->
      <div class="view-toggle mobile-only">
        <button class="view-btn" class:active={activeView === 'transactions'} onclick={() => activeView = 'transactions'}>Transactions</button>
        <button class="view-btn" class:active={activeView === 'budget'} onclick={() => activeView = 'budget'}>Budget</button>
      </div>

      {#if accountsOpen}
        <div class="mobile-accounts">
          <div class="acct-group-header"><span>Budget</span><span class="acct-group-total positive">{formatBalance(accounts.reduce((s, a) => s + a.balance, 0))}</span></div>
          {#each accounts as acct}
            <!-- svelte-ignore a11y_no_static_element_interactions -->
            <div class="mobile-acct-row" onclick={() => { selectAccount(acct.id || null); accountsOpen = false; }}><span class="acct-name">{acct.name}</span><span class="acct-bal" class:positive={acct.positive} class:negative={!acct.positive}>{formatBalance(acct.balance)}</span></div>
          {/each}
          <div class="acct-group-header" style="margin-top:8px"><span>Off Budget</span><span class="acct-group-total">{formatBalance(offBudgetAccounts.reduce((s, a) => s + a.balance, 0))}</span></div>
          {#each offBudgetAccounts as acct}
            <!-- svelte-ignore a11y_no_static_element_interactions -->
            <div class="mobile-acct-row" onclick={() => { selectAccount(acct.id || null); accountsOpen = false; }}><span class="acct-name">{acct.name}</span><span class="acct-bal positive">{formatBalance(acct.balance)}</span></div>
          {/each}
        </div>
      {/if}

      {#if activeView === 'transactions'}
      <!-- Suggested Transfers -->
      {#if suggestedTransfers.length > 0}
        <div class="suggestions">
          <div class="suggestions-header">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:16px;height:16px;color:var(--accent)"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg>
            <span class="suggestions-title">Suggested Transfers</span>
            <span class="suggestions-count">{suggestedTransfers.length}</span>
          </div>
          {#each suggestedTransfers as s}
            <div class="suggestion-row">
              <div class="suggestion-pair">
                <div class="suggestion-acct">{s.from.account}</div>
                <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:14px;height:14px;color:var(--text-4);flex-shrink:0"><path d="M5 12h14"/><polyline points="12 5 19 12 12 19"/></svg>
                <div class="suggestion-acct">{s.to.account}</div>
              </div>
              <div class="suggestion-amount">${s.amount.toLocaleString('en-US', { minimumFractionDigits: 2 })}</div>
              <div class="suggestion-actions">
                <button class="sug-btn link" onclick={() => linkSuggestedTransfer(s)}>Link</button>
                <button class="sug-btn skip" onclick={() => dismissSuggestion(s.id)}>Skip</button>
              </div>
            </div>
          {/each}
        </div>
      {/if}

      <!-- Selection bar -->
      {#if selected.size > 0}
        <div class="selection-bar">
          <span class="selection-count">{selected.size} selected</span>
          {#if canTransfer}
            <button class="transfer-btn" onclick={linkSelectedAsTransfer}>
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:14px;height:14px"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg>
              Make Transfer
            </button>
          {/if}
          <div class="bulk-cat">
            {#if bulkCategoryOpen}
              <select class="bulk-cat-select" onchange={(e) => bulkCategorize((e.target as HTMLSelectElement).value)}>
                <option value="">Apply category...</option>
                {#each sortedCategories() as cat}
                  <option value={cat}>{cat}</option>
                {/each}
              </select>
            {:else}
              <button class="bulk-cat-btn" onclick={() => bulkCategoryOpen = true}>
                Set Category
              </button>
            {/if}
          </div>
          <button class="clear-btn" onclick={() => { selected = new Set(); bulkCategoryOpen = false; }}>Clear</button>
        </div>
      {/if}

      <!-- Tabs -->
      <div class="budget-tabs">
        <button class="tab" class:active={activeTab === 'all'} onclick={() => { activeTab = 'all'; loadTransactions(); }}>All</button>
        <button class="tab" class:active={activeTab === 'uncategorized'} onclick={() => { activeTab = 'uncategorized'; loadTransactions(); }}>Uncategorized <span class="tab-badge">{uncatCount}</span></button>
        <button class="tab" class:active={activeTab === 'categorized'} onclick={() => { activeTab = 'categorized'; loadTransactions(); }}>Categorized</button>
      </div>

      <!-- Transactions -->
      <div class="txn-card">
        {#each filteredTransactions() as txn (txn.id)}
          <!-- svelte-ignore a11y_no_static_element_interactions -->
          <div
            class="txn-row"
            class:txn-uncat={txn.categoryType === 'uncat'}
            class:txn-selected={selected.has(txn.id)}
            tabindex="0"
            onkeydown={(e) => handleRowKeydown(e, txn.id)}
          >
            <input type="checkbox" class="txn-check" checked={selected.has(txn.id)} onchange={() => toggleSelect(txn.id)} />
            <div class="txn-date">{txn.date}</div>
            <div class="txn-payee">
              {#if txn.categoryType === 'transfer'}
                <div class="txn-name transfer-name">
                  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:13px;height:13px;flex-shrink:0"><polyline points="17 1 21 5 17 9"/><path d="M3 11V9a4 4 0 0 1 4-4h14"/><polyline points="7 23 3 19 7 15"/><path d="M21 13v2a4 4 0 0 1-4 4H3"/></svg>
                  {txn.payee}
                </div>
              {:else}
                <div class="txn-name">{txn.payee}</div>
                {#if txn.note}<div class="txn-note">{txn.note}</div>{/if}
              {/if}
            </div>
            <div class="txn-account">{txn.account}</div>
            <div class="txn-category">
              {#if txn.categoryType === 'transfer'}
                <span class="cat-pill transfer">Transfer</span>
              {:else if txn.categoryType === 'uncat'}
                <select class="cat-select" onchange={(e) => categorize(txn.id, (e.target as HTMLSelectElement).value)}>
                  <option value="">Select category</option>
                  {#each sortedCategories() as cat}
                    <option value={cat}>{cat}</option>
                  {/each}
                </select>
              {:else}
                <span class="cat-pill">{txn.category}</span>
              {/if}
            </div>
            <div class="txn-amount" class:pos={txn.amount >= 0} class:neg={txn.amount < 0}>{formatAmount(txn.amount)}</div>
          </div>
        {/each}
        {#if hasMore}
          <button class="load-more-btn" onclick={loadMore} disabled={loadingMore}>
            {loadingMore ? 'Loading...' : 'Load more transactions'}
          </button>
        {/if}
      </div>
      {:else}
      <!-- Budget Overview -->
      <div class="budget-overview">
        {#each budgetGroups as group}
          <div class="budget-group">
            <div class="budget-group-header">{group.name}</div>
            <div class="budget-table">
              <div class="budget-table-header">
                <span class="bt-name">Category</span>
                <span class="bt-val">Budgeted</span>
                <span class="bt-val">Spent</span>
                <span class="bt-val">Available</span>
              </div>
              {#each group.categories as cat}
                <div class="budget-table-row" class:overspent={cat.available < 0}>
                  <span class="bt-name">{cat.name}</span>
                  <span class="bt-val">{formatBudgetAmount(cat.budgeted)}</span>
                  <span class="bt-val spent">{formatBudgetAmount(cat.spent)}</span>
                  <span class="bt-val" class:positive={cat.available > 0} class:negative={cat.available < 0}>{cat.available < 0 ? '-' : ''}{formatBudgetAmount(Math.abs(cat.available))}</span>
                </div>
              {/each}
            </div>
          </div>
        {/each}
      </div>
      {/if}
    </div>
  </div>
</div>

<style>
  .budget-page { padding: 0; margin: -16px; }
  .budget-layout { display: flex; min-height: calc(100vh - 56px); }

  .desktop-only { display: none; }
  @media (min-width: 768px) { .desktop-only { display: flex; } }
  .mobile-only { display: flex; }
  @media (min-width: 768px) { .mobile-only { display: none; } }

  /* ── Sidebar ── */
  .budget-sidebar { width: 250px; flex-shrink: 0; border-right: 1px solid var(--border); background: var(--surface); flex-direction: column; overflow-y: auto; }
  .sidebar-header { font-size: var(--text-md); font-weight: 600; padding: var(--sp-5) var(--sp-4) var(--sp-3); color: var(--text-1); }
  .sidebar-nav { padding: 0 var(--sp-2) var(--sp-2); }
  .sidebar-nav-item { display: flex; align-items: center; gap: var(--sp-2); width: 100%; padding: 9px 12px; background: none; border: none; border-radius: var(--radius-md); font-size: var(--text-sm); font-weight: 500; color: var(--text-3); cursor: pointer; transition: all 150ms; text-align: left; font-family: var(--font); }
  .sidebar-nav-item:hover { color: var(--text-1); background: var(--card-hover); }
  .sidebar-nav-item.active { color: var(--text-1); background: var(--accent-dim); }
  .sidebar-nav-item :global(svg) { width: 16px; height: 16px; flex-shrink: 0; }
  .sidebar-badge { margin-left: auto; font-size: var(--text-xs); font-family: var(--mono); background: var(--accent-dim); color: var(--accent); padding: 1px 6px; border-radius: var(--radius-xs); }
  .sidebar-accounts { padding: var(--sp-2) 0; border-top: 1px solid var(--border); flex: 1; }
  .acct-group-header { display: flex; justify-content: space-between; padding: var(--sp-2) var(--sp-4) var(--sp-1); font-size: var(--text-xs); font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-4); }
  .acct-group-total { font-family: var(--mono); font-size: var(--text-xs); color: var(--text-3); }
  .acct-group-total.positive { color: var(--success); }
  .acct-row { display: flex; align-items: center; width: 100%; padding: 6px 16px; background: none; border: none; font-size: var(--text-sm); color: var(--text-2); cursor: pointer; transition: background 150ms; text-align: left; font-family: var(--font); }
  .acct-row:hover { background: var(--card-hover); }
  .acct-row.active { background: var(--accent-dim); color: var(--accent); }

  .load-more-btn {
    display: block; width: 100%; padding: var(--sp-3); margin-top: var(--sp-1);
    background: none; border: 1px dashed var(--border); border-radius: var(--radius-sm);
    font-size: var(--text-sm); font-weight: 500; color: var(--text-3); cursor: pointer;
    font-family: var(--font); transition: all var(--transition);
  }
  .load-more-btn:hover { background: var(--card-hover); color: var(--text-1); }
  .load-more-btn:disabled { opacity: 0.5; cursor: default; }
  .acct-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .acct-bal { margin-left: auto; font-family: var(--mono); font-size: var(--text-sm); flex-shrink: 0; }
  .acct-bal.positive { color: var(--success); }
  .acct-bal.negative { color: var(--error); }

  /* ── Main ── */
  .budget-main { flex: 1; min-width: 0; padding: var(--sp-6); overflow-y: auto; }
  .budget-header { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: var(--sp-4); }
  .budget-label { font-size: var(--text-sm); font-weight: 600; color: var(--text-4); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: var(--sp-1); }
  .back-to-all {
    display: inline-flex; align-items: center; gap: var(--sp-1);
    font-size: var(--text-sm); color: var(--text-3); background: none; border: none;
    cursor: pointer; font-family: var(--font); padding: 0; margin-bottom: var(--sp-1);
    transition: color var(--transition);
  }
  .back-to-all:hover { color: var(--accent); }
  .back-to-all svg { width: 14px; height: 14px; }
  .budget-title { font-size: var(--text-xl); font-weight: 300; color: var(--text-1); line-height: 1.2; }
  .budget-meta { font-size: var(--text-sm); color: var(--text-3); margin-top: var(--sp-1); }

  /* Mobile trigger */
  .accounts-trigger { align-items: center; gap: var(--sp-1.5); padding: var(--sp-2) var(--sp-3); border-radius: var(--radius-md); background: var(--card); border: 1px solid var(--border); font-size: var(--text-sm); font-weight: 500; color: var(--text-2); cursor: pointer; font-family: var(--font); transition: all var(--transition); flex-shrink: 0; }
  .accounts-trigger:hover { background: var(--card-hover); }
  .accounts-trigger :global(svg) { width: 16px; height: 16px; }
  .chevron { width: 10px; height: 10px; transition: transform 200ms; }
  .chevron.open { transform: rotate(180deg); }
  .mobile-accounts { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--sp-3) var(--sp-4); margin-bottom: var(--sp-4); }
  .mobile-acct-row { display: flex; justify-content: space-between; padding: var(--sp-2) 0; font-size: var(--text-sm); color: var(--text-2); cursor: pointer; transition: color var(--transition); }
  .mobile-acct-row:hover { color: var(--text-1); }

  /* ── Suggested Transfers ── */
  .suggestions { margin-bottom: var(--sp-4); padding: 14px; border-radius: var(--radius); background: color-mix(in srgb, var(--accent-dim) 60%, transparent); border: 1px solid var(--accent-border); }
  .suggestions-header { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: 10px; }
  .suggestions-title { font-size: var(--text-sm); font-weight: 600; color: var(--text-2); }
  .suggestions-count { font-size: var(--text-xs); font-family: var(--mono); background: var(--accent); color: white; padding: 1px 5px; border-radius: var(--radius-xs); }
  .suggestion-row { display: flex; align-items: center; gap: var(--sp-3); padding: 9px 12px; border-radius: var(--radius-md); background: var(--card); margin-bottom: var(--sp-1); transition: background var(--transition); }
  .suggestion-row:last-child { margin-bottom: 0; }
  .suggestion-row:hover { background: var(--card-hover); }
  .suggestion-pair { display: flex; align-items: center; gap: var(--sp-2); flex: 1; min-width: 0; }
  .suggestion-acct { font-size: var(--text-sm); font-weight: 500; color: var(--text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .suggestion-amount { font-family: var(--mono); font-size: var(--text-sm); font-weight: 500; color: var(--text-3); flex-shrink: 0; }
  .suggestion-actions { display: flex; gap: var(--sp-1); flex-shrink: 0; }
  .sug-btn { padding: 5px 11px; border-radius: var(--radius-sm); font-size: var(--text-sm); font-weight: 600; border: none; cursor: pointer; font-family: var(--font); transition: all var(--transition); }
  .sug-btn.link { background: var(--accent); color: white; }
  .sug-btn.link:hover { opacity: 0.9; }
  .sug-btn.skip { background: none; color: var(--text-4); }
  .sug-btn.skip:hover { color: var(--text-2); }

  /* ── Selection bar ── */
  .selection-bar { display: flex; align-items: center; gap: var(--sp-3); padding: 10px var(--sp-4); margin-bottom: var(--sp-3); border-radius: var(--radius); background: var(--accent-dim); border: 1px solid var(--accent-focus); }
  .selection-count { font-size: var(--text-base); font-weight: 600; color: var(--text-1); }
  .selection-hint { font-size: var(--text-sm); color: var(--text-3); }
  .transfer-btn { display: flex; align-items: center; gap: var(--sp-1.5); padding: 6px 14px; border-radius: var(--radius-sm); background: var(--accent); color: white; border: none; font-size: var(--text-sm); font-weight: 600; cursor: pointer; font-family: var(--font); }
  .bulk-cat { display: flex; align-items: center; }
  .bulk-cat-btn { padding: 6px 14px; border-radius: var(--radius-sm); background: var(--card); border: 1px solid var(--border); font-size: var(--text-sm); font-weight: 500; color: var(--text-2); cursor: pointer; font-family: var(--font); transition: all var(--transition); }
  .bulk-cat-btn:hover { border-color: var(--accent); color: var(--accent); }
  .bulk-cat-select { padding: 6px 10px; border-radius: var(--radius-sm); font-size: var(--text-sm); font-weight: 500; border: 1px solid var(--accent); background: var(--card); color: var(--text-1); font-family: var(--font); cursor: pointer; min-width: 140px; }
  .bulk-cat-select:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
  .clear-btn { margin-left: auto; background: none; border: none; font-size: var(--text-sm); color: var(--text-3); cursor: pointer; font-family: var(--font); }
  .clear-btn:hover { color: var(--text-1); }

  /* ── Tabs ── */
  .budget-tabs { display: flex; gap: var(--sp-1); margin-bottom: var(--sp-4); }
  .tab { padding: var(--sp-2) 14px; border-radius: var(--radius-md); font-size: var(--text-base); font-weight: 500; color: var(--text-3); background: none; border: none; cursor: pointer; transition: all var(--transition); font-family: var(--font); }
  .tab:hover { color: var(--text-1); background: var(--card-hover); }
  .tab.active { color: var(--text-1); background: var(--card); box-shadow: var(--shadow-xs); }
  .tab-badge { font-size: var(--text-xs); font-family: var(--mono); background: var(--accent-dim); color: var(--accent); padding: 1px 6px; border-radius: var(--radius-xs); margin-left: var(--sp-1); }

  /* ── Transaction card ── */
  .txn-card { background: var(--card); border-radius: var(--radius); border: 1px solid var(--border); box-shadow: var(--shadow-md); overflow: hidden; }
  .txn-row { display: flex; align-items: center; gap: 14px; padding: 15px 16px; transition: background var(--transition); }
  .txn-row:hover { background: var(--card-hover); }
  .txn-row + .txn-row { border-top: 1px solid var(--border); }
  .txn-row:nth-child(even) { background: color-mix(in srgb, var(--surface) 68%, var(--card)); }
  .txn-row:nth-child(even):hover { background: var(--card-hover); }
  .txn-row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; z-index: 1; }
  .txn-uncat { border-left: 4px solid var(--warning); }
  .txn-selected { background: color-mix(in srgb, var(--accent) 6%, var(--card)) !important; border-left: 4px solid var(--accent); }

  .txn-check { width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer; flex-shrink: 0; }
  .txn-date { font-size: var(--text-sm); color: var(--text-4); width: 48px; flex-shrink: 0; }
  .txn-payee { flex: 1.5; min-width: 0; }
  .txn-name { font-size: var(--text-base); font-weight: 600; color: var(--text-1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .transfer-name { display: flex; align-items: center; gap: var(--sp-1.5); color: var(--accent); font-weight: 500; }
  .txn-note { font-size: var(--text-xs); color: var(--text-4); margin-top: var(--sp-0.5); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .txn-account { flex: 0.7; font-size: var(--text-sm); color: var(--text-4); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .txn-category { flex: 0.9; padding-right: var(--sp-2); }

  .cat-pill { display: inline-block; padding: var(--sp-1) 10px; border-radius: var(--radius-sm); font-size: var(--text-sm); font-weight: 500; background: var(--accent-dim); color: var(--accent); }
  .cat-pill.transfer { background: var(--accent-dim); color: var(--accent); font-weight: 500; }

  .cat-select {
    padding: 8px 10px;
    border-radius: var(--radius-md);
    font-size: var(--text-sm);
    font-weight: 500;
    border: 1px solid color-mix(in srgb, var(--warning) 50%, var(--border));
    background: color-mix(in srgb, var(--warning) 2%, var(--card));
    color: var(--text-1);
    font-family: var(--font);
    cursor: pointer;
    width: 100%;
    max-width: 160px;
    min-height: 38px;
    transition: all var(--transition);
  }
  .cat-select:hover { border-color: var(--accent); background: var(--card); }
  .cat-select:focus { outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent); background: var(--card); }

  .txn-amount { font-family: var(--mono); font-size: var(--text-base); font-weight: 600; text-align: right; min-width: 90px; padding-left: var(--sp-2); }
  .txn-amount.pos { color: var(--success); }
  .txn-amount.neg { color: var(--error); }

  /* ── Sidebar refinement ── */
  .acct-row { padding: 5px 16px; font-size: var(--text-xs); }
  .acct-bal { font-size: var(--text-xs); }

  /* ── View toggle (mobile) ── */
  .view-toggle { gap: var(--sp-0.5); padding: 3px; background: var(--surface); border-radius: 10px; border: 1px solid var(--border); margin-bottom: var(--sp-4); }
  .view-btn { flex: 1; padding: 7px 0; border-radius: var(--radius-md); font-size: var(--text-sm); font-weight: 500; border: none; background: none; color: var(--text-3); cursor: pointer; font-family: var(--font); transition: all var(--transition); }
  .view-btn.active { background: var(--card); color: var(--text-1); box-shadow: var(--shadow-xs); }

  /* ── Budget Overview ── */
  .budget-overview { display: flex; flex-direction: column; gap: var(--sp-6); }
  .budget-group-header { font-size: var(--text-xs); font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-4); margin-bottom: var(--sp-2); }
  .budget-table { background: var(--card); border-radius: var(--radius); border: 1px solid var(--border); box-shadow: var(--shadow-sm); overflow: hidden; }
  .budget-table-header { display: flex; padding: 10px 16px; font-size: var(--text-xs); font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-4); border-bottom: 1px solid var(--border); }
  .budget-table-row { display: flex; padding: 14px 16px; cursor: default; }
  .budget-table-row + .budget-table-row { border-top: 1px solid var(--border); }
  .budget-table-row:nth-child(even) { background: color-mix(in srgb, var(--surface) 55%, var(--card)); }
  .budget-table-row.overspent { border-left: 3px solid var(--error); }
  .bt-name { flex: 1.5; font-size: var(--text-base); font-weight: 400; color: var(--text-2); }
  .budget-table-header .bt-name { font-size: var(--text-xs); font-weight: 600; color: var(--text-4); }
  .bt-val { flex: 1; text-align: right; font-family: var(--mono); font-size: var(--text-base); font-weight: 600; color: var(--text-1); }
  .budget-table-header .bt-val { font-family: var(--font); font-size: var(--text-xs); font-weight: 600; color: var(--text-4); }
  .bt-val.spent { color: var(--error); }
  .bt-val.positive { color: var(--success); font-weight: 600; }
  .bt-val.negative { color: var(--error); font-weight: 600; }

  /* ── Mobile ── */
  @media (max-width: 767px) {
    .budget-page { margin: -16px; }
    .budget-main { padding: var(--sp-4) var(--sp-4) var(--sp-20); }
    .txn-account { display: none; }
    .txn-row { gap: 10px; padding: 16px 14px; }
    .txn-payee { flex: 1.2; }
    .txn-category { flex: 1; }
    .txn-date { width: 42px; font-size: var(--text-sm); }
    .txn-name { font-size: var(--text-base); }
    .txn-amount { min-width: 75px; font-size: var(--text-sm); }
    .cat-select { max-width: none; min-height: 40px; font-size: var(--text-base); }
    .suggestion-pair { flex-direction: column; align-items: flex-start; gap: var(--sp-0.5); }
    .suggestion-row { flex-wrap: wrap; }
    .selection-bar { flex-wrap: wrap; gap: var(--sp-2); }
  }

  @media (min-width: 768px) {
    .budget-page { margin: -20px; }
    .mobile-accounts { display: none; }
  }
</style>
