OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-wallet-desktop / desktop / wallet.js

4217 lines · javascript ref:
// wallet.js - Hyper Wallet Desktop Frontend
// Uses pear-pipe IPC to communicate with main process

// Wallet initialization state
let currentIdentityMnemonic = null
let currentWalletMnemonic = null

// Authentication token management
function getToken() {
  return localStorage.getItem('hyper-wallet-token')
}

function saveToken(token) {
  localStorage.setItem('hyper-wallet-token', token)
}

function clearToken() {
  localStorage.removeItem('hyper-wallet-token')
}

// Authenticated fetch wrapper
async function authFetch(url, options = {}) {
  const token = getToken()

  // Add Authorization header if token exists
  if (token) {
    options.headers = {
      ...options.headers,
      'Authorization': `Bearer ${token}`
    }
  }

  try {
    // Use IPC to communicate with main process (avoids CSP cross-origin issues)
    const response = await window.ipcFetch(url, options)

    // Handle 401 Unauthorized
    if (response.status === 401) {
      console.warn('⚠️  Unauthorized - clearing token')
      clearToken()
      // Don't show setup wizard here - let the init() flow handle password prompt
      // Only throw error so the calling code knows authentication is needed
      throw new Error('Authentication required')
    }

    return response
  } catch (err) {
    console.error('Fetch request failed:', err)
    throw err
  }
}

// Setup Wizard Functions
function showSetupWizard() {
  document.getElementById('setup-wizard').style.display = 'flex'
  document.getElementById('main-app').style.display = 'none'
  showSetupWelcome()
}

function hideSetupWizard() {
  document.getElementById('setup-wizard').style.display = 'none'
  document.getElementById('main-app').style.display = 'block'
}

function showSetupWelcome() {
  document.getElementById('setup-welcome').style.display = 'block'
  document.getElementById('setup-create').style.display = 'none'
  document.getElementById('setup-restore').style.display = 'none'
  document.getElementById('setup-join').style.display = 'none'
}

function showCreateWallet() {
  document.getElementById('setup-welcome').style.display = 'none'
  document.getElementById('setup-create').style.display = 'block'
  document.getElementById('setup-restore').style.display = 'none'
  document.getElementById('setup-join').style.display = 'none'

  // Reset create wallet screen
  document.getElementById('mnemonic-display').style.display = 'none'
  document.getElementById('btn-generate-mnemonic').style.display = 'inline-block'
  document.getElementById('btn-confirm-mnemonic').style.display = 'none'
  document.getElementById('mnemonic-confirm-check').checked = false
  currentIdentityMnemonic = null
  currentWalletMnemonic = null
}

function showRestoreWallet() {
  document.getElementById('setup-welcome').style.display = 'none'
  document.getElementById('setup-create').style.display = 'none'
  document.getElementById('setup-restore').style.display = 'block'
  document.getElementById('setup-join').style.display = 'none'

  // Reset restore wallet screen
  document.getElementById('restore-identity-mnemonic').value = ''
  document.getElementById('restore-wallet-mnemonic').value = ''
  document.getElementById('restore-error').textContent = ''
}

async function generateNewWallet() {
  try {
    // Validate password
    const password = document.getElementById('create-password').value
    const passwordConfirm = document.getElementById('create-password-confirm').value
    const errorDiv = document.getElementById('create-password-error')

    errorDiv.style.display = 'none'

    if (!password || password.length < 8) {
      errorDiv.textContent = 'Password must be at least 8 characters'
      errorDiv.style.display = 'block'
      return
    }

    if (password !== passwordConfirm) {
      errorDiv.textContent = 'Passwords do not match'
      errorDiv.style.display = 'block'
      return
    }

    const btn = document.getElementById('btn-generate-mnemonic')
    btn.disabled = true
    btn.textContent = 'Generating...'

    const res = await authFetch(`/api/init`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ password })
    })

    const data = await res.json()

    if (data.error) {
      showError(data.error)
      btn.disabled = false
      btn.textContent = 'Generate Recovery Phrases'
      return
    }

    // Save API token for authentication
    if (data.token) {
      saveToken(data.token)
      console.log('✅ API token saved')
    }

    // Store mnemonics temporarily (already saved on server)
    currentIdentityMnemonic = data.identityMnemonic
    currentWalletMnemonic = data.walletMnemonic

    // Display identity mnemonic (24 words)
    const identityWords = data.identityMnemonic.split(' ')
    const identityMnemonicWords = document.getElementById('identity-mnemonic-words')
    identityMnemonicWords.innerHTML = identityWords.map((word, index) => `
      <div class="mnemonic-word">
        <span class="mnemonic-word-number">${index + 1}</span>
        <span>${word}</span>
      </div>
    `).join('')

    // Display wallet mnemonic (12 words)
    const walletWords = data.walletMnemonic.split(' ')
    const walletMnemonicWords = document.getElementById('wallet-mnemonic-words')
    walletMnemonicWords.innerHTML = walletWords.map((word, index) => `
      <div class="mnemonic-word">
        <span class="mnemonic-word-number">${index + 1}</span>
        <span>${word}</span>
      </div>
    `).join('')

    // Show mnemonic display
    document.getElementById('mnemonic-display').style.display = 'block'
    document.getElementById('btn-generate-mnemonic').style.display = 'none'
    document.getElementById('btn-confirm-mnemonic').style.display = 'inline-block'

    console.log('Public key:', data.publicKey)

  } catch (err) {
    console.error('Generate wallet error:', err)
    showError('Failed to generate wallet')
  }
}

// Enable confirm button when checkbox is checked
document.addEventListener('DOMContentLoaded', () => {
  const checkbox = document.getElementById('mnemonic-confirm-check')
  const confirmBtn = document.getElementById('btn-confirm-mnemonic')

  if (checkbox && confirmBtn) {
    checkbox.addEventListener('change', (e) => {
      confirmBtn.disabled = !e.target.checked
    })
  }

  // Event delegation for copying contact addresses
  document.addEventListener('click', (e) => {
    if (e.target.classList.contains('copyable-address')) {
      const address = e.target.getAttribute('data-address')
      if (address) {
        copyToClipboard(address, 'Address copied!')
      }
    }
  })
})

function confirmMnemonicBackup() {
  if (!currentIdentityMnemonic || !currentWalletMnemonic) {
    showError('No mnemonics generated')
    return
  }

  if (!document.getElementById('mnemonic-confirm-check').checked) {
    showError('Please confirm you have backed up BOTH recovery phrases')
    return
  }

  // System is already initialized on server, just transition to main app
  hideSetupWizard()
  init()
}

async function restoreWallet() {
  const identityInput = document.getElementById('restore-identity-mnemonic')
  const walletInput = document.getElementById('restore-wallet-mnemonic')
  const password = document.getElementById('restore-password').value
  const passwordConfirm = document.getElementById('restore-password-confirm').value
  const errorDiv = document.getElementById('restore-error')

  const identityMnemonic = identityInput.value.trim()
  const walletMnemonic = walletInput.value.trim()

  errorDiv.textContent = ''

  if (!identityMnemonic || !walletMnemonic) {
    errorDiv.textContent = 'Please enter both recovery phrases'
    return
  }

  if (!password || password.length < 8) {
    errorDiv.textContent = 'Password must be at least 8 characters'
    return
  }

  if (password !== passwordConfirm) {
    errorDiv.textContent = 'Passwords do not match'
    return
  }

  try {
    const res = await authFetch(`/api/restore`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        identityMnemonic,
        walletMnemonic,
        password
      })
    })

    const data = await res.json()

    if (data.error) {
      errorDiv.textContent = data.error
      return
    }

    // Save API token for authentication
    if (data.token) {
      saveToken(data.token)
      console.log('✅ API token saved')
    }

    showSuccess('System restored successfully!')
    setTimeout(() => {
      hideSetupWizard()
      init()
    }, 1000)

  } catch (err) {
    console.error('Restore wallet error:', err)
    errorDiv.textContent = 'Failed to restore system'
  }
}

function showJoinWallet() {
  document.getElementById('setup-welcome').style.display = 'none'
  document.getElementById('setup-create').style.display = 'none'
  document.getElementById('setup-restore').style.display = 'none'
  document.getElementById('setup-join').style.display = 'block'

  // Reset join wallet screen
  document.getElementById('join-invite-code').value = ''
  document.getElementById('join-device-name').value = ''
  document.getElementById('join-error').textContent = ''
  document.getElementById('join-status').style.display = 'none'
  document.getElementById('btn-join-wallet').disabled = false
}

async function joinExistingWallet() {
  const inviteCodeInput = document.getElementById('join-invite-code')
  const deviceNameInput = document.getElementById('join-device-name')
  const errorDiv = document.getElementById('join-error')
  const statusDiv = document.getElementById('join-status')
  const statusText = document.getElementById('join-status-text')
  const joinBtn = document.getElementById('btn-join-wallet')

  const inviteCode = inviteCodeInput.value.trim()
  const deviceName = deviceNameInput.value.trim()

  errorDiv.textContent = ''

  if (!inviteCode) {
    errorDiv.textContent = 'Please enter an invite code'
    return
  }

  if (!deviceName) {
    errorDiv.textContent = 'Please enter a device name'
    return
  }

  try {
    joinBtn.disabled = true
    statusDiv.style.display = 'block'
    statusText.textContent = 'Connecting to primary device...'

    const res = await authFetch(`/api/device/join`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        inviteCode,
        deviceName
      })
    })

    const data = await res.json()

    if (data.error) {
      errorDiv.textContent = data.error
      statusDiv.style.display = 'none'
      joinBtn.disabled = false
      return
    }

    statusText.textContent = '✅ Successfully joined wallet!'
    showSuccess('Wallet joined! Both identity and wallet keys synced.')

    setTimeout(() => {
      hideSetupWizard()
      init()
    }, 1500)

  } catch (err) {
    console.error('Join wallet error:', err)
    errorDiv.textContent = 'Failed to join wallet: ' + err.message
    statusDiv.style.display = 'none'
    joinBtn.disabled = false
  }
}

// Network state management
let selectedNetwork = localStorage.getItem('network') || 'testnet'

// Settings Functions
async function showSettings() {
  const modal = document.getElementById('settings-modal')
  modal.showModal()

  // Load settings data
  loadIdentityInfo()
  loadSyncStatus()
  await loadProviderSettings()

  // Set current network
  document.getElementById('default-network-select').value = selectedNetwork
}

async function loadProviderSettings() {
  try {
    const res = await authFetch(`/api/settings`)
    const data = await res.json()

    if (data.error) {
      console.error('Error loading settings:', data.error)
      return
    }

    const settings = data.settings

    // Set Bitcoin provider
    if (settings.blockchain.bitcoin.provider) {
      document.getElementById('bitcoin-provider-select').value = settings.blockchain.bitcoin.provider
    }

    // Set Ethereum provider
    if (settings.blockchain.ethereum.provider) {
      document.getElementById('ethereum-provider-select').value = settings.blockchain.ethereum.provider

      // Show/hide API key field based on provider
      const needsApiKey = settings.blockchain.ethereum.provider !== 'blockscout'
      const apiKeyRow = document.getElementById('ethereum-api-key-row')
      if (needsApiKey) {
        apiKeyRow.style.display = 'flex'
        if (settings.blockchain.ethereum.apiKey) {
          const masked = '***' + settings.blockchain.ethereum.apiKey.slice(-4)
          document.getElementById('ethereum-api-key-input').placeholder = masked
        }
      } else {
        apiKeyRow.style.display = 'none'
      }
    }
  } catch (err) {
    console.error('Error loading provider settings:', err)
  }
}

async function changeBitcoinProvider() {
  const provider = document.getElementById('bitcoin-provider-select').value

  try {
    const res = await authFetch(`/api/settings`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        key: 'blockchain.bitcoin.provider',
        value: provider
      })
    })

    const data = await res.json()

    if (data.error) {
      showError(`Error: ${data.error}`)
      return
    }

    showError(`Bitcoin provider set to ${provider}`)
  } catch (err) {
    showError(`Error: ${err.message}`)
  }
}

async function changeEthereumProvider() {
  const provider = document.getElementById('ethereum-provider-select').value

  try {
    const res = await authFetch(`/api/settings`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        key: 'blockchain.ethereum.provider',
        value: provider
      })
    })

    const data = await res.json()

    if (data.error) {
      showError(`Error: ${data.error}`)
      return
    }

    // Show/hide API key field based on provider
    const apiKeyRow = document.getElementById('ethereum-api-key-row')
    if (provider !== 'blockscout') {
      apiKeyRow.style.display = 'flex'
    } else {
      apiKeyRow.style.display = 'none'
    }

    showError(`Ethereum provider set to ${provider}`)
  } catch (err) {
    showError(`Error: ${err.message}`)
  }
}

async function saveEthereumApiKey() {
  const apiKey = document.getElementById('ethereum-api-key-input').value.trim()

  if (!apiKey) {
    showError('Please enter an API key')
    return
  }

  try {
    const res = await authFetch(`/api/settings`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        key: 'blockchain.ethereum.apiKey',
        value: apiKey
      })
    })

    const data = await res.json()

    if (data.error) {
      showError(`Error: ${data.error}`)
      return
    }

    showError('Ethereum API key saved')
    document.getElementById('ethereum-api-key-input').value = ''
    document.getElementById('ethereum-api-key-input').placeholder = '***' + apiKey.slice(-4)
  } catch (err) {
    showError(`Error: ${err.message}`)
  }
}

function closeSettings() {
  document.getElementById('settings-modal').close()
}

async function loadIdentityInfo() {
  try {
    const res = await authFetch(`/api/whoami`)
    const data = await res.json()

    if (data.error) {
      document.getElementById('identity-public-key').textContent = 'Not available'
      document.getElementById('identity-device-name').textContent = 'Not available'
      return
    }

    // Display public key (truncated)
    const pubKey = data.publicKey || 'Unknown'
    document.getElementById('identity-public-key').textContent =
      pubKey.length > 20 ? pubKey.substring(0, 16) + '...' : pubKey

    // Display device name (hostname)
    document.getElementById('identity-device-name').textContent = data.hostname || 'Unknown'

    // Update status badge
    const statusBadge = document.getElementById('identity-status-badge')
    statusBadge.textContent = 'Active'
    statusBadge.className = 'status-badge'
  } catch (err) {
    console.error('Failed to load identity info:', err)
    document.getElementById('identity-public-key').textContent = 'Error loading'
  }
}

async function loadSyncStatus() {
  try {
    const res = await authFetch(`/api/sync-status`)
    const data = await res.json()

    if (data.error) {
      document.getElementById('sync-status-badge').textContent = 'Offline'
      document.getElementById('sync-status-badge').className = 'status-badge status-error'
      document.getElementById('sync-device-count').textContent = '0'
      document.getElementById('sync-last-time').textContent = 'Never'
      return
    }

    // Update sync status
    const statusBadge = document.getElementById('sync-status-badge')
    if (data.syncing) {
      statusBadge.textContent = 'Syncing'
      statusBadge.className = 'status-badge status-syncing'
    } else {
      statusBadge.textContent = 'Synced'
      statusBadge.className = 'status-badge'
    }

    // Get real device count from device list
    const devicesRes = await authFetch(`/api/device/list`)
    const devicesData = await devicesRes.json()
    const deviceCount = devicesData.devices ? devicesData.devices.length : 1

    document.getElementById('sync-device-count').textContent = deviceCount

    // Update status based on device count
    if (deviceCount > 1) {
      statusBadge.textContent = 'Active'
      statusBadge.className = 'status-badge'
    } else {
      statusBadge.textContent = 'Single Device'
      statusBadge.className = 'status-badge status-warning'
    }

    document.getElementById('sync-last-time').textContent =
      deviceCount > 1 ? new Date().toLocaleString() : 'Never'
  } catch (err) {
    console.error('Failed to load sync status:', err)
  }
}

function changeDefaultNetwork() {
  const select = document.getElementById('default-network-select')
  setNetwork(select.value)
  showSuccess('Default network changed to ' + select.value)
}

async function exportIdentity() {
  if (!confirm('⚠️ WARNING: Your identity mnemonic will be displayed. Make sure no one can see your screen!\n\nContinue?')) {
    return
  }

  // Use modal instead of prompt() which is blocked in Pear/Electron
  const password = await promptForPasswordModal('Enter your encryption password to export identity mnemonic:')
  if (!password) {
    showError('Password required to export mnemonic')
    return
  }

  try {
    console.log('[Export] Requesting identity mnemonic...')
    const res = await authFetch(`/api/export-identity`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ password })
    })

    console.log('[Export] Response status:', res.status)
    const data = await res.json()
    console.log('[Export] Response data:', data.error ? `Error: ${data.error}` : 'Success')

    if (data.error) {
      showError(data.error)
      return
    }

    // Show mnemonic in modal (better than alert)
    if (data.mnemonic) {
      showMnemonicDisplay(data.mnemonic, 'Identity Mnemonic (24 words)')
    } else {
      showError('No mnemonic returned from server')
      console.error('[Export] No mnemonic in response:', data)
    }
  } catch (err) {
    console.error('[Export] Error:', err)
    showError('Failed to export identity mnemonic: ' + err.message)
  }
}

async function exportWalletMnemonic() {
  if (!confirm('⚠️ WARNING: Your wallet mnemonic controls your funds! Make sure no one can see your screen!\n\nContinue?')) {
    return
  }

  // Use modal instead of prompt() which is blocked in Pear/Electron
  const password = await promptForPasswordModal('Enter your encryption password to export wallet mnemonic:')
  if (!password) {
    showError('Password required to export mnemonic')
    return
  }

  try {
    console.log('[Export] Requesting wallet mnemonic...')
    const res = await authFetch(`/api/export-wallet`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ password })
    })

    console.log('[Export] Response status:', res.status)
    const data = await res.json()
    console.log('[Export] Response data:', data.error ? `Error: ${data.error}` : 'Success')

    if (data.error) {
      showError(data.error)
      return
    }

    // Show mnemonic in modal (better than alert)
    if (data.mnemonic) {
      showMnemonicDisplay(data.mnemonic, 'Wallet Mnemonic (12 words)')
    } else {
      showError('No mnemonic returned from server')
      console.error('[Export] No mnemonic in response:', data)
    }
  } catch (err) {
    console.error('[Export] Error:', err)
    showError('Failed to export wallet mnemonic: ' + err.message)
  }
}

function showWhoAmI() {
  // Run whoami and show in dialog
  showError('Feature coming soon: Detailed identity information')
}

async function addDevice() {
  try {
    // Start device add process
    const res = await authFetch(`/api/device/add`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' }
    })

    const data = await res.json()

    if (data.error) {
      showError(data.error)
      return
    }

    // Show invite modal
    const modal = document.getElementById('device-add-modal')
    document.getElementById('device-invite-code').value = data.inviteCode
    document.getElementById('device-pairing-status').textContent = '⏳ Waiting for device to connect...'
    document.getElementById('device-pairing-status').className = 'pairing-status waiting'
    document.getElementById('device-pairing-result').style.display = 'none'
    modal.showModal()

    // Poll for status
    const sessionId = data.sessionId
    const pollInterval = setInterval(async () => {
      const statusRes = await authFetch(`/api/device/add/${sessionId}`)
      const statusData = await statusRes.json()

      if (statusData.status === 'completed') {
        clearInterval(pollInterval)
        document.getElementById('device-pairing-status').textContent = `✅ Device added: ${statusData.deviceName}`
        document.getElementById('device-pairing-status').className = 'pairing-status success'
        document.getElementById('device-pairing-result').style.display = 'block'
        document.getElementById('device-pairing-result').innerHTML = `
          <p><strong>Device Name:</strong> ${statusData.deviceName}</p>
          <p><strong>Writer Key:</strong> ${statusData.writerKey.slice(0, 16)}...</p>
          <p>The new device is now part of your wallet and can participate in P2P sync.</p>
        `
        // Refresh devices list if open
        const devicesModal = document.getElementById('devices-modal')
        if (devicesModal.open) {
          loadDevicesList()
        }
      } else if (statusData.status === 'error') {
        clearInterval(pollInterval)
        document.getElementById('device-pairing-status').textContent = `❌ Error: ${statusData.error}`
        document.getElementById('device-pairing-status').className = 'pairing-status error'
      }
    }, 2000) // Poll every 2 seconds

    // Store interval ID for cleanup
    modal.dataset.pollInterval = pollInterval
  } catch (err) {
    showError(err.message)
  }
}

function closeDeviceAdd() {
  const modal = document.getElementById('device-add-modal')
  // Clear polling interval
  if (modal.dataset.pollInterval) {
    clearInterval(parseInt(modal.dataset.pollInterval))
    delete modal.dataset.pollInterval
  }
  modal.close()
}

function copyInviteCode() {
  const input = document.getElementById('device-invite-code')
  input.select()
  document.execCommand('copy')
  showError('Invite code copied to clipboard!')
}

async function showDevices() {
  try {
    const modal = document.getElementById('devices-modal')
    modal.showModal()
    await loadDevicesList()
  } catch (err) {
    showError(err.message)
  }
}

async function loadDevicesList() {
  try {
    const res = await authFetch(`/api/device/list`)
    const data = await res.json()

    if (data.error) {
      showError(data.error)
      return
    }

    const container = document.getElementById('devices-list')

    if (data.devices.length === 0) {
      container.innerHTML = '<p class="empty-state">No devices found. Add a device to enable multi-device sync.</p>'
      return
    }

    container.innerHTML = data.devices.map(device => `
      <div class="device-item ${device.isCurrentDevice ? 'current-device' : ''}">
        <div class="device-info">
          <div class="device-name">
            ${device.name}
            ${device.isCurrentDevice ? '<span class="badge">Current Device</span>' : ''}
          </div>

          <div class="device-keys">
            <div class="key-row">
              <span class="key-label">Identity Key:</span>
              <span class="key-value" onclick="copyToClipboard('${device.identityKey}', 'Identity key copied!')" title="Click to copy full key">
                ${device.identityKeyShort || 'N/A'}
              </span>
            </div>
            <div class="key-row">
              <span class="key-label">Writer Key:</span>
              <span class="key-value" onclick="copyToClipboard('${device.writerKey}', 'Writer key copied!')" title="Click to copy full key">
                ${device.writerKeyShort}
              </span>
            </div>
          </div>

          <div class="device-added">Added: ${new Date(device.addedAt).toLocaleString()}</div>
        </div>
        <div class="device-actions">
          ${!device.isCurrentDevice ? `
            <button class="btn-danger btn-small" onclick="removeDeviceConfirm('${device.writerKey}', '${device.name}')">
              Remove
            </button>
          ` : ''}
        </div>
      </div>
    `).join('')
  } catch (err) {
    showError(err.message)
  }
}

async function removeDeviceConfirm(deviceKey, deviceName) {
  const confirmed = confirm(
    `⚠️ Remove Device: ${deviceName}?\n\n` +
    `This will revoke this device's access to the wallet.\n` +
    `The device will no longer be able to:\n` +
    `- Create new transactions\n` +
    `- Participate in multi-device sync\n\n` +
    `Continue?`
  )

  if (!confirmed) return

  try {
    const res = await authFetch(`/api/device/remove`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ deviceKey })
    })

    const data = await res.json()

    if (data.error) {
      showError(data.error)
      return
    }

    showError('Device removed successfully')
    await loadDevicesList()
  } catch (err) {
    showError(err.message)
  }
}

function closeDevices() {
  document.getElementById('devices-modal').close()
}

function copyToClipboard(text, message = 'Copied!') {
  // Create temporary textarea
  const textarea = document.createElement('textarea')
  textarea.value = text
  textarea.style.position = 'fixed'
  textarea.style.opacity = '0'
  document.body.appendChild(textarea)
  textarea.select()

  try {
    document.execCommand('copy')
    showError(message)
  } catch (err) {
    showError('Failed to copy to clipboard')
  }

  document.body.removeChild(textarea)
}

async function deduplicateDevices() {
  const confirmed = confirm(
    'Remove duplicate device entries?\n\n' +
    'This will keep only the most recent entry for each unique device.\n\n' +
    'Continue?'
  )

  if (!confirmed) return

  try {
    const res = await authFetch(`/api/device/deduplicate`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' }
    })

    const data = await res.json()

    if (data.error) {
      showError(data.error)
      return
    }

    showError(`Removed ${data.removed} duplicate(s)`)
    await loadDevicesList()
  } catch (err) {
    showError(err.message)
  }
}

// ============================================================================
// CONTACT MANAGEMENT
// ============================================================================

let currentContacts = []
let currentEditContactId = null

async function showContacts() {
  const modal = document.getElementById('contacts-modal')
  modal.showModal()

  await loadContactsList()
}

function closeContacts() {
  document.getElementById('contacts-modal').close()
}

async function loadContactsList() {
  const contactsList = document.getElementById('contacts-list')
  contactsList.innerHTML = '<div class="spinner"></div>'

  try {
    const res = await authFetch(`/api/contacts`)
    const data = await res.json()

    if (data.error) {
      contactsList.innerHTML = `<div class="error-message">${data.error}</div>`
      return
    }

    currentContacts = data.contacts || []

    if (currentContacts.length === 0) {
      contactsList.innerHTML = `
        <div class="empty-state">
          <p>📇 No contacts yet</p>
          <p class="empty-hint">Add contacts to send transactions by name instead of typing addresses</p>
          <button class="btn-primary" onclick="showAddContact()">Add Your First Contact</button>
        </div>
      `
      return
    }

    // Sort contacts alphabetically
    currentContacts.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase()))

    let html = '<div class="contacts-grid">'

    for (const contact of currentContacts) {
      const favIcon = contact.metadata?.favorite ? '⭐' : ''

      // Support both old format (single address) and new format (multiple addresses)
      let addresses = []
      if (contact.addresses && Array.isArray(contact.addresses)) {
        // NEW FORMAT: Multiple addresses
        addresses = contact.addresses
      } else if (contact.chain && contact.address) {
        // OLD FORMAT: Single address - convert to array
        addresses = [{
          chain: contact.chain,
          address: contact.address,
          network: contact.network || selectedNetwork
        }]
      }

      html += `
        <div class="contact-card">
          <div class="contact-header">
            <span class="contact-name">${favIcon} ${escapeHtml(contact.name)}</span>
            <div class="contact-actions-inline">
              <button class="btn-icon-small" onclick="editContact('${contact.id}')" title="Edit">✏️</button>
              <button class="btn-icon-small" onclick="deleteContactConfirm('${contact.id}', '${escapeHtml(contact.name)}')" title="Delete">🗑️</button>
            </div>
          </div>
      `

      // Show all addresses
      if (addresses.length > 0) {
        html += '<div class="contact-addresses">'
        for (const addr of addresses) {
          const shortAddr = addr.address.substring(0, 16) + '...'

          // Get chain info from SUPPORTED_CHAINS
          const chainInfo = SUPPORTED_CHAINS[addr.chain] || { icon: '🔗', name: addr.chain }
          const chainIcon = chainInfo.icon
          const chainName = chainInfo.name

          // Build display label: "Chain Name" or "Chain Name - Label"
          const displayLabel = addr.label ? `${chainName} - ${addr.label}` : chainName

          html += `
            <div class="contact-address-row">
              <div class="contact-address-info">
                <span class="chain-icon" title="${displayLabel}">${chainIcon}</span>
                <code class="contact-address copyable-address" data-address="${escapeHtml(addr.address)}" title="Click to copy full address">${shortAddr}</code>
                <span class="chain-badge chain-badge-${addr.chain}">${displayLabel}</span>
              </div>
              <div class="contact-address-actions">
                <button class="btn-icon-small" onclick="sendToContactAddress('${contact.id}', '${addr.id}')" title="Send to ${displayLabel}">💸</button>
                <button class="btn-icon-small btn-danger" onclick="removeContactAddress('${contact.id}', '${addr.id}', '${escapeHtml(contact.name)}', '${escapeHtml(displayLabel)}')" title="Remove ${displayLabel} address">🗑️</button>
              </div>
            </div>
          `
        }
        html += '</div>'
      } else {
        html += '<p class="contact-error">⚠️ No addresses configured</p>'
      }

      // Add Wallet button
      html += `
        <div class="contact-footer">
          <button class="btn-link" onclick="showAddWallet('${contact.id}', '${escapeHtml(contact.name)}')">+ Add Wallet</button>
        </div>
      `

      if (contact.notes) {
        html += `<p class="contact-notes">${escapeHtml(contact.notes)}</p>`
      }

      html += '</div>'
    }

    html += '</div>'

    contactsList.innerHTML = html
  } catch (err) {
    console.error('Load contacts error:', err)
    contactsList.innerHTML = `<div class="error-message">Error loading contacts: ${err.message}</div>`
  }
}

function showAddContact() {
  const modal = document.getElementById('add-contact-modal')
  modal.showModal()

  // Reset form
  document.getElementById('add-contact-form').reset()
  document.getElementById('add-contact-error').style.display = 'none'
}

function closeAddContact() {
  document.getElementById('add-contact-modal').close()
}

async function submitAddContact(event) {
  event.preventDefault()

  const name = document.getElementById('contact-name').value
  const chain = document.getElementById('contact-chain').value
  const address = document.getElementById('contact-address').value.trim()
  const notes = document.getElementById('contact-notes').value.trim()

  const errorDiv = document.getElementById('add-contact-error')
  errorDiv.style.display = 'none'

  try {
    const res = await authFetch(`/api/contacts`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name, chain, address, notes })
    })

    const data = await res.json()

    if (data.error) {
      errorDiv.textContent = data.error
      errorDiv.style.display = 'block'
      return
    }

    showError(`Contact "${name}" added successfully!`)
    closeAddContact()
    await loadContactsList()
  } catch (err) {
    errorDiv.textContent = `Error: ${err.message}`
    errorDiv.style.display = 'block'
  }
}

async function editContact(contactId) {
  const contact = currentContacts.find(c => c.id === contactId)
  if (!contact) {
    showError('Contact not found')
    return
  }

  currentEditContactId = contactId

  // Populate form
  document.getElementById('edit-contact-id').value = contact.id
  document.getElementById('edit-contact-name').value = contact.name
  document.getElementById('edit-contact-chain').value = contact.chain
  document.getElementById('edit-contact-address').value = contact.address
  document.getElementById('edit-contact-notes').value = contact.notes || ''

  const modal = document.getElementById('edit-contact-modal')
  modal.showModal()

  document.getElementById('edit-contact-error').style.display = 'none'
}

function closeEditContact() {
  document.getElementById('edit-contact-modal').close()
  currentEditContactId = null
}

async function submitEditContact(event) {
  event.preventDefault()

  const id = currentEditContactId
  const name = document.getElementById('edit-contact-name').value
  const notes = document.getElementById('edit-contact-notes').value.trim()

  const errorDiv = document.getElementById('edit-contact-error')
  errorDiv.style.display = 'none'

  try {
    const res = await authFetch(`/api/contacts/${id}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ name, notes })
    })

    const data = await res.json()

    if (data.error) {
      errorDiv.textContent = data.error
      errorDiv.style.display = 'block'
      return
    }

    showError(`Contact "${name}" updated successfully!`)
    closeEditContact()
    await loadContactsList()
  } catch (err) {
    errorDiv.textContent = `Error: ${err.message}`
    errorDiv.style.display = 'block'
  }
}

async function deleteContactConfirm(contactId, contactName) {
  const confirmed = confirm(
    `Delete contact "${contactName}"?\n\n` +
    `This will remove the contact from your address book.\n\n` +
    `Continue?`
  )

  if (!confirmed) return

  try {
    const res = await authFetch(`/api/contacts/${contactId}`, {
      method: 'DELETE'
    })

    const data = await res.json()

    if (data.error) {
      showError(data.error)
      return
    }

    showError(`Contact "${contactName}" deleted`)
    await loadContactsList()
  } catch (err) {
    showError(`Error: ${err.message}`)
  }
}

// Show add wallet modal for contact
function showAddWallet(contactId, contactName) {
  currentEditContactId = contactId

  document.getElementById('add-wallet-contact-name').textContent = contactName
  document.getElementById('add-wallet-chain').value = 'bitcoin'
  document.getElementById('add-wallet-address').value = ''
  document.getElementById('add-wallet-error').style.display = 'none'

  const modal = document.getElementById('add-wallet-modal')
  modal.showModal()
}

// Close add wallet modal
function closeAddWallet() {
  document.getElementById('add-wallet-modal').close()
  currentEditContactId = null
}

// Submit add wallet
async function submitAddWallet(event) {
  event.preventDefault()

  const contactId = currentEditContactId
  const chain = document.getElementById('add-wallet-chain').value
  const address = document.getElementById('add-wallet-address').value.trim()
  const label = document.getElementById('add-wallet-label').value.trim()

  const errorDiv = document.getElementById('add-wallet-error')
  errorDiv.style.display = 'none'

  try {
    const res = await authFetch(`/api/contacts/${contactId}/addresses`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ chain, address, network: selectedNetwork, label })
    })

    const data = await res.json()

    if (data.error) {
      errorDiv.textContent = data.error
      errorDiv.style.display = 'block'
      return
    }

    const chainName = SUPPORTED_CHAINS[chain]?.name || chain.toUpperCase()
    showSuccess(`${chainName} wallet added!`)
    closeAddWallet()
    await loadContactsList()
  } catch (err) {
    errorDiv.textContent = `Error: ${err.message}`
    errorDiv.style.display = 'block'
  }
}

// Remove address from contact
async function removeContactAddress(contactId, addressId, contactName, chainLabel) {
  const confirmed = confirm(
    `Remove ${chainLabel} wallet from "${contactName}"?\n\n` +
    `This will remove this wallet address from the contact.\n\n` +
    `Continue?`
  )

  if (!confirmed) return

  try {
    const res = await authFetch(`/api/contacts/${contactId}/addresses/${addressId}`, {
      method: 'DELETE'
    })

    const data = await res.json()

    if (data.error) {
      showError(data.error)
      return
    }

    showSuccess(`${chainLabel} wallet removed`)
    await loadContactsList()
  } catch (err) {
    showError(`Error: ${err.message}`)
  }
}

// Send to specific address of a contact
async function sendToContactAddress(contactId, addressId) {
  const contact = currentContacts.find(c => c.id === contactId)
  if (!contact) {
    showError('Contact not found')
    return
  }

  // Find the specific address by ID
  let addrInfo = null
  let address = null
  let chain = null

  if (contact.addresses && Array.isArray(contact.addresses)) {
    // NEW FORMAT: Multiple addresses with IDs
    addrInfo = contact.addresses.find(a => a.id === addressId)
    if (addrInfo) {
      address = addrInfo.address
      chain = addrInfo.chain
    }
  }

  // Fallback for old format (single address, no ID)
  if (!address && contact.address && contact.chain) {
    address = contact.address
    chain = contact.chain
  }

  if (!address || !chain) {
    showError(`Address not found for ${contact.name}`)
    return
  }

  // Close contacts modal
  closeContacts()

  // Set chain and asset to match contact address
  // Handle token chains like 'ethereum-usdt' -> chain='ethereum', asset='usdt'
  if (chain.includes('-')) {
    const parts = chain.split('-')
    selectedChain = parts[0]
    selectedAsset = parts[1]
  } else {
    selectedChain = chain
    selectedAsset = null
  }
  localStorage.setItem('chain', selectedChain)
  localStorage.setItem('asset', selectedAsset || '')
  selectedCurrency = getSelectedCurrency()
  document.getElementById('currency-selector').value = selectedChain

  // Update wallet view
  updateWalletView()

  // Show send form
  await showSend(selectedCurrency)

  // Pre-fill address
  setTimeout(() => {
    const addressInput = document.getElementById('send-to')
    if (addressInput) {
      addressInput.value = address
    }
  }, 100)
}

// Legacy function - send to first available address
async function sendToContact(contactId) {
  const contact = currentContacts.find(c => c.id === contactId)
  if (!contact) {
    showError('Contact not found')
    return
  }

  // Get first available address ID
  let addressId = null
  if (contact.addresses && Array.isArray(contact.addresses) && contact.addresses.length > 0) {
    addressId = contact.addresses[0].id
  } else {
    // Old format: no ID, pass null and let sendToContactAddress handle it
    addressId = null
  }

  if (!addressId && !contact.address) {
    showError('Contact has no addresses')
    return
  }

  // Use the address-specific function
  await sendToContactAddress(contactId, addressId)
}

function escapeHtml(text) {
  const div = document.createElement('div')
  div.textContent = text
  return div.innerHTML
}

function joinDevice() {
  const modal = document.getElementById('device-join-modal')
  document.getElementById('join-invite-code').value = ''
  document.getElementById('join-device-name').value = os.hostname() || 'New Device'
  document.getElementById('join-error').textContent = ''
  modal.showModal()
}

function closeDeviceJoin() {
  document.getElementById('device-join-modal').close()
}

async function executeDeviceJoin() {
  const inviteCode = document.getElementById('join-invite-code').value.trim()
  const deviceName = document.getElementById('join-device-name').value.trim()
  const errorDiv = document.getElementById('join-error')

  if (!inviteCode) {
    errorDiv.textContent = 'Please enter an invite code'
    return
  }

  if (!deviceName) {
    errorDiv.textContent = 'Please enter a device name'
    return
  }

  try {
    errorDiv.textContent = '⏳ Connecting to primary device...'

    const res = await authFetch(`/api/device/join`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ inviteCode, deviceName })
    })

    const data = await res.json()

    if (data.error) {
      errorDiv.textContent = `❌ Error: ${data.error}`
      return
    }

    // Success! Close modal and reload page
    alert(
      '✅ Successfully joined wallet!\n\n' +
      `Device: ${data.device.name}\n` +
      `Writer Key: ${data.device.writerKey}\n\n` +
      'Both mnemonics have been saved securely.\n' +
      'The page will now reload.'
    )

    closeDeviceJoin()
    window.location.reload()
  } catch (err) {
    errorDiv.textContent = `❌ Error: ${err.message}`
  }
}

async function forceSyncNow() {
  // Trigger the sync button's functionality
  await syncTransactions()
}

function verifyBackup() {
  showError('Feature coming soon: Backup verification quiz')
}

async function resetWallet() {
  const confirmed = confirm(
    '⚠️ DANGER: This will DELETE ALL local wallet data!\n\n' +
    'Make sure you have backed up both:\n' +
    '- Identity mnemonic (24 words)\n' +
    '- Wallet mnemonic (12 words)\n\n' +
    'This action CANNOT be undone!\n\n' +
    'Type "DELETE" to confirm:'
  )

  if (!confirmed) return

  const verification = prompt('Type DELETE to confirm:')
  if (verification !== 'DELETE') {
    showError('Reset cancelled')
    return
  }

  try {
    const res = await authFetch(`/api/reset`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' }
    })

    const data = await res.json()

    if (data.error) {
      showError(data.error)
      return
    }

    showSuccess('Wallet reset complete. Reloading...')
    setTimeout(() => {
      window.location.reload()
    }, 2000)
  } catch (err) {
    showError('Failed to reset wallet')
  }
}

// Supported chains configuration (extensible for future chains)
// Only chains with registered WDK wallets are included
const SUPPORTED_CHAINS = {
  bitcoin: {
    name: 'Bitcoin',
    icon: '₿',
    testnetName: 'Testnet',
    mainnetName: 'Mainnet',
    formatBalance: (balance) => {
      const btc = Number(balance) / 100000000
      return `${btc.toFixed(8)} BTC`
    },
    formatSmallUnit: (balance) => {
      return `${Number(balance).toLocaleString()} sats`
    },
    priceId: 'bitcoin', // CoinGecko ID
    addressPattern: {
      testnet: /^(tb1|[mn])[a-zA-HJ-NP-Z0-9]{25,62}$/,
      mainnet: /^(bc1|[13])[a-zA-HJ-NP-Z0-9]{25,62}$/
    },
    feeReserve: 500,
    tokens: null
  },

  ethereum: {
    name: 'Ethereum',
    icon: 'Ξ',
    testnetName: 'Sepolia',
    mainnetName: 'Mainnet',
    formatBalance: (balance) => {
      const eth = Number(balance) / 1e18
      return `${eth.toFixed(6)} ETH`
    },
    formatSmallUnit: (balance) => {
      return `${Number(balance).toLocaleString()} wei`
    },
    priceId: 'ethereum', // CoinGecko ID
    addressPattern: {
      testnet: /^0x[a-fA-F0-9]{40}$/,
      mainnet: /^0x[a-fA-F0-9]{40}$/
    },
    feeReserve: 100000,
    tokens: {
      usdt: {
        symbol: 'USDT',
        name: 'Tether USD',
        icon: '₮',
        decimals: 6,
        priceId: 'tether',
        formatBalance: (balance) => `${(Number(balance) / 1e6).toFixed(2)} USDT`
      },
      usdc: {
        symbol: 'USDC',
        name: 'USD Coin',
        icon: '$',
        decimals: 6,
        priceId: 'usd-coin',
        formatBalance: (balance) => `${(Number(balance) / 1e6).toFixed(2)} USDC`
      }
    }
  },

  // Backwards compatibility: Keep flat entries for existing code paths
  'ethereum-usdt': {
    name: 'Ethereum - USDT',
    icon: '₮',
    testnetName: 'Sepolia',
    mainnetName: 'Mainnet',
    decimals: 6,  // USDT has 6 decimals
    formatBalance: (balance) => {
      const usdt = Number(balance) / 1e6  // USDT has 6 decimals
      return `${usdt.toFixed(2)} USDT`
    },
    formatSmallUnit: (balance) => {
      return `${Number(balance).toLocaleString()} units`
    },
    priceId: 'tether', // CoinGecko ID for USDT
    addressPattern: {
      testnet: /^0x[a-fA-F0-9]{40}$/,  // Same as Ethereum (uses ETH address)
      mainnet: /^0x[a-fA-F0-9]{40}$/
    },
    feeReserve: 0,  // No fee reserve needed (fees paid in ETH)
    isToken: true,
    baseChain: 'ethereum'
  },

  'ethereum-usdc': {
    name: 'Ethereum - USDC',
    icon: '$',
    testnetName: 'Sepolia',
    mainnetName: 'Mainnet',
    formatBalance: (balance) => {
      const usdc = Number(balance) / 1e6  // USDC has 6 decimals
      return `${usdc.toFixed(2)} USDC`
    },
    formatSmallUnit: (balance) => {
      return `${Number(balance).toLocaleString()} units`
    },
    priceId: 'usd-coin', // CoinGecko ID for USDC
    addressPattern: {
      testnet: /^0x[a-fA-F0-9]{40}$/,  // Same as Ethereum (uses ETH address)
      mainnet: /^0x[a-fA-F0-9]{40}$/
    },
    feeReserve: 0,  // No fee reserve needed (fees paid in ETH)
    isToken: true,
    baseChain: 'ethereum'
  }
}

// Chain and asset state management
let selectedChain = localStorage.getItem('chain') || 'bitcoin'
let selectedAsset = localStorage.getItem('asset') || null  // null = native, 'usdt' = token

// Backwards compatibility: selectedCurrency derived from selectedChain + selectedAsset
function getSelectedCurrency() {
  if (selectedAsset && selectedChain === 'ethereum') {
    return `ethereum-${selectedAsset}`
  }
  return selectedChain
}

// Legacy support: set selectedCurrency getter
let selectedCurrency = getSelectedCurrency()

// Price cache (keyed by CoinGecko ID)
let prices = {}
let pricesLoaded = false

// Transaction cache for details modal
let allTransactions = []

// Send transaction state
let currentSendChain = ''
let currentBalance = 0
let estimatedFee = null
let usedMaxAmount = false  // Track if user clicked Max button

// Set network and refresh data
function setNetwork(network) {
  selectedNetwork = network
  localStorage.setItem('network', network)

  // Update UI buttons
  document.querySelectorAll('.network-btn').forEach(btn => {
    btn.classList.remove('active')
    if (btn.dataset.network === network) {
      btn.classList.add('active')
    }
  })

  // Update network labels
  updateNetworkLabels()

  // Refresh all data
  refreshAll()
}

// Update network labels
function updateNetworkLabels() {
  document.getElementById('network-name').textContent = selectedNetwork.toUpperCase()
  updateWalletView()
}

// Switch currency (chain selector)
function switchCurrency() {
  const selector = document.getElementById('currency-selector')
  selectedChain = selector.value
  selectedAsset = null  // Reset asset when switching chains
  localStorage.setItem('chain', selectedChain)
  localStorage.setItem('asset', '')

  // Update selectedCurrency for backwards compatibility
  selectedCurrency = getSelectedCurrency()

  updateWalletView()

  // Use unified balance display for Ethereum, standard for others
  if (selectedChain === 'ethereum') {
    loadEthereumBalances()
  } else {
    loadCurrentBalance()
  }

  loadCurrentQRCode()
  loadTransactions()
}

// Update wallet view for selected chain
function updateWalletView() {
  const chain = SUPPORTED_CHAINS[selectedChain]
  if (chain) {
    const networkName = selectedNetwork === 'testnet' ? chain.testnetName : chain.mainnetName
    document.getElementById('wallet-icon').textContent = chain.icon
    document.getElementById('wallet-name').textContent = chain.name
    document.getElementById('wallet-network').textContent = networkName
  }

  // Load wallet addresses for selector
  loadWalletAddressSelector()
}

// Load wallet addresses into the selector dropdown
// Counter to prevent onchange cascade when programmatically updating selector
// Using a counter instead of boolean to handle concurrent/nested calls
let addressSelectorUpdateCount = 0

async function loadWalletAddressSelector() {
  const select = document.getElementById('wallet-address-select')

  // Increment counter to prevent onchange from firing during programmatic update
  addressSelectorUpdateCount++
  const myUpdateId = addressSelectorUpdateCount

  select.innerHTML = '<option value="">Loading...</option>'

  try {
    // For tokens (ethereum-usdt, ethereum-usdc), use base chain for addresses
    const addressChain = selectedChain // Always use base chain, not token-specific
    const res = await authFetch(`/api/addresses/${selectedNetwork}-${addressChain}`)
    const data = await res.json()

    // Check if we're still the latest update (another call may have started)
    if (myUpdateId !== addressSelectorUpdateCount) return

    if (data.error) {
      select.innerHTML = '<option value="">Error loading addresses</option>'
      return
    }

    if (data.addresses.length === 0) {
      select.innerHTML = '<option value="">No addresses yet</option>'
      return
    }

    // Build options
    let html = ''
    for (const addr of data.addresses) {
      const shortAddr = addr.address.substring(0, 12) + '...'
      const label = addr.label || `Address ${addr.index}`
      const displayText = `${label} - ${shortAddr}`
      const currentMarker = addr.isCurrent ? ' ⭐' : ''

      html += `<option value="${addr.index}" ${addr.isCurrent ? 'selected' : ''}>${displayText}${currentMarker}</option>`
    }

    // Store current value before update to detect programmatic vs user changes
    const previousValue = select.value

    select.innerHTML = html

    // If the selected value changed due to our update (not user), mark as programmatic
    if (select.value !== previousValue) {
      select.dataset.programmaticChange = 'true'
    }
  } catch (err) {
    console.error('Load address selector error:', err)
    select.innerHTML = '<option value="">Failed to load</option>'
  }
}

// Handle wallet address selector change
async function onWalletAddressChange() {
  const select = document.getElementById('wallet-address-select')

  // Ignore programmatic updates to prevent cascade
  if (select.dataset.programmaticChange === 'true') {
    delete select.dataset.programmaticChange
    return
  }
  const selectedIndex = parseInt(select.value)

  if (isNaN(selectedIndex)) return

  try {
    // For tokens, use base chain for address management
    const addressChain = selectedChain
    const res = await authFetch(`/api/addresses/${selectedNetwork}-${addressChain}/${selectedIndex}/current`, {
      method: 'POST'
    })

    const data = await res.json()

    if (data.error) {
      showError(data.error)
      return
    }

    showSuccess('Wallet address changed!')

    // Update star markers in the existing options (no reload needed)
    Array.from(select.options).forEach(option => {
      const text = option.textContent.replace(' ⭐', '')
      option.textContent = option.value === String(selectedIndex) ? text + ' ⭐' : text
    })

    // Reload balance - use unified view for Ethereum, standard for others
    if (selectedChain === 'ethereum') {
      await loadEthereumBalances()
    } else {
      await loadCurrentBalance()
    }
    await loadCurrentQRCode()
    await loadTransactions()

    // NOTE: Don't reload selector here - causes cascade. Star markers updated above.
  } catch (err) {
    showError(`Failed to change address: ${err.message}`)
  }
}

// Load balance for current currency
async function loadCurrentBalance() {
  const element = document.getElementById('wallet-balance-display')
  element.innerHTML = '<div class="spinner"></div>'

  try {
    // Get currently selected address index
    const select = document.getElementById('wallet-address-select')
    const rawIndex = select ? parseInt(select.value) : 0
    const accountIndex = isNaN(rawIndex) ? 0 : rawIndex

    const res = await authFetch(`/api/balance/${selectedNetwork}-${selectedCurrency}?accountIndex=${accountIndex}`)
    const data = await res.json()

    if (data.error) {
      element.innerHTML = '<span class="error">Error loading balance</span>'
      return
    }

    const chain = SUPPORTED_CHAINS[selectedCurrency]
    if (!chain) {
      element.innerHTML = '<span class="error">Unsupported chain</span>'
      return
    }

    const mainAmount = chain.formatBalance(data.balance)
    const subAmount = chain.formatSmallUnit(data.balance)
    let usdHTML = ''

    // Calculate USD value (only for mainnet and if price is available)
    if (selectedNetwork === 'mainnet' && pricesLoaded) {
      const price = prices[chain.priceId]
      if (price && price > 0) {
        // Convert to base unit (varies by chain - respect token decimals)
        let divisor
        if (selectedCurrency === 'bitcoin') {
          divisor = 100000000  // 8 decimals
        } else if (chain.decimals) {
          divisor = Math.pow(10, chain.decimals)  // Use chain-specific decimals (e.g., 6 for USDT)
        } else {
          divisor = 1e18  // Default to 18 for ETH
        }
        const baseAmount = Number(data.balance) / divisor
        const usdValue = baseAmount * price
        usdHTML = `<div class="balance-usd">${formatUSD(usdValue)}</div>`
      }
    } else if (selectedNetwork === 'testnet') {
      usdHTML = `<div class="balance-usd balance-usd-testnet">Testnet (no value)</div>`
    }

    element.innerHTML = `
      <div class="balance-main">${mainAmount}</div>
      <div class="balance-sub">${subAmount}</div>
      ${usdHTML}
    `
  } catch (err) {
    console.error('Balance error:', err)
    element.innerHTML = '<span class="error">Failed to load balance</span>'
  }
}

// Load unified Ethereum balances (ETH + USDT + USDC)
async function loadEthereumBalances() {
  console.log('loadEthereumBalances called, selectedChain:', selectedChain, 'selectedNetwork:', selectedNetwork)
  const container = document.getElementById('wallet-balance-display')

  // Show loading with asset placeholders
  container.innerHTML = `
    <div class="unified-balance">
      <div class="total-value">
        <span class="total-usd">Loading...</span>
        <span class="total-label">Total Balance</span>
      </div>
      <div class="asset-list">
        <div class="asset-row"><span class="asset-icon">Ξ</span><span class="asset-name">ETH</span><span class="asset-balance">...</span></div>
        <div class="asset-row"><span class="asset-icon">₮</span><span class="asset-name">USDT</span><span class="asset-balance">...</span></div>
        <div class="asset-row"><span class="asset-icon">$</span><span class="asset-name">USDC</span><span class="asset-balance">...</span></div>
      </div>
    </div>
  `

  try {
    const res = await authFetch(`/api/balances/ethereum`)
    const data = await res.json()

    if (data.error) {
      container.innerHTML = `<span class="error">${data.error}</span>`
      return
    }

    const { balances } = data

    // Handle missing balances gracefully
    const ethBalance = balances?.eth?.balance || '0'
    const usdtBalance = balances?.usdt?.balance || '0'
    const usdcBalance = balances?.usdc?.balance || '0'

    const ethAmount = Number(ethBalance) / 1e18
    const usdtAmount = Number(usdtBalance) / 1e6
    const usdcAmount = Number(usdcBalance) / 1e6

    // Use testnet-appropriate USD values (show 0 for testnet)
    const isTestnet = selectedNetwork === 'testnet'
    const ethUsd = isTestnet ? 0 : ethAmount * (prices.ethereum || 0)
    const usdtUsd = isTestnet ? 0 : usdtAmount * (prices.tether || 1)
    const usdcUsd = isTestnet ? 0 : usdcAmount * (prices['usd-coin'] || 1)
    const totalUsd = ethUsd + usdtUsd + usdcUsd

    const totalDisplay = isTestnet ? 'Testnet' : formatUSD(totalUsd)

    container.innerHTML = `
      <div class="unified-balance">
        <div class="total-value">
          <span class="total-usd">${totalDisplay}</span>
          <span class="total-label">${isTestnet ? 'Sepolia Testnet' : 'Total Balance'}</span>
        </div>

        <div class="asset-list">
          <div class="asset-row ${selectedAsset === null ? 'selected' : ''}"
               onclick="selectAsset(null)">
            <span class="asset-icon">Ξ</span>
            <span class="asset-name">ETH</span>
            <span class="asset-balance">${ethAmount.toFixed(6)}</span>
            <span class="asset-actions">
              <button class="btn-small" onclick="event.stopPropagation(); showSend('ethereum')" ${ethAmount <= 0 ? 'disabled' : ''}>Send</button>
            </span>
          </div>

          <div class="asset-row ${selectedAsset === 'usdt' ? 'selected' : ''}"
               onclick="selectAsset('usdt')">
            <span class="asset-icon">₮</span>
            <span class="asset-name">USDT</span>
            <span class="asset-balance">${usdtAmount.toFixed(2)}</span>
            <span class="asset-actions">
              <button class="btn-small" onclick="event.stopPropagation(); showSend('ethereum-usdt')" ${usdtAmount <= 0 ? 'disabled' : ''}>Send</button>
            </span>
          </div>

          <div class="asset-row ${selectedAsset === 'usdc' ? 'selected' : ''}"
               onclick="selectAsset('usdc')">
            <span class="asset-icon">$</span>
            <span class="asset-name">USDC</span>
            <span class="asset-balance">${usdcAmount.toFixed(2)}</span>
            <span class="asset-actions">
              <button class="btn-small" onclick="event.stopPropagation(); showSend('ethereum-usdc')" ${usdcAmount <= 0 ? 'disabled' : ''}>Send</button>
            </span>
          </div>
        </div>

        ${ethAmount < 0.001 ? `
          <div class="gas-warning">
            ⚠️ Low ETH - needed for gas fees to send tokens
          </div>
        ` : ''}
      </div>
    `
  } catch (err) {
    console.error('Load balances error:', err)
    container.innerHTML = `
      <div class="unified-balance">
        <div class="error">Failed to load balances</div>
        <button onclick="loadEthereumBalances()" class="btn-secondary">Retry</button>
      </div>
    `
  }
}

// Select asset for Ethereum chain (null = ETH, 'usdt' = USDT, 'usdc' = USDC)
// NOTE: All Ethereum tokens share the same address - only visual selection changes
function selectAsset(asset, evt) {
  selectedAsset = asset
  localStorage.setItem('asset', asset || '')

  // Update selectedCurrency for backwards compatibility
  selectedCurrency = getSelectedCurrency()

  // Update visual selection
  document.querySelectorAll('.asset-row').forEach((row, index) => {
    row.classList.remove('selected')
    // ETH is index 0, USDT is index 1, USDC is index 2
    const rowAsset = index === 0 ? null : index === 1 ? 'usdt' : 'usdc'
    if (rowAsset === asset) {
      row.classList.add('selected')
    }
  })

  // For tokens (USDT/USDC), don't reload QR/address - they share the same ETH address
  // Only reload transactions which may be filtered by token type
  loadTransactions()
}

// Load QR code for current currency
async function loadCurrentQRCode() {
  const qrContainer = document.getElementById('wallet-qr-container')
  const addressDisplay = document.getElementById('wallet-address-display')

  qrContainer.innerHTML = '<div class="spinner"></div>'
  addressDisplay.textContent = 'Loading address...'

  try {
    const res = await authFetch(`/api/qrcode/${selectedNetwork}-${selectedCurrency}`)
    const data = await res.json()

    if (data.error) {
      qrContainer.innerHTML = '<span class="error">Error</span>'
      addressDisplay.textContent = 'Failed to load address'
      return
    }

    // Display QR code image
    qrContainer.innerHTML = `<img src="${data.qrcode}" alt="Receive Address QR Code">`

    // Display address (clickable to copy)
    addressDisplay.textContent = data.address
    addressDisplay.title = 'Click to copy address'
    addressDisplay.onclick = () => copyAddressToClipboard(data.address)

  } catch (err) {
    console.error('QR code error:', err)
    qrContainer.innerHTML = '<span class="error">Failed to load</span>'
    addressDisplay.textContent = 'Failed to load address'
  }
}

// Copy address to clipboard
async function copyAddressToClipboard(address) {
  try {
    await navigator.clipboard.writeText(address)
    showSuccess('Address copied to clipboard!')
  } catch (err) {
    // Fallback for older browsers
    const temp = document.createElement('textarea')
    temp.value = address
    document.body.appendChild(temp)
    temp.select()
    document.execCommand('copy')
    document.body.removeChild(temp)
    showSuccess('Address copied!')
  }
}

// Show receive for current currency
function showReceiveForCurrent() {
  showReceive(selectedCurrency)
}

// Show send for current currency
function showSendForCurrent() {
  showSend(selectedCurrency)
}

// Fetch current prices (CoinGecko API)
async function fetchPrices() {
  try {
    // Get all price IDs from supported chains
    const priceIds = Object.values(SUPPORTED_CHAINS)
      .map(chain => chain.priceId)
      .filter(id => id) // Filter out chains without priceId
      .join(',')

    if (!priceIds) {
      console.log('No price IDs configured')
      return
    }

    const res = await fetch(`https://api.coingecko.com/api/v3/simple/price?ids=${priceIds}&vs_currencies=usd`)
    const data = await res.json()

    // Store prices by CoinGecko ID
    prices = {}
    for (const [coinId, priceData] of Object.entries(data)) {
      prices[coinId] = priceData.usd || 0
    }

    pricesLoaded = true

    const priceLog = Object.entries(prices)
      .map(([coin, price]) => `${coin}=$${price.toLocaleString()}`)
      .join(', ')
    console.log(`Prices loaded: ${priceLog}`)
  } catch (err) {
    console.error('Failed to fetch prices:', err)
    pricesLoaded = false
  }
}

// Format USD value
function formatUSD(amount) {
  if (!amount) return '$0.00'
  return `$${Number(amount).toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
}

// Format balance helpers
function formatBTC(satoshis) {
  const btc = Number(satoshis) / 100000000
  return `${btc.toFixed(8)} BTC`
}

function formatETH(wei) {
  const eth = Number(wei) / 1e18
  return `${eth.toFixed(18).replace(/\.?0+$/, '')} ETH`
}

function formatSats(satoshis) {
  return `${Number(satoshis).toLocaleString()} sats`
}

function formatGwei(wei) {
  const gwei = Number(wei) / 1e9
  return `${gwei.toFixed(2)} Gwei`
}

// Format fee in ETH (for Ethereum-based token transfers)
// Gas fees for token transfers are paid in ETH, not the token
function formatEthFee(wei) {
  const eth = Number(wei) / 1e18
  if (eth < 0.0001) {
    return `${(eth * 1e6).toFixed(4)} µETH`
  }
  return `${eth.toFixed(6)} ETH`
}

// Convert human-readable amount to smallest units (satoshi/wei/token units)
// This is needed because users type "2" meaning 2 USDC, but backend expects 2000000
function toSmallestUnits(amount, chain) {
  const num = parseFloat(amount)
  if (isNaN(num) || num < 0) {
    return '0'
  }

  if (chain === 'bitcoin') {
    // BTC to satoshi (1e8)
    return Math.floor(num * 1e8).toString()
  } else if (chain.includes('usdt') || chain.includes('usdc')) {
    // USDT/USDC have 6 decimals
    return Math.floor(num * 1e6).toString()
  } else {
    // ETH to wei (1e18) - use BigInt to avoid precision loss
    // For large numbers, we need to be careful with floating point
    const wholePart = Math.floor(num)
    const fractionalPart = num - wholePart
    const wholeWei = BigInt(wholePart) * BigInt(1e18)
    const fractionalWei = BigInt(Math.floor(fractionalPart * 1e18))
    return (wholeWei + fractionalWei).toString()
  }
}

// Convert smallest units to human-readable amount
// This is used when displaying amounts or setting input values
function fromSmallestUnits(amount, chain) {
  const num = Number(amount)
  if (isNaN(num) || num < 0) {
    return '0'
  }

  if (chain === 'bitcoin') {
    // Satoshi to BTC (1e8)
    const btc = num / 1e8
    return btc.toFixed(8).replace(/\.?0+$/, '')
  } else if (chain.includes('usdt') || chain.includes('usdc')) {
    // USDT/USDC have 6 decimals
    const tokens = num / 1e6
    return tokens.toFixed(6).replace(/\.?0+$/, '')
  } else {
    // Wei to ETH (1e18)
    const eth = num / 1e18
    return eth.toFixed(18).replace(/\.?0+$/, '')
  }
}

// Show error toast
function showError(message) {
  const toast = document.getElementById('error-toast')
  toast.textContent = message
  toast.classList.add('show')

  // If a modal is open, append toast to it (so it appears above backdrop)
  const openDialog = document.querySelector('dialog[open]')
  if (openDialog && !openDialog.contains(toast)) {
    openDialog.appendChild(toast)
  } else if (!openDialog && toast.parentElement !== document.body) {
    // If no dialog open, move back to body
    document.body.appendChild(toast)
  }

  setTimeout(() => {
    toast.classList.remove('show')
  }, 5000)
}

// Check wallet status
async function checkStatus() {
  try {
    const res = await authFetch(`/api/status`)
    const data = await res.json()

    if (!data.initialized) {
      // Show setup wizard instead of error
      showSetupWizard()
      return { initialized: false }
    }

    document.getElementById('wallet-status').innerHTML = '✅ Ready'
    // Network name is set by updateNetworkLabels() based on selectedNetwork
    return data
  } catch (err) {
    console.error('Status check failed:', err)
    document.getElementById('wallet-status').innerHTML = '❌ Server Error'
    showError('Cannot connect to wallet server')
    return { initialized: false }
  }
}

// Sync transactions from blockchain
async function syncTransactions() {
  const syncBtn = document.getElementById('sync-btn')
  const originalText = syncBtn.textContent

  try {
    syncBtn.disabled = true
    syncBtn.textContent = '🔄 Syncing...'

    // Get currently selected address index
    const select = document.getElementById('wallet-address-select')
    const rawIndex = select ? parseInt(select.value) : 0
    const accountIndex = isNaN(rawIndex) ? 0 : rawIndex

    // Sync current currency chain with selected account index
    const chain = selectedCurrency
    const res = await authFetch(`/api/transactions/sync`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        chain,
        network: selectedNetwork,  // Add network parameter
        accountIndex
      })
    })

    const data = await res.json()

    if (data.error) {
      showError(`Sync failed: ${data.error}`)
      return
    }

    if (data.result.synced > 0) {
      showSuccess(`✅ Synced ${data.result.synced} new transactions`)
    } else {
      showSuccess('✅ Transactions up to date')
    }

    // Always refresh balance and transactions after sync
    // Balance comes fresh from blockchain via RPC (Alchemy)
    if (selectedChain === 'ethereum') {
      await loadEthereumBalances()
    } else {
      await loadCurrentBalance()
    }
    await loadTransactions()

    if (data.result.errors && data.result.errors.length > 0) {
      console.warn('Sync warnings:', data.result.errors)
    }

  } catch (err) {
    console.error('Sync error:', err)
    showError(`Sync failed: ${err.message}`)
  } finally {
    syncBtn.disabled = false
    syncBtn.textContent = originalText
  }
}

// Load transactions
async function loadTransactions() {
  const list = document.getElementById('tx-list')
  list.innerHTML = '<div class="spinner"></div>'

  try {
    // Load transactions, contacts, and current address in parallel
    const [txRes, contactsRes, currentAddrRes] = await Promise.all([
      authFetch(`/api/transactions/all`),
      authFetch(`/api/contacts`),
      authFetch(`/api/address/${selectedNetwork}-${selectedCurrency}`)
    ])

    const data = await txRes.json()
    const contactsData = await contactsRes.json()
    const currentAddrData = await currentAddrRes.json()

    if (data.error) {
      list.innerHTML = '<div class="empty-state">Error loading transactions</div>'
      return
    }

    if (data.transactions.length === 0) {
      list.innerHTML = '<div class="empty-state">No transactions yet</div>'
      return
    }

    // Get the currently selected address
    const currentAddress = currentAddrData.address ? currentAddrData.address.toLowerCase() : null

    // Build address-to-contact lookup
    const contacts = contactsData.contacts || []
    const addressToContact = {}
    for (const contact of contacts) {
      // Handle both old format (single address) and new format (addresses array)
      if (contact.addresses && Array.isArray(contact.addresses)) {
        // New format: multiple addresses
        for (const addr of contact.addresses) {
          addressToContact[addr.address.toLowerCase()] = contact
        }
      } else if (contact.address) {
        // Old format: single address
        addressToContact[contact.address.toLowerCase()] = contact
      }
    }

    // Filter by selected network, selected currency, AND current wallet address
    const filtered = data.transactions.filter(tx => {
      // Must match network and chain
      if (tx.network !== selectedNetwork || tx.chain !== selectedCurrency) {
        return false
      }

      // Must involve the currently selected address (either as sender or receiver)
      if (currentAddress) {
        const fromAddr = tx.from.toLowerCase()
        const toAddr = tx.to.toLowerCase()
        return fromAddr === currentAddress || toAddr === currentAddress
      }

      // If no current address, show all for this chain (fallback)
      return true
    })

    if (filtered.length === 0) {
      list.innerHTML = '<div class="empty-state">No transactions for this currency on this network</div>'
      return
    }

    // Sort by timestamp descending
    const sorted = filtered.sort((a, b) => b.timestamp - a.timestamp)

    // Store for details modal
    allTransactions = sorted

    // Show only last 10
    const recent = sorted.slice(0, 10)

    list.innerHTML = recent.map((tx, index) => {
      const date = new Date(tx.timestamp).toLocaleDateString()
      const time = new Date(tx.timestamp).toLocaleTimeString()
      const icon = tx.direction === 'sent' ? '📤' : '📥'
      const dirClass = tx.direction === 'sent' ? 'sent' : 'received'
      const statusIcon = tx.status === 'confirmed' ? '✅' : '⏳'

      // Format amount - handle tokens specially
      const baseChain = tx.chain.replace(/-usdt$/, '').replace(/-usdc$/, '')
      const chain = SUPPORTED_CHAINS[baseChain]
      let amount
      if (tx.token || tx.chain.includes('-usdt') || tx.chain.includes('-usdc')) {
        const tokenSymbol = tx.token || (tx.chain.includes('-usdt') ? 'USDT' : 'USDC')
        const decimals = tx.decimals || 6
        const tokenAmount = Number(tx.amount) / Math.pow(10, decimals)
        amount = `${tokenAmount.toFixed(2)} ${tokenSymbol}`
      } else {
        amount = chain ? chain.formatBalance(tx.amount) : tx.amount
      }

      // Look up contact for the to/from address
      const relevantAddress = tx.direction === 'sent' ? tx.to : tx.from
      const contact = addressToContact[relevantAddress.toLowerCase()]
      const displayName = contact
        ? `<span class="contact-name-badge">📇 ${escapeHtml(contact.name)}</span> <small>${truncateAddress(relevantAddress)}</small>`
        : truncateAddress(relevantAddress)

      return `
        <div class="tx-item" onclick="showTxDetails(${index})">
          <div class="tx-icon">${icon}</div>
          <div class="tx-details">
            <div class="tx-main">
              <span class="tx-direction ${dirClass}">${tx.direction.toUpperCase()}</span>
              <span class="tx-chain">${tx.chain}</span>
            </div>
            <div class="tx-address">${displayName}</div>
            <div class="tx-date">${date} ${time}</div>
          </div>
          <div class="tx-amount">
            <div class="tx-value">${amount}</div>
            <div class="tx-status">${statusIcon} ${tx.status}</div>
          </div>
        </div>
      `
    }).join('')

  } catch (err) {
    console.error('Transactions error:', err)
    list.innerHTML = '<div class="empty-state">Failed to load transactions</div>'
  }
}

// Helper to truncate address
function truncateAddress(addr) {
  if (!addr || addr.length < 12) return addr
  return `${addr.substring(0, 6)}...${addr.substring(addr.length - 4)}`
}

// Refresh all data
async function refreshAll() {
  const btn = event.target
  btn.disabled = true
  btn.textContent = '⟳'

  await Promise.all([
    loadCurrentBalance(),
    loadCurrentQRCode(),
    loadTransactions()
  ])

  btn.disabled = false
  btn.textContent = '🔄 Refresh'
}

// Current chain for receive modal
let receiveModalChain = null
let manageAddressesChain = null

// Show manage addresses modal
async function showManageAddresses() {
  manageAddressesChain = selectedCurrency

  const modal = document.getElementById('manage-addresses-modal')
  const chainInfo = SUPPORTED_CHAINS[selectedCurrency] || { icon: '🔗', name: selectedCurrency }
  const chainName = chainInfo.name
  const networkLabel = selectedNetwork === 'testnet'
    ? (selectedCurrency === 'ethereum' ? 'Sepolia' : 'Testnet')
    : 'Mainnet'

  document.getElementById('manage-addresses-chain').textContent = `- ${chainName} (${networkLabel})`

  // Load addresses
  await loadManageAddresses(selectedCurrency)

  modal.showModal()
}

// Close manage addresses modal
function closeManageAddresses() {
  document.getElementById('manage-addresses-modal').close()
  manageAddressesChain = null
}

// Load addresses for manage modal
async function loadManageAddresses(chain) {
  const container = document.getElementById('manage-addresses-list')
  container.innerHTML = '<p class="loading-text">Loading addresses...</p>'

  try {
    const res = await authFetch(`/api/addresses/${selectedNetwork}-${chain}`)
    const data = await res.json()

    if (data.error) {
      container.innerHTML = `<p class="error-text">${data.error}</p>`
      return
    }

    if (data.addresses.length === 0) {
      container.innerHTML = '<p class="info-text">No addresses yet. Click "Derive New Address" to create your first address!</p>'
      return
    }

    // Build address list HTML
    let html = ''
    for (const addr of data.addresses) {
      const isCurrent = addr.isCurrent
      const currentBadge = isCurrent ? '<span class="badge badge-primary">Current</span>' : ''
      const labelDisplay = addr.label || `Address ${addr.index}`

      html += `
        <div class="wallet-address-item ${isCurrent ? 'current' : ''}">
          <div class="address-info-full">
            <div class="address-label-row">
              <input type="text"
                class="address-label-input ${addr.label ? '' : 'placeholder'}"
                value="${addr.label || `Address ${addr.index}`}"
                onblur="updateWalletAddressLabel('${chain}', ${addr.index}, this.value)"
                placeholder="Add label (e.g., Hardware wallet, Hot wallet)..."
              />
              ${currentBadge}
            </div>
            <div class="address-full-row">
              <code class="address-code-full" title="Click to copy" onclick="copyWalletAddress('${addr.address}')">${addr.address}</code>
            </div>
            <div class="address-meta">
              <span class="meta-item">Index: ${addr.index}</span>
              <span class="meta-item">Path: ${addr.path}</span>
              <span class="meta-item">Created: ${new Date(addr.createdAt).toLocaleDateString()}</span>
            </div>
          </div>
          <div class="address-actions-vertical">
            ${!isCurrent ? `<button class="btn-secondary btn-sm" onclick="setWalletAddressCurrent('${chain}', ${addr.index})">Set as Current</button>` : ''}
            <button class="btn-secondary btn-sm" onclick="copyWalletAddress('${addr.address}')">Copy Address</button>
            ${!isCurrent ? `<button class="btn-danger btn-sm" onclick="archiveWalletAddress('${chain}', ${addr.index})">Archive</button>` : ''}
          </div>
        </div>
      `
    }

    container.innerHTML = html
  } catch (err) {
    console.error('Load addresses error:', err)
    container.innerHTML = `<p class="error-text">Failed to load addresses</p>`
  }
}

// Label input modal state
let labelInputCallback = null

// Show label input modal
function showLabelInput(callback) {
  labelInputCallback = callback
  document.getElementById('label-input-field').value = ''
  document.getElementById('label-input-modal').showModal()
}

// Close label input modal
function closeLabelInput() {
  document.getElementById('label-input-modal').close()
  labelInputCallback = null
}

// Confirm label input
function confirmLabelInput() {
  const label = document.getElementById('label-input-field').value.trim()
  document.getElementById('label-input-modal').close()
  if (labelInputCallback) {
    labelInputCallback(label)
  }
  labelInputCallback = null
}

// Derive new address from manage modal
async function deriveNewAddressFromManage() {
  if (!manageAddressesChain) return

  showLabelInput(async (label) => {
    try {
      const res = await authFetch(`/api/addresses/${selectedNetwork}-${manageAddressesChain}/derive`, {
        method: 'POST',
        body: JSON.stringify({ label: label || '' })
      })

      const data = await res.json()

      if (data.error) {
        showError(data.error)
        return
      }

      showSuccess('New address derived!')
      await loadManageAddresses(manageAddressesChain)
    } catch (err) {
      showError(`Failed to derive address: ${err.message}`)
    }
  })
}

// Show receive modal
async function showReceive(chain) {
  receiveModalChain = chain

  const modal = document.getElementById('receive-modal')
  const addressInput = document.getElementById('receive-address')
  const chainName = chain.charAt(0).toUpperCase() + chain.slice(1)
  const networkLabel = selectedNetwork === 'testnet'
    ? (chain === 'ethereum' ? 'Sepolia' : 'Testnet')
    : 'Mainnet'

  document.getElementById('receive-chain').textContent = `${chainName} (${networkLabel})`

  // Load current address
  addressInput.value = 'Loading...'
  document.getElementById('receive-address-label').textContent = ''

  try {
    const res = await authFetch(`/api/address/${selectedNetwork}-${chain}`)
    const data = await res.json()

    if (data.error) {
      addressInput.value = 'Error loading address'
      return
    }

    addressInput.value = data.address

    // Show label if present
    if (data.label) {
      document.getElementById('receive-address-label').textContent = `Label: ${data.label}`
    } else {
      document.getElementById('receive-address-label').textContent = 'Current receiving address'
    }

    // Display address in large format (no QR code - not available in holepunchto)
    document.getElementById('qr-code').innerHTML = `
      <div class="address-large">${data.address}</div>
    `

    // Load wallet addresses list
    await loadWalletAddresses(chain)

  } catch (err) {
    console.error('Address error:', err)
    addressInput.value = 'Failed to load address'
  }

  modal.showModal()
}

// Load wallet addresses for chain
async function loadWalletAddresses(chain) {
  const container = document.getElementById('wallet-addresses-list')
  container.innerHTML = '<p class="loading-text">Loading addresses...</p>'

  try {
    const res = await authFetch(`/api/addresses/${selectedNetwork}-${chain}`)
    const data = await res.json()

    if (data.error) {
      container.innerHTML = `<p class="error-text">${data.error}</p>`
      return
    }

    if (data.addresses.length === 0) {
      container.innerHTML = '<p class="info-text">No addresses yet. Derive one to get started!</p>'
      return
    }

    // Build address list HTML
    let html = ''
    for (const addr of data.addresses) {
      const shortAddr = addr.address.substring(0, 20) + '...'
      const isCurrent = addr.isCurrent
      const currentBadge = isCurrent ? '<span class="badge badge-primary">Current</span>' : ''
      const labelDisplay = addr.label || `Address ${addr.index}`

      html += `
        <div class="wallet-address-item ${isCurrent ? 'current' : ''}">
          <div class="address-info">
            <div class="address-label-row">
              <input type="text"
                class="address-label-input ${addr.label ? '' : 'placeholder'}"
                value="${addr.label || `Address ${addr.index}`}"
                onblur="updateWalletAddressLabel('${chain}', ${addr.index}, this.value)"
                placeholder="Add label..."
              />
              ${currentBadge}
            </div>
            <code class="address-code" title="${addr.address}">${shortAddr}</code>
          </div>
          <div class="address-actions">
            ${!isCurrent ? `<button class="btn-icon-small" onclick="setWalletAddressCurrent('${chain}', ${addr.index})" title="Set as current">⭐</button>` : ''}
            <button class="btn-icon-small" onclick="copyWalletAddress('${addr.address}')" title="Copy">📋</button>
            ${!isCurrent ? `<button class="btn-icon-small btn-danger" onclick="archiveWalletAddress('${chain}', ${addr.index})" title="Archive">🗑️</button>` : ''}
          </div>
        </div>
      `
    }

    container.innerHTML = html
  } catch (err) {
    console.error('Load addresses error:', err)
    container.innerHTML = `<p class="error-text">Failed to load addresses</p>`
  }
}

// Derive new address
async function deriveNewAddress() {
  if (!receiveModalChain) return

  showLabelInput(async (label) => {
    try {
      const res = await authFetch(`/api/addresses/${selectedNetwork}-${receiveModalChain}/derive`, {
        method: 'POST',
        body: JSON.stringify({ label: label || '' })
      })

      const data = await res.json()

      if (data.error) {
        showError(data.error)
        return
      }

      showSuccess('New address derived!')
      await loadWalletAddresses(receiveModalChain)
    } catch (err) {
      showError(`Failed to derive address: ${err.message}`)
    }
  })
}

// Update wallet address label
async function updateWalletAddressLabel(chain, index, label) {
  try {
    const res = await authFetch(`/api/addresses/${selectedNetwork}-${chain}/${index}`, {
      method: 'PUT',
      body: JSON.stringify({ label })
    })

    const data = await res.json()

    if (data.error) {
      showError(data.error)
      return
    }

    showSuccess('Label updated!')
  } catch (err) {
    console.error('Update label error:', err)
  }
}

// Set wallet address as current
async function setWalletAddressCurrent(chain, index) {
  try {
    const res = await authFetch(`/api/addresses/${selectedNetwork}-${chain}/${index}/current`, {
      method: 'POST'
    })

    const data = await res.json()

    if (data.error) {
      showError(data.error)
      return
    }

    showSuccess('Address set as current!')

    // Reload both modals if open
    if (receiveModalChain) {
      await showReceive(chain)
    }
    if (manageAddressesChain) {
      await loadManageAddresses(chain)
    }

    // Update main wallet view
    await updateWalletView()
  } catch (err) {
    showError(`Failed to set current: ${err.message}`)
  }
}

// Copy wallet address to clipboard
async function copyWalletAddress(address) {
  try {
    await navigator.clipboard.writeText(address)
    showSuccess('Address copied!')
  } catch (err) {
    showError('Failed to copy address')
  }
}

// Archive wallet address
async function archiveWalletAddress(chain, index) {
  const confirmed = confirm('Archive this address? It will be hidden from the list.')
  if (!confirmed) return

  try {
    const res = await authFetch(`/api/addresses/${selectedNetwork}-${chain}/${index}`, {
      method: 'DELETE'
    })

    const data = await res.json()

    if (data.error) {
      showError(data.error)
      return
    }

    showSuccess('Address archived!')

    // Reload both modals if open
    if (receiveModalChain) {
      await loadWalletAddresses(chain)
    }
    if (manageAddressesChain) {
      await loadManageAddresses(chain)
    }
  } catch (err) {
    showError(`Failed to archive: ${err.message}`)
  }
}

// Close receive modal
function closeReceive() {
  document.getElementById('receive-modal').close()
}

// Copy address to clipboard
async function copyAddress() {
  const input = document.getElementById('receive-address')
  input.select()

  try {
    await navigator.clipboard.writeText(input.value)
    showSuccess('Address copied to clipboard!')
  } catch (err) {
    // Fallback for older browsers
    document.execCommand('copy')
    showSuccess('Address copied!')
  }
}

// Show success message
function showSuccess(message) {
  const toast = document.getElementById('error-toast')
  toast.textContent = message
  toast.classList.add('show', 'success')

  // If a modal is open, append toast to it (so it appears above backdrop)
  const openDialog = document.querySelector('dialog[open]')
  if (openDialog && !openDialog.contains(toast)) {
    openDialog.appendChild(toast)
  } else if (!openDialog && toast.parentElement !== document.body) {
    // If no dialog open, move back to body
    document.body.appendChild(toast)
  }

  setTimeout(() => {
    toast.classList.remove('show', 'success')
  }, 3000)
}

// Show transaction details modal
function showTxDetails(index) {
  const tx = allTransactions[index]
  if (!tx) return

  const modal = document.getElementById('tx-details-modal')
  const content = document.getElementById('tx-details-content')

  const date = new Date(tx.timestamp).toLocaleString()
  const directionIcon = tx.direction === 'sent' ? '📤' : '📥'
  const statusIcon = tx.status === 'confirmed' ? '✅' : '⏳'

  // Format amount based on chain/token
  let amount, fee
  const baseChain = tx.chain.replace(/-usdt$/, '').replace(/-usdc$/, '')
  const chain = SUPPORTED_CHAINS[baseChain]

  // Check if this is a token transaction
  if (tx.token || tx.chain.includes('-usdt') || tx.chain.includes('-usdc')) {
    const tokenSymbol = tx.token || (tx.chain.includes('-usdt') ? 'USDT' : 'USDC')
    const decimals = tx.decimals || 6  // USDT/USDC have 6 decimals
    const tokenAmount = Number(tx.amount) / Math.pow(10, decimals)
    amount = `${tokenAmount.toFixed(decimals > 6 ? 6 : decimals)} ${tokenSymbol}`
    // Fee is still in ETH (wei)
    fee = chain ? chain.formatSmallUnit(tx.fee) : `${tx.fee} wei`
  } else {
    amount = chain ? chain.formatBalance(tx.amount) : tx.amount
    fee = chain ? chain.formatSmallUnit(tx.fee) : tx.fee
  }

  // Explorer links (chain-specific URLs)
  let explorerLink = ''
  if (baseChain === 'bitcoin') {
    const network = tx.network === 'testnet' ? 'testnet/' : ''
    explorerLink = `https://blockstream.info/${network}tx/${tx.hash}`
  } else if (baseChain === 'ethereum') {
    const network = tx.network === 'testnet' ? 'sepolia.' : ''
    explorerLink = `https://${network}etherscan.io/tx/${tx.hash}`
  }

  content.innerHTML = `
    <div class="tx-detail-row">
      <span class="tx-detail-label">Status:</span>
      <span class="tx-detail-value">${statusIcon} ${tx.status.toUpperCase()}</span>
    </div>
    <div class="tx-detail-row">
      <span class="tx-detail-label">Direction:</span>
      <span class="tx-detail-value">${directionIcon} ${tx.direction.toUpperCase()}</span>
    </div>
    <div class="tx-detail-row">
      <span class="tx-detail-label">Chain:</span>
      <span class="tx-detail-value">${tx.chain.toUpperCase()} (${tx.network})</span>
    </div>
    <div class="tx-detail-row">
      <span class="tx-detail-label">Amount:</span>
      <span class="tx-detail-value">${amount}</span>
    </div>
    <div class="tx-detail-row">
      <span class="tx-detail-label">Fee:</span>
      <span class="tx-detail-value">${fee}</span>
    </div>
    <div class="tx-detail-row">
      <span class="tx-detail-label">From:</span>
      <span class="tx-detail-value tx-detail-address">${tx.from}</span>
    </div>
    <div class="tx-detail-row">
      <span class="tx-detail-label">To:</span>
      <span class="tx-detail-value tx-detail-address">${tx.to}</span>
    </div>
    <div class="tx-detail-row">
      <span class="tx-detail-label">Hash:</span>
      <span class="tx-detail-value tx-detail-hash">${tx.hash}</span>
    </div>
    ${tx.blockHeight ? `
    <div class="tx-detail-row">
      <span class="tx-detail-label">Block:</span>
      <span class="tx-detail-value">${tx.blockHeight.toLocaleString()}</span>
    </div>
    ` : ''}
    <div class="tx-detail-row">
      <span class="tx-detail-label">Date:</span>
      <span class="tx-detail-value">${date}</span>
    </div>
    <div class="tx-detail-actions">
      <a href="${explorerLink}" target="_blank" class="btn-primary">🔍 View on Explorer</a>
    </div>
  `

  modal.showModal()
}

// Close transaction details modal
function closeTxDetails() {
  document.getElementById('tx-details-modal').close()
}

// Show send modal
async function showSend(chain) {
  currentSendChain = chain
  const modal = document.getElementById('send-modal')

  // Get proper display name for chain/token
  let displayName, unit, decimals
  if (chain === 'ethereum-usdt') {
    displayName = 'USDT'
    unit = 'USDT'
    decimals = 6
  } else if (chain === 'ethereum-usdc') {
    displayName = 'USDC'
    unit = 'USDC'
    decimals = 6
  } else if (chain === 'ethereum') {
    displayName = 'Ethereum'
    unit = 'ETH'
    decimals = 18
  } else if (chain === 'bitcoin') {
    displayName = 'Bitcoin'
    unit = 'sats'
    decimals = 0
  } else {
    displayName = chain.charAt(0).toUpperCase() + chain.slice(1)
    unit = chain
    decimals = 18
  }

  const networkLabel = selectedNetwork === 'testnet'
    ? (chain.startsWith('ethereum') ? 'Sepolia' : 'Testnet')
    : 'Mainnet'

  document.getElementById('send-chain').textContent = `${displayName} (${networkLabel})`

  // Reset form
  document.getElementById('send-to').value = ''
  document.getElementById('send-amount').value = ''
  document.getElementById('send-to-error').textContent = ''
  document.getElementById('send-amount-error').textContent = ''
  document.getElementById('fee-display').innerHTML = '<div class="spinner"></div>'
  usedMaxAmount = false  // Reset max flag

  // Reset max flag if user manually changes amount
  document.getElementById('send-amount').addEventListener('input', function handleAmountChange() {
    usedMaxAmount = false  // User manually changed amount, so not max anymore
  }, { once: true })  // Only listen once per modal open

  // Load current balance
  try {
    const res = await authFetch(`/api/balance/${selectedNetwork}-${chain}`)
    const data = await res.json()
    currentBalance = Number(data.balance)

    // Format balance display: prefer the chain's own formatter (SUPPORTED_CHAINS
    // carries per-token ones, e.g. USDT/USDC at 6 decimals), else fall back to
    // decimals-aware formatting rather than showing raw wei.
    const chainConfig = SUPPORTED_CHAINS[chain]
    let balanceDisplay
    if (chainConfig && chainConfig.formatBalance) {
      balanceDisplay = chainConfig.formatBalance(currentBalance)
    } else if (chain === 'bitcoin') {
      balanceDisplay = `${currentBalance.toLocaleString()} sats`
    } else if (decimals === 6) {
      balanceDisplay = `${(currentBalance / 1e6).toFixed(2)} ${unit}`
    } else {
      balanceDisplay = `${(currentBalance / 1e18).toFixed(6)} ${unit}`
    }

    document.getElementById('send-amount-helper').textContent = `Balance: ${balanceDisplay}`
  } catch (err) {
    console.error('Failed to load balance:', err)
    document.getElementById('send-amount-helper').textContent = 'Balance: unavailable'
  }

  modal.showModal()
}

// Close send modal
function closeSend() {
  document.getElementById('send-modal').close()
}

// Set amount to maximum balance
async function setMaxAmount() {
  console.log('setMaxAmount called', { currentBalance, currentSendChain })

  if (!currentSendChain) {
    showError('Chain not selected')
    return
  }

  if (!currentBalance || currentBalance <= 0) {
    showError('No balance available')
    return
  }

  const chain = SUPPORTED_CHAINS[currentSendChain]
  if (!chain) {
    console.error('Chain config not found:', currentSendChain)
    showError('Invalid chain configuration')
    return
  }

  const toAddress = document.getElementById('send-to').value.trim()

  // If no recipient address, use static fee reserve (original behavior)
  if (!toAddress) {
    const reserved = chain.feeReserve || 0
    const maxAmount = Math.max(0, currentBalance - reserved)

    console.log('Setting max amount (static reserve):', { maxAmount, reserved, currentBalance })

    // Convert to human-readable for input display
    const maxAmountDisplay = fromSmallestUnits(maxAmount, currentSendChain)
    document.getElementById('send-amount').value = maxAmountDisplay
    document.getElementById('fee-display').innerHTML = `
      <p class="help-text" style="color: #f59e0b;">
        ℹ️ Estimated max amount (enter recipient address for exact fee calculation)
      </p>
    `
    usedMaxAmount = true  // Mark that max was used

    showSuccess(`Max amount set (estimated, enter address for exact fees)`)
    return
  }

  // With recipient address - calculate exact fees via API
  document.getElementById('fee-display').innerHTML = '<div class="spinner"></div>'
  document.getElementById('send-amount').value = '...'

  try {
    // Estimate fee for max transaction
    // Use (balance - reserve) to avoid "insufficient balance" error during estimation
    const reserved = chain.feeReserve || 0
    const estimateAmount = Math.max(1, currentBalance - reserved)

    const res = await authFetch(`/api/estimate-fee`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        chain: currentSendChain,
        network: selectedNetwork,
        to: toAddress,
        amount: estimateAmount  // Estimate fee for (balance - reserve)
      })
    })

    const data = await res.json()

    if (data.error) {
      showError(`Fee estimation failed: ${data.error}`)
      document.getElementById('send-amount').value = ''
      document.getElementById('fee-display').innerHTML = `<p class="form-error">${data.error}</p>`
      return
    }

    estimatedFee = data.fee
    const maxAmount = Math.max(0, currentBalance - estimatedFee)

    if (maxAmount <= 0) {
      showError('Insufficient balance to cover transaction fees')
      document.getElementById('send-amount').value = ''
      return
    }

    console.log('Setting max amount (exact fee):', { maxAmount, fee: estimatedFee, currentBalance })

    // Convert to human-readable for input display
    const maxAmountDisplay = fromSmallestUnits(maxAmount, currentSendChain)

    // Set the max amount
    document.getElementById('send-amount').value = maxAmountDisplay
    usedMaxAmount = true  // Mark that max was used

    // Display fee info
    const feeFormatted = currentSendChain === 'bitcoin'
      ? formatSats(estimatedFee)
      : formatEthFee(estimatedFee)

    // For tokens, fee is in ETH (different currency), so show separately
    const isToken = currentSendChain.includes('usdt') || currentSendChain.includes('usdc')
    let totalDisplay
    if (currentSendChain === 'bitcoin') {
      totalDisplay = `${currentBalance.toLocaleString()} sats`
    } else if (isToken) {
      const tokenSymbol = currentSendChain.includes('usdt') ? 'USDT' : 'USDC'
      totalDisplay = `${maxAmountDisplay} ${tokenSymbol} + ${feeFormatted} gas`
    } else {
      // Native ETH
      const totalEth = Number(currentBalance) / 1e18
      totalDisplay = `${totalEth.toFixed(6)} ETH`
    }

    document.getElementById('fee-display').innerHTML = `
      <div class="fee-option">
        <span class="fee-label">Estimated Fee</span>
        <span class="fee-value">${feeFormatted}</span>
      </div>
      <div class="fee-option">
        <span class="fee-label">Total</span>
        <span class="fee-value">${totalDisplay}</span>
      </div>
      <p class="help-text" style="margin-top: 0.5rem; color: #f59e0b;">
        ℹ️ Max amount is your balance minus transaction fees
      </p>
    `

    // Show info message
    showSuccess(`Max amount set (${chain.formatSmallUnit(maxAmount)} after fees)`)
  } catch (err) {
    console.error('Fee estimation error:', err)
    showError(`Failed to calculate max amount: ${err.message}`)
    document.getElementById('send-amount').value = ''
    document.getElementById('fee-display').innerHTML = '<p class="form-error">Failed to estimate fee</p>'
  }
}

// Estimate transaction fee
async function estimateFee() {
  const toAddress = document.getElementById('send-to').value
  const amountInput = document.getElementById('send-amount').value

  if (!toAddress || !amountInput) {
    document.getElementById('fee-display').innerHTML =
      '<p class="help-text">Enter address and amount to estimate fee</p>'
    return
  }

  document.getElementById('fee-display').innerHTML = '<div class="spinner"></div>'

  // Convert human-readable amount to smallest units for backend
  const amount = toSmallestUnits(amountInput, currentSendChain)

  try {
    const res = await authFetch(`/api/estimate-fee`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        chain: currentSendChain,
        network: selectedNetwork,
        to: toAddress,
        amount: amount
      })
    })

    const data = await res.json()

    if (data.error) {
      document.getElementById('fee-display').innerHTML =
        `<p class="form-error">${data.error}</p>`
      estimatedFee = null
      return
    }

    estimatedFee = data.fee

    const feeFormatted = currentSendChain === 'bitcoin'
      ? formatSats(data.fee)
      : formatEthFee(data.fee)

    // For tokens, fee is in ETH (different currency), so show separately
    const isToken = currentSendChain.includes('usdt') || currentSendChain.includes('usdc')
    let totalDisplay
    if (currentSendChain === 'bitcoin') {
      // Amount is now in sats (smallest units)
      const totalSats = Number(amount) + Number(data.fee)
      totalDisplay = `${totalSats.toLocaleString()} sats`
    } else if (isToken) {
      const tokenSymbol = currentSendChain.includes('usdt') ? 'USDT' : 'USDC'
      // Display human-readable amount from input
      totalDisplay = `${amountInput} ${tokenSymbol} + ${feeFormatted} gas`
    } else {
      // Native ETH - convert both to ETH for total
      const totalWei = BigInt(amount) + BigInt(data.fee)
      const totalEth = Number(totalWei) / 1e18
      totalDisplay = `${totalEth.toFixed(6)} ETH`
    }

    document.getElementById('fee-display').innerHTML = `
      <div class="fee-option">
        <span class="fee-label">Estimated Fee</span>
        <span class="fee-value">${feeFormatted}</span>
      </div>
      <div class="fee-option">
        <span class="fee-label">Total</span>
        <span class="fee-value">${totalDisplay}</span>
      </div>
    `
  } catch (err) {
    console.error('Fee estimation error:', err)
    document.getElementById('fee-display').innerHTML =
      '<p class="form-error">Failed to estimate fee</p>'
    estimatedFee = null
  }
}

// Validate and show confirmation
function confirmSend() {
  const toAddress = document.getElementById('send-to').value.trim()
  const amountInput = document.getElementById('send-amount').value.trim()

  // Clear previous errors
  document.getElementById('send-to-error').textContent = ''
  document.getElementById('send-amount-error').textContent = ''

  let hasError = false

  // Validate address using chain config
  if (!toAddress) {
    document.getElementById('send-to-error').textContent = 'Address is required'
    hasError = true
  } else {
    const chain = SUPPORTED_CHAINS[currentSendChain]
    if (chain && chain.addressPattern) {
      const pattern = chain.addressPattern[selectedNetwork]
      if (pattern && !toAddress.match(pattern)) {
        document.getElementById('send-to-error').textContent = `Invalid ${chain.name} address`
        hasError = true
      }
    }
  }

  // Validate amount - convert to smallest units for balance comparison
  const amountInSmallestUnits = Number(toSmallestUnits(amountInput, currentSendChain))
  if (!amountInput || isNaN(parseFloat(amountInput)) || parseFloat(amountInput) <= 0) {
    document.getElementById('send-amount-error').textContent = 'Invalid amount'
    hasError = true
  } else if (amountInSmallestUnits > currentBalance) {
    document.getElementById('send-amount-error').textContent = 'Insufficient balance'
    hasError = true
  }

  if (hasError) return

  // Estimate fee if not done
  if (!estimatedFee) {
    estimateFee()
    setTimeout(() => {
      if (estimatedFee) confirmSend()
    }, 2000)
    return
  }

  // Show confirmation modal
  const confirmModal = document.getElementById('confirm-modal')
  const confirmContent = document.getElementById('confirm-content')

  // Convert to smallest units for calculations
  const amountSmallest = toSmallestUnits(amountInput, currentSendChain)

  // Format for display
  const isToken = currentSendChain.includes('usdt') || currentSendChain.includes('usdc')
  let amountFormatted
  if (currentSendChain === 'bitcoin') {
    amountFormatted = formatBTC(amountSmallest)
  } else if (isToken) {
    const tokenSymbol = currentSendChain.includes('usdt') ? 'USDT' : 'USDC'
    amountFormatted = `${amountInput} ${tokenSymbol}`
  } else {
    amountFormatted = formatETH(amountSmallest)
  }

  const feeFormatted = currentSendChain === 'bitcoin'
    ? formatSats(estimatedFee)
    : formatEthFee(estimatedFee)

  // Calculate total display
  let totalDisplay
  if (currentSendChain === 'bitcoin') {
    const totalSats = Number(amountSmallest) + Number(estimatedFee)
    totalDisplay = `${totalSats.toLocaleString()} sats`
  } else if (isToken) {
    const tokenSymbol = currentSendChain.includes('usdt') ? 'USDT' : 'USDC'
    totalDisplay = `${amountInput} ${tokenSymbol} + ${feeFormatted} gas`
  } else {
    const totalWei = BigInt(amountSmallest) + BigInt(estimatedFee)
    const totalEth = Number(totalWei) / 1e18
    totalDisplay = `${totalEth.toFixed(6)} ETH`
  }

  const networkWarning = selectedNetwork === 'mainnet'
    ? '<div class="confirm-warning">⚠️ WARNING: You are sending on MAINNET. This will use real funds!</div>'
    : ''

  confirmContent.innerHTML = `
    ${networkWarning}
    <div class="confirm-row">
      <span class="confirm-label">Chain:</span>
      <span class="confirm-value">${chain ? chain.name : currentSendChain.toUpperCase()} (${selectedNetwork})</span>
    </div>
    <div class="confirm-row">
      <span class="confirm-label">To:</span>
      <span class="confirm-value">${toAddress}</span>
    </div>
    <div class="confirm-row">
      <span class="confirm-label">Amount:</span>
      <span class="confirm-value">${amountFormatted}</span>
    </div>
    <div class="confirm-row">
      <span class="confirm-label">Fee:</span>
      <span class="confirm-value">${feeFormatted}</span>
    </div>
    <div class="confirm-row">
      <span class="confirm-label">Total:</span>
      <span class="confirm-value">${totalDisplay}</span>
    </div>
  `

  confirmModal.showModal()
}

// Close confirmation modal
function closeConfirm() {
  document.getElementById('confirm-modal').close()
}

// Execute send transaction
async function executeSend() {
  const toAddress = document.getElementById('send-to').value.trim()
  const amountInput = document.getElementById('send-amount').value.trim()

  // Convert human-readable amount to smallest units for backend
  const amount = toSmallestUnits(amountInput, currentSendChain)

  // Close confirmation modal
  closeConfirm()
  closeSend()

  // Show loading
  showError('Sending transaction...')

  try {
    const res = await authFetch(`/api/send`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        chain: currentSendChain,
        network: selectedNetwork,
        to: toAddress,
        amount: amount,
        sendMax: usedMaxAmount  // Tell backend this is a max send
      })
    })

    const data = await res.json()

    if (data.error) {
      showError(data.error)
      return
    }

    showSuccess(`Transaction sent! Hash: ${data.hash.substring(0, 16)}...`)
    usedMaxAmount = false  // Reset flag after successful send

    // Show native notification (don't let this crash the app)
    try {
      const isToken = currentSendChain.includes('usdt') || currentSendChain.includes('usdc')
      const displayUnit = isToken
        ? (currentSendChain.includes('usdt') ? 'USDT' : 'USDC')
        : currentSendChain.toUpperCase()
      showNotification(
        'Transaction Sent',
        `${amountInput} ${displayUnit} sent successfully`,
        'success'
      )
    } catch (notifErr) {
      console.log('Notification failed (non-critical):', notifErr.message)
    }

    // Sync transaction from blockchain, then refresh balances and display
    setTimeout(async () => {
      // First sync from blockchain to fetch the new transaction
      try {
        // Get current account index for sync
        const select = document.getElementById('wallet-address-select')
        const rawIndex = select ? parseInt(select.value) : 0
        const accountIndex = isNaN(rawIndex) ? 0 : rawIndex

        const syncRes = await authFetch(`/api/transactions/sync`, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({
            chain: currentSendChain,
            network: selectedNetwork,  // Add network parameter
            accountIndex
          })
        })
        const syncData = await syncRes.json()
        console.log('Auto-sync after send:', syncData)
      } catch (syncErr) {
        console.warn('Auto-sync failed (non-critical):', syncErr.message)
      }

      // Then refresh UI
      loadCurrentBalance()
      loadTransactions()
    }, 2000) // Wait 2 seconds for transaction to propagate

  } catch (err) {
    console.error('Send error:', err)
    showError('Failed to send transaction')
    usedMaxAmount = false  // Reset flag on error
  }
}

// Export transactions as CSV
function exportTransactions() {
  if (allTransactions.length === 0) {
    showError('No transactions to export')
    return
  }

  // CSV headers
  const headers = ['Date', 'Time', 'Chain', 'Network', 'Direction', 'From', 'To', 'Amount', 'Fee', 'Status', 'Hash', 'Block Height']

  // CSV rows
  const rows = allTransactions.map(tx => {
    const date = new Date(tx.timestamp)
    return [
      date.toLocaleDateString(),
      date.toLocaleTimeString(),
      tx.chain,
      tx.network,
      tx.direction,
      tx.from,
      tx.to,
      tx.amount,
      tx.fee,
      tx.status,
      tx.hash,
      tx.blockHeight || ''
    ].map(field => `"${field}"`).join(',')
  })

  // Combine headers and rows
  const csv = [headers.join(','), ...rows].join('\n')

  // Create download link
  const blob = new Blob([csv], { type: 'text/csv' })
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = `hyper-wallet-transactions-${selectedNetwork}-${Date.now()}.csv`
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  URL.revokeObjectURL(url)

  showSuccess(`Exported ${allTransactions.length} transactions`)
}

// Check if wallet needs password setup (plaintext mnemonics)
async function checkEncryptionStatus() {
  try {
    const res = await authFetch(`/api/wallet/encryption-status`)
    if (res.ok) {
      const data = await res.json()
      return data.encrypted
    }
  } catch (err) {
    console.error('Failed to check encryption status:', err)
  }
  return null // Unknown status
}

// Prompt for password to unlock wallet using dialog
async function promptForPassword() {
  const maxAttempts = 3
  let attempts = 0

  console.log('🔐 promptForPassword() called')

  // Check if this is first-time password setup or regular unlock
  const isEncrypted = await checkEncryptionStatus()
  const isFirstTimeSetup = isEncrypted === false

  console.log('📊 Encryption status:', { isEncrypted, isFirstTimeSetup })

  return new Promise((resolve) => {
    const dialog = document.getElementById('password-dialog')
    const titleEl = document.getElementById('password-dialog-title')
    const warningEl = document.getElementById('password-dialog-warning')
    const passwordInput = document.getElementById('password-input')
    const confirmGroup = document.getElementById('password-confirm-group')
    const confirmInput = document.getElementById('password-confirm-input')
    const matchError = document.getElementById('password-match-error')
    const strengthDiv = document.getElementById('password-strength')
    const strengthText = document.getElementById('password-strength-text')
    const strengthBar = document.getElementById('password-strength-bar')
    const submitBtn = document.getElementById('password-submit-btn')
    const cancelBtn = document.getElementById('password-cancel-btn')
    const attemptsEl = document.getElementById('password-attempts')

    // Check if all elements exist
    if (!dialog || !passwordInput || !submitBtn) {
      console.error('❌ Password dialog elements not found!')
      console.error({ dialog, passwordInput, submitBtn })
      resolve(false)
      return
    }

    console.log('✅ Password dialog elements found')

    // Configure dialog for first-time setup vs unlock
    if (isFirstTimeSetup) {
      titleEl.textContent = '⚠️  SET WALLET PASSWORD'
      warningEl.style.display = 'block'
      confirmGroup.style.display = 'block'
      strengthDiv.style.display = 'block'
    } else {
      titleEl.textContent = '🔐 Unlock Wallet'
      warningEl.style.display = 'none'
      confirmGroup.style.display = 'none'
      strengthDiv.style.display = 'none'
    }

    // Show attempts counter
    if (attempts > 0) {
      attemptsEl.textContent = `Attempt ${attempts + 1} of ${maxAttempts}`
      attemptsEl.style.display = 'block'
    } else {
      attemptsEl.style.display = 'none'
    }

    // Password strength calculator
    function checkPasswordStrength(password) {
      if (!password) return { strength: 0, text: 'Too short', color: '#d32f2f' }

      let strength = 0
      if (password.length >= 8) strength += 25
      if (password.length >= 12) strength += 25
      if (/[a-z]/.test(password) && /[A-Z]/.test(password)) strength += 20
      if (/\d/.test(password)) strength += 15
      if (/[^a-zA-Z0-9]/.test(password)) strength += 15

      if (strength < 40) return { strength, text: 'Weak', color: '#d32f2f' }
      if (strength < 60) return { strength, text: 'Fair', color: '#ff9800' }
      if (strength < 80) return { strength, text: 'Good', color: '#2196f3' }
      return { strength, text: 'Strong', color: '#4caf50' }
    }

    // Validation function
    function validateInputs() {
      const password = passwordInput.value
      const confirm = confirmInput.value

      // Update strength indicator (only for first-time setup)
      if (isFirstTimeSetup) {
        const { strength, text, color} = checkPasswordStrength(password)
        strengthText.textContent = text
        strengthText.style.color = color
        strengthBar.style.width = `${strength}%`
        strengthBar.style.background = color
      }

      // Check password length
      if (password.length < 8) {
        submitBtn.disabled = true
        return
      }

      // Check password match (only for first-time setup)
      if (isFirstTimeSetup) {
        if (password !== confirm) {
          matchError.style.display = 'block'
          submitBtn.disabled = true
          return
        } else {
          matchError.style.display = 'none'
        }
      }

      // All validations passed
      submitBtn.disabled = false
    }

    // Add input listeners
    passwordInput.addEventListener('input', validateInputs)
    if (isFirstTimeSetup) {
      confirmInput.addEventListener('input', validateInputs)
    }

    // Handle form submission
    async function handleSubmit() {
      const password = passwordInput.value

      try {
        submitBtn.disabled = true
        submitBtn.textContent = 'Submitting...'

        // Call appropriate endpoint based on whether this is first-time setup
        const endpoint = isFirstTimeSetup ? '/api/set-password' : '/api/unlock'
        const res = await authFetch(endpoint, {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ password })
        })

        if (!res.ok) {
          const error = await res.json()
          if (res.status === 401) {
            attempts++

            if (attempts >= maxAttempts) {
              alert('❌ Maximum unlock attempts reached. Please refresh the page to try again.')
              dialog.close()
              resolve(false)
              return
            }

            // Show error and reset
            if (isFirstTimeSetup) {
              alert('❌ Failed to set password. Please try again.')
            } else {
              alert('❌ Invalid password. Please try again.')
            }

            passwordInput.value = ''
            confirmInput.value = ''
            submitBtn.textContent = 'Submit'
            validateInputs()

            // Update attempts counter
            attemptsEl.textContent = `Attempt ${attempts + 1} of ${maxAttempts}`
            attemptsEl.style.display = 'block'
            return
          }
          throw new Error(error.error || 'Unlock failed')
        }

        const data = await res.json()

        // Store API token
        saveToken(data.token)

        // Close dialog
        dialog.close()

        // Show success message
        if (data.migrated) {
          alert('✅ SUCCESS!\n\nYour wallet mnemonics have been encrypted.\n\nREMEMBER YOUR PASSWORD - you will need it every time you access the wallet!')
          showSuccess('✅ Wallet encrypted and unlocked!')
        } else {
          showSuccess('✅ Wallet unlocked successfully')
        }

        console.log('🔓 Wallet unlocked, public key:', data.publicKey)
        resolve(true)

      } catch (err) {
        console.error('Unlock error:', err)
        alert(`❌ Error: ${err.message}`)
        attempts++

        if (attempts >= maxAttempts) {
          dialog.close()
          resolve(false)
        } else {
          submitBtn.textContent = 'Submit'
          submitBtn.disabled = false
        }
      }
    }

    // Button handlers
    submitBtn.onclick = handleSubmit
    cancelBtn.onclick = () => {
      dialog.close()
      resolve(false)
    }

    // Enter key submits
    passwordInput.addEventListener('keydown', (e) => {
      if (e.key === 'Enter' && !submitBtn.disabled) {
        handleSubmit()
      }
    })
    confirmInput.addEventListener('keydown', (e) => {
      if (e.key === 'Enter' && !submitBtn.disabled) {
        handleSubmit()
      }
    })

    // Reset and show dialog
    passwordInput.value = ''
    confirmInput.value = ''
    matchError.style.display = 'none'
    submitBtn.textContent = 'Submit'
    submitBtn.disabled = true

    dialog.showModal()
    passwordInput.focus()
  })
}

// Show change password dialog
function showChangePassword() {
  const dialog = document.getElementById('change-password-dialog')
  const currentInput = document.getElementById('current-password-input')
  const newInput = document.getElementById('new-password-input')
  const confirmInput = document.getElementById('new-password-confirm-input')
  const matchError = document.getElementById('change-password-match-error')
  const strengthDiv = document.getElementById('change-password-strength')
  const strengthText = document.getElementById('change-password-strength-text')
  const strengthBar = document.getElementById('change-password-strength-bar')
  const submitBtn = document.getElementById('change-password-submit-btn')
  const cancelBtn = document.getElementById('change-password-cancel-btn')
  const errorDiv = document.getElementById('change-password-error')

  // Password strength calculator (reuse from unlock dialog)
  function checkPasswordStrength(password) {
    if (!password) return { strength: 0, text: 'Too short', color: '#d32f2f' }

    let strength = 0
    if (password.length >= 8) strength += 25
    if (password.length >= 12) strength += 25
    if (/[a-z]/.test(password) && /[A-Z]/.test(password)) strength += 20
    if (/\d/.test(password)) strength += 15
    if (/[^a-zA-Z0-9]/.test(password)) strength += 15

    if (strength < 40) return { strength, text: 'Weak', color: '#d32f2f' }
    if (strength < 60) return { strength, text: 'Fair', color: '#ff9800' }
    if (strength < 80) return { strength, text: 'Good', color: '#2196f3' }
    return { strength, text: 'Strong', color: '#4caf50' }
  }

  // Validation function
  function validateInputs() {
    const current = currentInput.value
    const newPassword = newInput.value
    const confirm = confirmInput.value

    // Update strength indicator
    const { strength, text, color } = checkPasswordStrength(newPassword)
    strengthText.textContent = text
    strengthText.style.color = color
    strengthBar.style.width = `${strength}%`
    strengthBar.style.background = color

    // Check all fields filled
    if (!current || newPassword.length < 8 || !confirm) {
      submitBtn.disabled = true
      return
    }

    // Check password match
    if (newPassword !== confirm) {
      matchError.style.display = 'block'
      submitBtn.disabled = true
      return
    } else {
      matchError.style.display = 'none'
    }

    // Check new password different from current
    if (newPassword === current) {
      matchError.textContent = '❌ New password must be different from current'
      matchError.style.display = 'block'
      submitBtn.disabled = true
      return
    }

    matchError.textContent = '❌ New passwords do not match'
    matchError.style.display = 'none'

    // All validations passed
    submitBtn.disabled = false
  }

  // Add input listeners
  currentInput.addEventListener('input', validateInputs)
  newInput.addEventListener('input', validateInputs)
  confirmInput.addEventListener('input', validateInputs)

  // Handle form submission
  async function handleSubmit() {
    const currentPassword = currentInput.value
    const newPassword = newInput.value

    try {
      submitBtn.disabled = true
      submitBtn.textContent = 'Changing Password...'
      errorDiv.style.display = 'none'

      // Call change password endpoint
      const res = await authFetch(`/api/change-password`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          currentPassword,
          newPassword
        })
      })

      if (!res.ok) {
        const error = await res.json()
        throw new Error(error.error || 'Password change failed')
      }

      const data = await res.json()

      // Close dialog
      dialog.close()

      // Show success
      alert('✅ Password changed successfully!\n\nYour mnemonics have been re-encrypted with the new password.\n\nYou\'ll need the new password on next login.')
      showSuccess('Password changed successfully')

    } catch (err) {
      console.error('Change password error:', err)
      errorDiv.textContent = err.message
      errorDiv.style.display = 'block'
      submitBtn.textContent = 'Change Password'
      submitBtn.disabled = false
    }
  }

  // Button handlers
  submitBtn.onclick = handleSubmit
  cancelBtn.onclick = () => {
    dialog.close()
  }

  // Enter key submits
  currentInput.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' && !submitBtn.disabled) {
      handleSubmit()
    }
  })
  newInput.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' && !submitBtn.disabled) {
      handleSubmit()
    }
  })
  confirmInput.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' && !submitBtn.disabled) {
      handleSubmit()
    }
  })

  // Reset and show dialog
  currentInput.value = ''
  newInput.value = ''
  confirmInput.value = ''
  matchError.style.display = 'none'
  errorDiv.style.display = 'none'
  submitBtn.textContent = 'Change Password'
  submitBtn.disabled = true

  dialog.showModal()
  currentInput.focus()
}

// Initialize on page load
async function init() {
  console.log('🚀 Hyper Wallet UI initialized')

  // Set up network selector UI
  document.querySelectorAll('.network-btn').forEach(btn => {
    btn.classList.remove('active')
    if (btn.dataset.network === selectedNetwork) {
      btn.classList.add('active')
    }
  })

  // Update network labels
  updateNetworkLabels()

  const status = await checkStatus()

  // If initialized, check if we need to prompt for password
  if (status.initialized) {
    const token = getToken()

    // If password not cached on server but we have a token, it's stale
    if (status.passwordCached === false && token) {
      console.log('🔐 Stale token detected - clearing and prompting for password')
      clearToken()
    }

    // If no token (or cleared stale token), prompt for password
    if (!getToken()) {
      console.log('🔐 Wallet initialized but password needed')
      const success = await promptForPassword()
      if (!success) {
        showError('Failed to unlock wallet. Please refresh and try again.')
        return
      }
      // Hide setup wizard and show main app after successful unlock
      console.log('✅ Unlock successful - showing main app')
      hideSetupWizard()
    }
  }

  // Populate currency selector from SUPPORTED_CHAINS
  const currencySelector = document.getElementById('currency-selector')
  if (currencySelector) {
    // Clear existing options
    currencySelector.innerHTML = ''

    // Add option for each base chain (not tokens)
    for (const [chainId, chain] of Object.entries(SUPPORTED_CHAINS)) {
      // Skip token entries - they're accessed via the unified Ethereum view
      if (chain.isToken) continue

      const option = document.createElement('option')
      option.value = chainId
      option.textContent = `${chain.icon} ${chain.name}`
      currencySelector.appendChild(option)
    }

    // Set selected chain
    currencySelector.value = selectedChain
  }

  // Fetch prices first (don't wait)
  fetchPrices()

  // Only load wallet data if initialized AND authenticated
  if (status.initialized && getToken()) {
    // Update wallet view for selected chain (icon, name, network, address selector)
    updateWalletView()

    // Use unified balance display for Ethereum
    const loadBalancePromise = selectedChain === 'ethereum'
      ? loadEthereumBalances()
      : loadCurrentBalance()

    await Promise.all([
      loadBalancePromise,
      loadCurrentQRCode(),
      loadTransactions()
    ])
  } else if (status.initialized && !getToken()) {
    console.warn('⚠️  Wallet initialized but not authenticated - waiting for password')
  }

  // Auto-refresh every 30 seconds
  setInterval(() => {
    if (document.visibilityState === 'visible') {
      // Use unified balance display for Ethereum
      if (selectedChain === 'ethereum') {
        loadEthereumBalances()
      } else {
        loadCurrentBalance()
      }
      loadCurrentQRCode()
      loadTransactions()
    }
  }, 30000)

  // Refresh prices every 5 minutes
  setInterval(() => {
    fetchPrices()
  }, 300000)

  // Add event listeners for send form (once DOM is ready)
  const sendAmountInput = document.getElementById('send-amount')
  const sendToInput = document.getElementById('send-to')

  if (sendAmountInput) {
    sendAmountInput.addEventListener('input', () => {
      // Debounce fee estimation
      clearTimeout(window.feeEstimateTimeout)
      window.feeEstimateTimeout = setTimeout(estimateFee, 500)
    })
  }

  if (sendToInput) {
    sendToInput.addEventListener('blur', estimateFee)
  }

  // Setup native feature event listeners
  setupNativeFeatureListeners()
}

// Native Feature Event Listeners
function setupNativeFeatureListeners() {
  // Listen for IPC events from main process (via pear-electron)
  if (typeof ui !== 'undefined' && ui.on) {
    // Refresh balances (triggered by tray menu or keyboard shortcut)
    ui.on('refresh-balances', async () => {
      console.log('🔄 Refresh balances triggered')
      await loadCurrentBalance()
      await loadTransactions()
      try {
        showNotification('Balances refreshed', 'Your wallet balances have been updated')
      } catch (err) {
        console.log('Notification failed (non-critical):', err.message)
      }
    })

    // Show send tab (triggered by keyboard shortcut Cmd+N)
    ui.on('show-send-tab', () => {
      console.log('💸 Show send tab triggered')
      showTab('send')
    })

    // Show settings (triggered by tray menu or keyboard shortcut Cmd+,)
    ui.on('show-settings', () => {
      console.log('⚙️  Show settings triggered')
      showTab('settings')
    })

    console.log('✓ Native feature event listeners registered')
  } else {
    console.log('ℹ️  Native feature events not available')
  }
}

// Native Notification Helper
// NOTE: Native notifications disabled in Pear runtime due to libnotify blocking issues
// Using in-app toasts only (showSuccess/showError)
function showNotification(title, body, type = 'info') {
  // Log to console only - native notifications cause UI freeze in Pear runtime
  console.log(`📢 Notification: ${title} - ${body}`)

  // Show in-app notification via toast (non-blocking)
  if (type === 'success') {
    showSuccess(`${title}: ${body}`)
  } else if (type === 'error') {
    showError(`${title}: ${body}`)
  }
  // Native Notification API disabled - causes libnotify blocking in pear-runtime
}

// Expose functions to global window object for onclick/onchange handlers
// (Required because wallet.js is loaded as type="module")
window.addDevice = addDevice
window.closeAddContact = closeAddContact
window.submitAddContact = submitAddContact
window.submitEditContact = submitEditContact
window.submitAddWallet = submitAddWallet
window.sendToContact = sendToContact
window.sendToContactAddress = sendToContactAddress
window.editContact = editContact
window.closeAddWallet = closeAddWallet
window.closeConfirm = closeConfirm
window.closeContacts = closeContacts
window.closeDeviceAdd = closeDeviceAdd
window.closeDeviceJoin = closeDeviceJoin
window.closeDevices = closeDevices
window.closeEditContact = closeEditContact
window.closeLabelInput = closeLabelInput
window.closeManageAddresses = closeManageAddresses
window.closeReceive = closeReceive
window.closeSend = closeSend
window.closeSettings = closeSettings
window.closeTxDetails = closeTxDetails
window.confirmLabelInput = confirmLabelInput
window.confirmMnemonicBackup = confirmMnemonicBackup
window.confirmSend = confirmSend
window.copyAddress = copyAddress
window.copyInviteCode = copyInviteCode
window.deduplicateDevices = deduplicateDevices
window.deriveNewAddress = deriveNewAddress
window.deriveNewAddressFromManage = deriveNewAddressFromManage
window.executeDeviceJoin = executeDeviceJoin
window.executeSend = executeSend
window.exportIdentity = exportIdentity
window.exportTransactions = exportTransactions
window.exportWalletMnemonic = exportWalletMnemonic
window.forceSyncNow = forceSyncNow
window.generateNewWallet = generateNewWallet
window.joinDevice = joinDevice
window.joinExistingWallet = joinExistingWallet
window.loadTransactions = loadTransactions
window.refreshAll = refreshAll
window.resetWallet = resetWallet
window.restoreWallet = restoreWallet
window.saveEthereumApiKey = saveEthereumApiKey
window.setMaxAmount = setMaxAmount
window.setNetwork = setNetwork
window.showAddContact = showAddContact
window.showChangePassword = showChangePassword
window.showContacts = showContacts
window.showCreateWallet = showCreateWallet
window.showDevices = showDevices
window.showJoinWallet = showJoinWallet
window.showManageAddresses = showManageAddresses
window.showReceiveForCurrent = showReceiveForCurrent
window.showRestoreWallet = showRestoreWallet
window.showSend = showSend
window.showSendForCurrent = showSendForCurrent
window.showSettings = showSettings
window.selectAsset = selectAsset
window.loadEthereumBalances = loadEthereumBalances
window.showSetupWelcome = showSetupWelcome
window.showWhoAmI = showWhoAmI
window.syncTransactions = syncTransactions
window.verifyBackup = verifyBackup
window.changeBitcoinProvider = changeBitcoinProvider
window.changeDefaultNetwork = changeDefaultNetwork
window.changeEthereumProvider = changeEthereumProvider
window.onWalletAddressChange = onWalletAddressChange
window.switchCurrency = switchCurrency
window.archiveWalletAddress = archiveWalletAddress
window.copyToClipboard = copyToClipboard
window.copyWalletAddress = copyWalletAddress
window.deleteContactConfirm = deleteContactConfirm
window.removeContactAddress = removeContactAddress
window.removeDeviceConfirm = removeDeviceConfirm
window.setWalletAddressCurrent = setWalletAddressCurrent
window.showAddWallet = showAddWallet
window.showTxDetails = showTxDetails

// Password prompt modal functions (replaces blocked prompt())
let passwordPromptResolver = null

function promptForPasswordModal(message = 'Enter your password:') {
  return new Promise((resolve) => {
    const modal = document.getElementById('password-prompt-modal')
    const messageEl = document.getElementById('password-prompt-message')
    const inputEl = document.getElementById('password-prompt-input')

    messageEl.textContent = message
    inputEl.value = ''
    passwordPromptResolver = resolve

    modal.showModal()
    inputEl.focus()

    // Allow Enter key to submit
    inputEl.onkeypress = (e) => {
      if (e.key === 'Enter') {
        submitPasswordPrompt()
      }
    }
  })
}

function submitPasswordPrompt() {
  const modal = document.getElementById('password-prompt-modal')
  const inputEl = document.getElementById('password-prompt-input')
  const password = inputEl.value

  if (passwordPromptResolver) {
    passwordPromptResolver(password)
    passwordPromptResolver = null
  }

  inputEl.value = ''
  modal.close()
}

function closePasswordPrompt() {
  const modal = document.getElementById('password-prompt-modal')
  const inputEl = document.getElementById('password-prompt-input')

  if (passwordPromptResolver) {
    passwordPromptResolver(null)
    passwordPromptResolver = null
  }

  inputEl.value = ''
  modal.close()
}

window.promptForPasswordModal = promptForPasswordModal
window.submitPasswordPrompt = submitPasswordPrompt
window.closePasswordPrompt = closePasswordPrompt

// Mnemonic display modal functions
let currentMnemonic = ''

function showMnemonicDisplay(mnemonic, title = 'Mnemonic') {
  const modal = document.getElementById('mnemonic-display-modal')
  const textEl = document.getElementById('mnemonic-display-text')
  const titleEl = modal.querySelector('h3')

  currentMnemonic = mnemonic
  textEl.textContent = mnemonic
  titleEl.textContent = `🔐 ${title}`

  modal.showModal()
}

function closeMnemonicDisplay() {
  const modal = document.getElementById('mnemonic-display-modal')
  currentMnemonic = ''
  modal.close()
}

async function copyMnemonicToClipboard() {
  try {
    await navigator.clipboard.writeText(currentMnemonic)
    showSuccess('Mnemonic copied to clipboard!')
  } catch (err) {
    showError('Failed to copy to clipboard')
  }
}

window.showMnemonicDisplay = showMnemonicDisplay
window.closeMnemonicDisplay = closeMnemonicDisplay
window.copyMnemonicToClipboard = copyMnemonicToClipboard

// Start when DOM is ready
if (document.readyState === 'loading') {
  document.addEventListener('DOMContentLoaded', init)
} else {
  init()
}