const canvas = document.querySelector('#mazeCanvas');
const ctx = canvas.getContext('2d');
const WALLET_STORAGE_KEY = 'mazexchain.local.wallet';
const EXTERNAL_WALLET_STORAGE_KEY = 'mazexchain.external.wallet';
const EXTENSION_WALLET_STORAGE_KEY = 'mazexchain.extension.wallet';

let current = null;
let chain = null;
let wallet = null;
let externalWallet = null;
let extensionWallet = null;
let lastClick = null;
let lastOutput = null;
let flashStartedAt = 0;
let cachedGridImage = null;
let cachedGridKey = '';
let animationFrame = null;
let samples = [];
let refreshInFlight = null;
let walletCryptoPromise = null;

function gridMetrics() {
  const size = canvas.width;
  const count = current?.grid_size || 20;
  return { size, count, cell: size / count };
}

async function api(path, options = {}) {
  const res = await fetch(path, {
    headers: { 'Content-Type': 'application/json' },
    ...options,
  });
  const data = await res.json();
  if (!res.ok || data.error) throw new Error(data.error || data.message || 'Request failed');
  return data;
}

async function getWalletCrypto() {
  if (window.MazeXWalletCrypto) return window.MazeXWalletCrypto;
  walletCryptoPromise ||= import('/wallet_crypto.mjs?v=native-wallet-relative-imports').then(
    (module) => module.walletCrypto || window.MazeXWalletCrypto,
  );
  const cryptoApi = await walletCryptoPromise;
  if (!cryptoApi) throw new Error('Native wallet crypto module is unavailable.');
  return cryptoApi;
}

function shortHash(value) {
  if (!value) return '--';
  return `${String(value).slice(0, 14)}...${String(value).slice(-10)}`;
}

function escapeHtml(value) {
  return String(value ?? '')
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;');
}

function setText(selector, value) {
  const node = document.querySelector(selector);
  if (node) node.textContent = value;
}

function setWalletLocalStatus(value) {
  setText('#walletLocalStatus', value);
}

function nextFrame() {
  return new Promise((resolve) => requestAnimationFrame(() => resolve()));
}

function mazeXProvider() {
  return window.mazexchain?.isMazeXChain && typeof window.mazexchain.request === 'function'
    ? window.mazexchain
    : null;
}

async function waitForMazeXProvider(timeoutMs = 1200) {
  const provider = mazeXProvider();
  if (provider) return provider;
  return new Promise((resolve, reject) => {
    const timeout = setTimeout(() => {
      window.removeEventListener('mazexchain#initialized', onReady);
      reject(new Error('MazeXChain Wallet extension was not detected. Install or enable the browser extension first.'));
    }, timeoutMs);
    function onReady() {
      clearTimeout(timeout);
      const readyProvider = mazeXProvider();
      if (readyProvider) resolve(readyProvider);
      else reject(new Error('MazeXChain Wallet extension initialized without a provider.'));
    }
    window.addEventListener('mazexchain#initialized', onReady, { once: true });
  });
}

async function requestMazeXWallet(method, params = {}) {
  const provider = await waitForMazeXProvider();
  return provider.request({ method, params });
}

function activeNativeAddress() {
  return extensionWallet?.address || wallet?.address || '';
}

function solverIdentity() {
  return activeNativeAddress() || externalWallet?.address || document.querySelector('#solverId')?.value || 'solver-001';
}

function loadWallet() {
  try {
    wallet = JSON.parse(localStorage.getItem(WALLET_STORAGE_KEY) || 'null');
  } catch {
    wallet = null;
  }
  try {
    externalWallet = JSON.parse(localStorage.getItem(EXTERNAL_WALLET_STORAGE_KEY) || 'null');
  } catch {
    externalWallet = null;
  }
  try {
    extensionWallet = JSON.parse(localStorage.getItem(EXTENSION_WALLET_STORAGE_KEY) || 'null');
  } catch {
    extensionWallet = null;
  }
}

function saveWallet(nextWallet) {
  wallet = nextWallet;
  if (wallet) localStorage.setItem(WALLET_STORAGE_KEY, JSON.stringify(wallet));
  else localStorage.removeItem(WALLET_STORAGE_KEY);
  renderWallet();
}

function saveExternalWallet(nextWallet) {
  externalWallet = nextWallet;
  if (externalWallet) localStorage.setItem(EXTERNAL_WALLET_STORAGE_KEY, JSON.stringify(externalWallet));
  else localStorage.removeItem(EXTERNAL_WALLET_STORAGE_KEY);
  renderWallet();
}

function saveExtensionWallet(nextWallet) {
  extensionWallet = nextWallet;
  if (extensionWallet) localStorage.setItem(EXTENSION_WALLET_STORAGE_KEY, JSON.stringify(extensionWallet));
  else localStorage.removeItem(EXTENSION_WALLET_STORAGE_KEY);
  renderWallet();
}

function activeWalletLabel() {
  if (extensionWallet?.address) return { source: 'MazeX Wallet', address: extensionWallet.address };
  if (wallet?.address) return { source: 'Browser Key', address: wallet.address };
  if (externalWallet?.address) return { source: 'MetaMask Identity', address: externalWallet.address };
  return { source: 'No Wallet', address: '' };
}

function renderWallet() {
  const mnemonicInput = document.querySelector('#walletMnemonic');
  const privateKeyInput = document.querySelector('#walletPrivateKey');
  const solverInput = document.querySelector('#solverId');
  const loginDot = document.querySelector('#loginSummaryDot');
  const loginAddress = document.querySelector('#loginSummaryAddress');
  const walletHint = document.querySelector('#walletConnectHint');
  const extensionLabel = extensionWallet?.address
    ? `MazeX Wallet ${shortHash(extensionWallet.address)}`
    : 'MazeX Wallet extension not connected';
  const metamaskLabel = externalWallet?.address
    ? `MetaMask identity ${shortHash(externalWallet.address)}`
    : 'MetaMask not connected';
  const externalLabel = `${extensionLabel} / ${metamaskLabel}`;
  setText('#externalWalletStatus', externalLabel);
  if (extensionWallet?.address) {
    if (loginDot) {
      loginDot.classList.add('connected');
      loginDot.setAttribute('aria-label', 'Wallet connected');
    }
    if (loginAddress) loginAddress.textContent = shortHash(extensionWallet.address);
    setText('#walletStatus', 'MazeX Wallet connected');
    setText('#walletAddress', extensionWallet.address);
    if (walletHint) walletHint.textContent = 'Extension wallet is active. Candidate answers and transfers will be signed by the extension.';
    if (solverInput) solverInput.value = extensionWallet.address;
    return;
  }
  if (wallet?.address) {
    if (loginDot) {
      loginDot.classList.add('connected');
      loginDot.setAttribute('aria-label', 'Wallet connected');
    }
    if (loginAddress) loginAddress.textContent = shortHash(wallet.address);
    setText('#walletStatus', 'Browser key connected');
    setText('#walletAddress', wallet.address);
    if (walletHint) walletHint.textContent = 'Browser key is active for local testing. For release use, prefer the MazeX Wallet extension.';
    if (solverInput) solverInput.value = wallet.address;
    if (mnemonicInput && wallet.mnemonic && !mnemonicInput.value) mnemonicInput.value = wallet.mnemonic;
    if (privateKeyInput && wallet.private_key && !privateKeyInput.value) privateKeyInput.value = wallet.private_key;
    return;
  }
  if (externalWallet?.address) {
    if (loginDot) {
      loginDot.classList.add('connected');
      loginDot.setAttribute('aria-label', 'External identity connected');
    }
    if (loginAddress) loginAddress.textContent = shortHash(externalWallet.address);
    setText('#walletStatus', 'External identity connected');
    setText('#walletAddress', externalWallet.address);
    if (walletHint) walletHint.textContent = 'MetaMask is connected as an external identity. Native MazeX signing still requires a MazeX wallet.';
    if (solverInput) solverInput.value = externalWallet.address;
    return;
  }
  if (loginDot) {
    loginDot.classList.remove('connected');
    loginDot.setAttribute('aria-label', 'Wallet disconnected');
  }
  if (loginAddress) loginAddress.textContent = 'wallet --';
  setText('#walletStatus', 'Wallet not connected');
  setText('#walletAddress', 'address --');
  if (walletHint) walletHint.textContent = 'Connect a MazeXChain wallet to view MAZEX balance, sign candidate explanations, and transfer funds.';
}

function renderAccount(account = null) {
  const el = document.querySelector('#walletBalances');
  if (!el) return;
  if (!activeWalletLabel().address || !account) {
    el.innerHTML = `
      <div><strong>--</strong><span>confirmed</span></div>
      <div><strong>--</strong><span>pending</span></div>
      <div><strong>--</strong><span>nonce</span></div>
    `;
    return;
  }
  const confirmed = account?.confirmed_balance ?? 0;
  const pending = account?.pending_balance ?? 0;
  const nonce = account?.nonce ?? 0;
  el.innerHTML = `
    <div><strong>${escapeHtml(confirmed)}</strong><span>confirmed</span></div>
    <div><strong>${escapeHtml(pending)}</strong><span>pending</span></div>
    <div><strong>${escapeHtml(nonce)}</strong><span>nonce</span></div>
  `;
}

function formatBytes(bytes) {
  const value = Number(bytes || 0);
  if (!Number.isFinite(value) || value <= 0) return '--';
  const units = ['B', 'KB', 'MB', 'GB'];
  let size = value;
  let unitIndex = 0;
  while (size >= 1024 && unitIndex < units.length - 1) {
    size /= 1024;
    unitIndex += 1;
  }
  return `${size.toFixed(unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`;
}

function setDownloadMeta(manifest) {
  const rows = manifest?.downloads || [];
  const byFile = Object.fromEntries(rows.map((row) => [row.file, row]));
  const targets = [
    ['/downloads/mazexchain-wallet-extension.zip', '#downloadWalletMeta'],
    ['/downloads/mazexchain-node-program.zip', '#downloadProgramMeta'],
    ['/downloads/mazexchain-chain-data.zip', '#downloadChainMeta'],
    ['/downloads/mazexchain-whitepaper-zh.pdf', '#downloadWhitepaperMeta'],
  ];
  for (const [file, selector] of targets) {
    const row = byFile[file];
    setText(selector, row ? `${formatBytes(row.bytes)} / sha256 ${shortHash(row.sha256)}` : 'Package manifest missing');
  }
  setText(
    '#downloadGeneratedAt',
    manifest?.generated_at ? `release ${new Date(manifest.generated_at).toISOString().slice(0, 10)}` : 'release manifest --',
  );
}

async function loadDownloadManifest() {
  try {
    const res = await fetch('/downloads/manifest.json', { cache: 'no-store' });
    if (!res.ok) throw new Error('manifest unavailable');
    setDownloadMeta(await res.json());
  } catch {
    setDownloadMeta(null);
  }
}

async function signWithWallet(payload) {
  if (extensionWallet?.address) {
    const result = await requestMazeXWallet('signPayload', { payload });
    return result.signedPayload;
  }
  if (!wallet?.address) {
    throw new Error('Connect the MazeXChain Wallet extension or create/import a native mazexchain wallet first.');
  }
  const cryptoApi = await getWalletCrypto();
  return cryptoApi.signPayload(wallet, payload);
}

async function signCandidate(candidateStep) {
  const address = activeNativeAddress();
  if (!address) throw new Error('Connect the MazeXChain Wallet extension or create/import a native mazexchain wallet before submitting answers.');
  const signing = await api('/api/base1/signing-payload', {
    method: 'POST',
    body: JSON.stringify({
      address,
      candidateStep,
      nonce: Date.now(),
    }),
  });
  return signWithWallet(signing.payload);
}

async function runSubmitPreflight() {
  const result = await api('/api/base1/preflight', {
    method: 'POST',
    body: JSON.stringify({
      ttl: 2,
      maxPeers: 24,
      timeoutMs: 3000,
    }),
  });
  if (result.changed) {
    await refresh();
    throw new Error('Preflight found a newer valid head and synced this node. Rebuild the answer package for the current Step.');
  }
  if (result.blocked || result.ok === false) {
    throw new Error('Preflight found a stronger head but failed to sync it. Submission is blocked until this node is aligned.');
  }
  return result;
}

async function signTransfer({ to, amount }) {
  const address = activeNativeAddress();
  if (!address) throw new Error('Connect the MazeXChain Wallet extension or create/import a native mazexchain wallet first.');
  const signing = await api('/api/base1/transfer-signing-payload', {
    method: 'POST',
    body: JSON.stringify({
      from: address,
      to,
      amount,
      fee: 1,
    }),
  });
  return signWithWallet(signing.payload);
}

function roundRect(x, y, width, height, radius) {
  ctx.beginPath();
  ctx.moveTo(x + radius, y);
  ctx.arcTo(x + width, y, x + width, y + height, radius);
  ctx.arcTo(x + width, y + height, x, y + height, radius);
  ctx.arcTo(x, y + height, x, y, radius);
  ctx.arcTo(x, y, x + width, y, radius);
  ctx.closePath();
}

function drawKeyMatrix(size, cell, count) {
  const keyGap = Math.max(0.6, cell * 0.08);
  const keyRadius = Math.max(1.2, cell * 0.22);
  ctx.save();
  for (let y = 0; y < count; y += 1) {
    for (let x = 0; x < count; x += 1) {
      const px = x * cell + keyGap;
      const py = y * cell + keyGap;
      const w = Math.max(1, cell - keyGap * 2);
      const h = Math.max(1, cell - keyGap * 2);
      ctx.fillStyle = (x + y) % 2 === 0 ? '#ffffff' : '#f6f6f6';
      ctx.shadowColor = 'rgba(0, 0, 0, 0.08)';
      ctx.shadowBlur = Math.max(0.5, cell * 0.08);
      ctx.shadowOffsetY = Math.max(0.4, cell * 0.04);
      roundRect(px, py, w, h, keyRadius);
      ctx.fill();
      ctx.shadowBlur = 0;
      ctx.shadowOffsetY = 0;
      ctx.strokeStyle = 'rgba(0, 0, 0, 0.16)';
      ctx.lineWidth = Math.max(0.2, cell * 0.035);
      roundRect(px, py, w, h, keyRadius);
      ctx.stroke();
    }
  }

  ctx.strokeStyle = 'rgba(0, 0, 0, 0.28)';
  ctx.lineWidth = 1.2;
  const major = count >= 80 ? 10 : 5;
  for (let i = 0; i <= count; i += major) {
    const p = i * cell;
    ctx.beginPath();
    ctx.moveTo(p, 0);
    ctx.lineTo(p, size);
    ctx.stroke();
    ctx.beginPath();
    ctx.moveTo(0, p);
    ctx.lineTo(size, p);
    ctx.stroke();
  }
  ctx.restore();
}

function drawKeyState(x, y, color, kind) {
  const { cell } = gridMetrics();
  const age = Math.min(1, (performance.now() - flashStartedAt) / 560);
  const pulse = 1 - age;
  const px = x * cell;
  const py = y * cell;
  const pad = Math.max(0.7, cell * 0.08);
  const radius = Math.max(1.4, cell * 0.22);
  const w = Math.max(1, cell - pad * 2);
  const h = Math.max(1, cell - pad * 2);

  ctx.save();
  ctx.shadowColor = kind === 'lit' ? 'rgba(0, 0, 0, 0.22)' : 'rgba(0, 0, 0, 0.28)';
  ctx.shadowBlur = kind === 'lit' ? 18 + pulse * 32 : 10;
  ctx.shadowOffsetY = kind === 'pressed' ? 1.2 : 0;
  ctx.fillStyle = kind === 'pressed' ? 'rgba(0, 0, 0, 0.96)' : 'rgba(255, 255, 255, 0.98)';
  roundRect(px + pad, py + pad, w, h, radius);
  ctx.fill();
  ctx.strokeStyle = kind === 'pressed' ? 'rgba(255, 255, 255, 0.96)' : 'rgba(0, 0, 0, 0.96)';
  ctx.lineWidth = Math.max(1, cell * 0.1);
  roundRect(px + pad + 0.8, py + pad + 0.8, Math.max(1, w - 1.6), Math.max(1, h - 1.6), radius);
  ctx.stroke();

  if (pulse > 0.02) {
    ctx.globalAlpha = kind === 'lit' ? 0.18 * pulse : 0.12 * pulse;
    const halo = cell * (1.7 + 2.4 * pulse);
    ctx.fillStyle = 'rgba(0, 0, 0, 0.08)';
    roundRect(px + cell / 2 - halo / 2, py + cell / 2 - halo / 2, halo, halo, halo / 2);
    ctx.fill();
    if (!animationFrame) {
      animationFrame = requestAnimationFrame(() => {
        animationFrame = null;
        drawGrid();
      });
    }
  }
  ctx.restore();
}

function drawGrid() {
  const { size, cell, count } = gridMetrics();
  const cacheKey = `${size}:${count}`;
  if (!cachedGridImage || cachedGridKey !== cacheKey) {
    ctx.clearRect(0, 0, size, size);
    const gradient = ctx.createLinearGradient(0, 0, size, size);
    gradient.addColorStop(0, '#fff');
    gradient.addColorStop(0.52, '#f8f8f8');
    gradient.addColorStop(1, '#fff');
    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, size, size);
    drawKeyMatrix(size, cell, count);
    cachedGridImage = ctx.getImageData(0, 0, size, size);
    cachedGridKey = cacheKey;
  } else {
    ctx.putImageData(cachedGridImage, 0, 0);
  }
  if (lastClick) drawKeyState(lastClick.x, lastClick.y, '#000', 'pressed');
  if (lastOutput) drawKeyState(lastOutput.u, lastOutput.v, '#000', 'lit');
}

function renderVerifierInfo() {
  const el = document.querySelector('#verifierInfo');
  if (!el || !current?.verifier) return;
  const rows = [
    ['version', current.verifier.version],
    ['mode', current.verifier.point_mode],
    ['points', current.verifier.point_count],
    ['point_hash', shortHash(current.verifier.point_set_hash)],
    ['context_hash', shortHash(current.verifier.context_set_hash)],
    ['behavior_root', shortHash(current.behavior_root)],
    ['behavior_commitment', shortHash(current.behavior_commitment_hash)],
    ['stages', current.verifier.stages.join(' -> ')],
  ];
  el.innerHTML = rows
    .map(
      ([key, value]) =>
        `<div class="validator-item"><strong>${escapeHtml(key)}</strong><span title="${escapeHtml(value)}">${escapeHtml(value)}</span></div>`,
    )
    .join('');
}

function renderSampleLog() {
  const el = document.querySelector('#sampleLog');
  if (!samples.length) {
    el.innerHTML = '<div class="sample-pill">No samples yet. Click the grid to record A(x,y) -> B(u,v).</div>';
    return;
  }
  el.innerHTML = samples
    .slice(0, 16)
    .map(
      (item) =>
        `<div class="sample-pill">A(${escapeHtml(item.x)}, ${escapeHtml(item.y)}) -> B(${escapeHtml(item.u)}, ${escapeHtml(item.v)}) <span>${escapeHtml(shortHash(item.context_hash))}</span></div>`,
    )
    .join('');
}

async function loadLeaderboard() {
  const data = await api('/api/base1/leaderboard');
  const el = document.querySelector('#leaderboard');
  if (!data.leaderboard.length) {
    el.innerHTML = '<div class="row"><strong>No Solvers yet</strong><small>The first accepted explanation will appear here.</small></div>';
    return;
  }
  el.innerHTML = data.leaderboard
    .slice(0, 20)
    .map(
      (row, index) => `
        <div class="row">
          <strong>#${index + 1} ${escapeHtml(row.solver)}</strong>
          <small>score ${escapeHtml(row.total_score)} / solves ${escapeHtml(row.solves)} / rewards ${escapeHtml(row.total_rewards)}</small>
          <small>highest L${escapeHtml(row.highest_layer)} S${escapeHtml(row.highest_step)}</small>
        </div>`,
    )
    .join('');
}

async function loadEvents() {
  const data = await api('/api/base1/events');
  const el = document.querySelector('#events');
  if (!data.events.length) {
    el.innerHTML = '<div class="row"><strong>No events yet</strong><small>Step commitments, solves, and state transitions will appear here.</small></div>';
    return;
  }
  el.innerHTML = data.events
    .slice(0, 24)
    .map(
      (event) => `
        <div class="row">
          <strong>${escapeHtml(event.type)}</strong>
          <small>L${escapeHtml(event.layer_no ?? '')} S${escapeHtml(event.step_no ?? '')} ${escapeHtml(event.solver || '')}</small>
          <small>${escapeHtml(shortHash(event.behavior_root || event.step_commit || event.answer_hash || event.formula_hash))}</small>
        </div>`,
    )
    .join('');
}

async function loadReveals() {
  const data = await api('/api/base1/reveals');
  const el = document.querySelector('#reveals');
  if (!data.reveals.length) {
    el.innerHTML = '<div class="row"><strong>No public solves yet</strong><small>Accepted answer hashes and behavior hashes will appear here.</small></div>';
    return;
  }
  el.innerHTML = data.reveals
    .slice(0, 20)
    .map(
      (row) => `
        <div class="row">
          <strong>L${escapeHtml(row.layer_no)} / S${escapeHtml(row.step_no)} / ${escapeHtml(row.solver)}</strong>
          <small>answer ${escapeHtml(shortHash(row.answer_hash))}</small>
          <small>behavior ${escapeHtml(shortHash(row.behavior_hash))}</small>
          <small>root ${escapeHtml(shortHash(row.behavior_root))}</small>
          <small>reward ${escapeHtml(row.reward_amount)} / score ${escapeHtml(row.difficulty_score)}</small>
        </div>`,
    )
    .join('');
}

async function loadChainBlocks() {
  const data = await api('/api/base1/chain/blocks?limit=12');
  const el = document.querySelector('#chainBlocks');
  el.innerHTML = data.blocks
    .map(
      (block) => `
        <div class="row">
          <strong>Block #${escapeHtml(block.height)} / ${escapeHtml(block.producer)}</strong>
          <small>${escapeHtml(shortHash(block.block_hash))}</small>
          <small>tx ${escapeHtml(block.tx_count)} / work ${escapeHtml(block.work_score || 0)} / total ${escapeHtml(block.cumulative_work_score || 0)}</small>
          <small>state ${escapeHtml(shortHash(block.state_root))}</small>
        </div>`,
    )
    .join('');
}

async function loadWalletAccount() {
  const address = activeNativeAddress() || document.querySelector('#solverId')?.value || '';
  if (!address) {
    renderAccount(null);
    return;
  }
  const data = await api(`/api/base1/account?address=${encodeURIComponent(address)}`);
  renderAccount(data.account);
}

async function loadRewards() {
  const data = await api('/api/base1/rewards?limit=20');
  const el = document.querySelector('#rewards');
  if (!el) return;
  if (!data.rewards.length) {
    el.innerHTML = '<div class="row"><strong>No rewards yet</strong><small>Accepted solves enter pending balance first.</small></div>';
    return;
  }
  el.innerHTML = data.rewards
    .map(
      (reward) => `
        <div class="row">
          <strong>${escapeHtml(reward.status)} / ${escapeHtml(reward.amount)} MAZEX</strong>
          <small>L${escapeHtml(reward.layer_no)} S${escapeHtml(reward.step_no)} / ${escapeHtml(reward.recipient)}</small>
          <small>confirm ${escapeHtml(reward.confirmations)}/${escapeHtml(reward.confirmations_required)} / work ${escapeHtml(reward.work_after)}/${escapeHtml(reward.work_required_after)}</small>
        </div>`,
    )
    .join('');
}

async function loadNetworkStatus() {
  const [data, witnessData, receiptData, timeData] = await Promise.all([
    api('/api/base1/node/peers'),
    api('/api/base1/witnesses?limit=6'),
    api('/api/base1/solve-receipts?limit=6'),
    api('/api/base1/time'),
  ]);
  const el = document.querySelector('#networkStatus');
  if (!el) return;
  const node = data.node;
  const propagation = data.propagation || {};
  const peers = data.peers || [];
  const messages = data.messages || [];
  const witnesses = witnessData.witnesses || [];
  const receipts = receiptData.receipts || [];
  const time = timeData.time || {};
  el.innerHTML = `
    <div class="row">
      <strong>Node ${escapeHtml(shortHash(node.node_id))}</strong>
      <small>height ${escapeHtml(node.height)} / work ${escapeHtml(node.cumulative_work_score)}</small>
      <small>priority relays ${escapeHtml(propagation.priority_relay_count || 0)} / peers ${escapeHtml(peers.length)}</small>
      <small>${escapeHtml(propagation.policy || 'peer_order_default')} / messages ${escapeHtml(messages.length)} / witnesses ${escapeHtml(witnesses.length)}</small>
      <small>UTC ${escapeHtml(time.observed_utc || '--')} / MazeX bucket ${escapeHtml(time.time_bucket ?? '--')}</small>
    </div>
    ${peers
      .slice(0, 8)
      .map(
        (peer) =>
          `<div class="row"><strong>${peer.priority_relay ? 'Priority Relay' : 'Peer'} / ${escapeHtml(peer.name)}</strong><small>${escapeHtml(peer.url)}</small><small>${escapeHtml(peer.status)} / trust ${escapeHtml(peer.reputation?.trust_score ?? '--')}</small></div>`,
      )
      .join('')}
    ${witnesses
      .slice(0, 4)
      .map(
        (witness) =>
          `<div class="row"><strong>witness ${escapeHtml(witness.witness_type)}</strong><small>${escapeHtml(witness.reason)}</small><small>${escapeHtml(shortHash(witness.head_hash))}</small></div>`,
      )
      .join('')}
    ${receipts
      .slice(0, 4)
      .map(
        (receipt) =>
          `<div class="row"><strong>receipt #${escapeHtml(receipt.receipt_order)} / step #${escapeHtml(receipt.step_order)}</strong><small>${escapeHtml(receipt.status)} / ${escapeHtml(receipt.solver)}</small><small>confirm ${escapeHtml(receipt.confirmation_count || 0)}/${escapeHtml(receipt.required_confirmation_count || 0)} / ${escapeHtml(shortHash(receipt.receipt_hash))}</small></div>`,
      )
      .join('')}
  `;
}

async function refresh() {
  if (refreshInFlight) return refreshInFlight;
  refreshInFlight = refreshCurrent();
  try {
    return await refreshInFlight;
  } finally {
    refreshInFlight = null;
  }
}

async function refreshCurrent() {
  const data = await api('/api/base1/current?sync=1');
  current = data.current || null;
  chain = data.chain || null;
  if (!current) {
    setText('#progress', 'Current Step missing');
    return;
  }

  setText('#progress', `Layer ${current.layer_no} / Step ${current.progress}`);
  setText('#gridSizeTitle', `${current.grid_size} x ${current.grid_size}`);
  setText('#gridSizeChip', `Grid ${current.grid_size}`);
  setText('#scoreChip', `Score ${current.score}`);
  setText('#operatorChip', `Ops ${current.counts?.operator_unit_count ?? '--'}`);
  setText('#parameterChip', `Params ${current.counts?.static_parameter_count ?? '--'} + dyn ${current.counts?.dynamic_parameter_count ?? 0}`);
  setText('#verifyChip', `${current.verifier.point_mode} ${current.verifier.point_count}`);
  setText('#chainChip', `Block ${chain?.height ?? '--'} / Work ${chain?.cumulative_work_score ?? 0} / T ${data.time?.time_bucket ?? '--'}`);
  setText('#sampleCost', 'Sampling is free');
  setText('#submitCost', `Success fee ${current.submit_cost} MAZEX, deducted from reward`);
  setText('#formulaHash', `formula_hash ${shortHash(current.formula_hash)}`);
  setText('#fragmentRoot', `fragment_registry_root ${shortHash(current.fragment_registry_root)}`);
  setText('#fragmentSelection', `fragment_selection_hash ${shortHash(current.fragment_selection_hash)}`);
  setText('#fragmentComposition', `fragment_composition_plan_hash ${shortHash(current.fragment_composition_plan_hash)}`);
  setText('#behaviorRoot', `behavior_root ${shortHash(current.behavior_root)}`);
  setText('#behaviorCommitment', `behavior_commitment ${shortHash(current.behavior_commitment_hash)}`);
  setText('#seedHash', `seed_source_hash ${shortHash(current.seed_source_hash)}`);
  setText('#stepCommit', `step_commit ${shortHash(current.step_commit)}`);
  renderVerifierInfo();
  await Promise.all([
    loadLeaderboard(),
    loadEvents(),
    loadReveals(),
    loadChainBlocks(),
    loadWalletAccount(),
    loadRewards(),
    loadNetworkStatus(),
  ]);
  drawGrid();
}

function downloadSamplesCsv() {
  if (!samples.length) {
    setText('#lastSample', 'No samples are available to download.');
    return;
  }
  const header = ['layer', 'step', 'x', 'y', 'u', 'v', 'context_hash'];
  const rows = samples
    .slice()
    .reverse()
    .map((item) => [item.layer_no, item.step_no, item.x, item.y, item.u, item.v, item.context_hash]);
  const csv = [header, ...rows].map((row) => row.join(',')).join('\n');
  const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `mazex_L${current.layer_no}_S${current.step_no}_samples.csv`;
  document.body.appendChild(a);
  a.click();
  a.remove();
  URL.revokeObjectURL(url);
}

canvas.addEventListener('click', async (event) => {
  if (!current) return;
  const rect = canvas.getBoundingClientRect();
  const count = current.grid_size;
  const x = Math.max(0, Math.min(count - 1, Math.floor(((event.clientX - rect.left) / rect.width) * count)));
  const y = Math.max(0, Math.min(count - 1, Math.floor(((event.clientY - rect.top) / rect.height) * count)));
  try {
    const sample = await api('/api/base1/sample', {
      method: 'POST',
      body: JSON.stringify({ userId: solverIdentity(), x, y }),
    });
    lastClick = { x, y };
    lastOutput = { u: sample.u, v: sample.v };
    flashStartedAt = performance.now();
    samples.unshift(sample);
    renderSampleLog();
    setText('#lastSample', `A(${x}, ${y}) -> B(${sample.u}, ${sample.v}) / context ${shortHash(sample.context_hash)}`);
    drawGrid();
  } catch (error) {
    setText('#lastSample', error.message);
  }
});

document.querySelector('#createWalletBtn').addEventListener('click', async (event) => {
  const button = event.currentTarget;
  try {
    button.disabled = true;
    button.textContent = 'Creating...';
    setWalletLocalStatus('Generating a native wallet locally. This may take a moment because the recovery phrase is derived in the browser.');
    await nextFrame();
    const cryptoApi = await getWalletCrypto();
    const nativeWallet = await cryptoApi.createWallet();
    saveWallet(nativeWallet);
    document.querySelector('#walletMnemonic').value = nativeWallet.mnemonic || '';
    document.querySelector('#walletPrivateKey').value = nativeWallet.private_key || '';
    setWalletLocalStatus(`Native wallet created: ${shortHash(nativeWallet.address)}. Open the recovery drawer to back up the phrase.`);
    setText('#submitResult', 'Native wallet created. Recovery phrase and private key stayed in this browser; only signatures are sent to the server.');
    await refresh();
  } catch (error) {
    setWalletLocalStatus(`Wallet creation failed: ${error.message}`);
    setText('#submitResult', error.message);
  } finally {
    button.disabled = false;
    button.textContent = 'Create Browser Key';
  }
});

document.querySelector('#importWalletBtn').addEventListener('click', async (event) => {
  const button = event.currentTarget;
  try {
    button.disabled = true;
    button.textContent = 'Importing...';
    setWalletLocalStatus('Importing wallet locally and deriving the native MazeXChain address.');
    await nextFrame();
    const cryptoApi = await getWalletCrypto();
    const mnemonic = document.querySelector('#walletMnemonic').value.trim().replace(/\s+/g, ' ');
    const privateKey = document.querySelector('#walletPrivateKey').value.trim();
    if (!mnemonic && !privateKey) throw new Error('Paste a recovery phrase or private key first.');
    const nativeWallet = mnemonic
      ? await cryptoApi.importMnemonic(mnemonic)
      : await cryptoApi.importPrivateKey(privateKey);
    saveWallet(nativeWallet);
    document.querySelector('#walletMnemonic').value = nativeWallet.mnemonic || '';
    document.querySelector('#walletPrivateKey').value = nativeWallet.private_key || '';
    setWalletLocalStatus(`Native wallet imported: ${shortHash(nativeWallet.address)}.`);
    setText('#submitResult', 'Native wallet imported. Future submissions and transfers will be signed locally in this browser.');
    await refresh();
  } catch (error) {
    setWalletLocalStatus(`Wallet import failed: ${error.message}`);
    setText('#submitResult', error.message);
  } finally {
    button.disabled = false;
    button.textContent = 'Import Browser Key';
  }
});

document.querySelector('#connectMetaMaskBtn')?.addEventListener('click', async () => {
  try {
    if (!window.ethereum?.request) {
      throw new Error('MetaMask was not detected in this browser.');
    }
    const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
    const address = accounts?.[0];
    if (!address) throw new Error('MetaMask did not return an account.');
    saveExternalWallet({
      type: 'evm',
      provider: 'metamask',
      address,
      connected_at: new Date().toISOString(),
    });
    setText(
      '#submitResult',
      wallet?.address
        ? 'MetaMask identity connected. Native mazexchain wallet remains the active signing wallet.'
        : 'MetaMask identity connected. Create or import a native mazexchain wallet before submitting answers.',
    );
    await refresh();
  } catch (error) {
    setText('#submitResult', error.message);
  }
});

document.querySelector('#connectMazeXWalletBtn')?.addEventListener('click', async () => {
  try {
    setWalletLocalStatus('Connecting to MazeXChain Wallet extension...');
    const result = await requestMazeXWallet('connect');
    if (!result?.account?.address) throw new Error('MazeXChain Wallet extension did not return an account.');
    saveExtensionWallet(result.account);
    setWalletLocalStatus(`MazeXChain Wallet extension connected: ${shortHash(result.account.address)}.`);
    setText('#submitResult', 'MazeXChain Wallet extension connected. Future submissions and transfers will be signed by the extension.');
    await refresh();
  } catch (error) {
    setWalletLocalStatus(`Extension connection failed: ${error.message}`);
    setText('#submitResult', error.message);
  }
});

document.querySelector('#disconnectWalletBtn').addEventListener('click', () => {
  saveWallet(null);
  saveExtensionWallet(null);
  saveExternalWallet(null);
  document.querySelector('#walletMnemonic').value = '';
  document.querySelector('#walletPrivateKey').value = '';
  document.querySelector('#solverId').value = 'solver-001';
  setWalletLocalStatus('Wallet disconnected. Native key material remains only if you kept a backup outside this browser.');
  setText('#submitResult', 'Wallet disconnected.');
  refresh().catch(() => {});
});

document.querySelector('#copyWalletAddressBtn')?.addEventListener('click', async () => {
  const { address, source } = activeWalletLabel();
  if (!address) {
    setWalletLocalStatus('No wallet address is connected.');
    return;
  }
  await navigator.clipboard.writeText(address);
  setWalletLocalStatus(`${source} address copied: ${shortHash(address)}.`);
});

document.querySelector('#lockWalletBtn')?.addEventListener('click', () => {
  saveExtensionWallet(null);
  saveExternalWallet(null);
  saveWallet(null);
  document.querySelector('#walletMnemonic').value = '';
  document.querySelector('#walletPrivateKey').value = '';
  document.querySelector('#solverId').value = 'solver-001';
  setWalletLocalStatus('Site session cleared. Reconnect the extension or restore a browser key to sign again.');
  setText('#submitResult', 'Wallet session cleared.');
  refresh().catch(() => {});
});

document.querySelector('#batchSampleBtn').addEventListener('click', async () => {
  try {
    const result = await api('/api/base1/sample-batch', {
      method: 'POST',
      body: JSON.stringify({ userId: solverIdentity(), count: 20 }),
    });
    samples = [...result.samples.reverse(), ...samples];
    const newest = samples[0];
    lastClick = { x: newest.x, y: newest.y };
    lastOutput = { u: newest.u, v: newest.v };
    flashStartedAt = performance.now();
    renderSampleLog();
    setText('#lastSample', `Generated ${result.count} free samples. Latest: A(${newest.x}, ${newest.y}) -> B(${newest.u}, ${newest.v})`);
    drawGrid();
  } catch (error) {
    setText('#lastSample', error.message);
  }
});

document.querySelector('#downloadSamplesBtn').addEventListener('click', downloadSamplesCsv);

document.querySelector('#confirmBlockBtn').addEventListener('click', async () => {
  try {
    const result = await api('/api/base1/confirm', {
      method: 'POST',
      body: JSON.stringify({ producer: solverIdentity() }),
    });
    setText(
      '#transferResult',
      `Confirmation block #${result.block.height} appended; confirmed rewards ${result.settlement.confirmed.length}`,
    );
    await refresh();
  } catch (error) {
    setText('#transferResult', error.message);
  }
});

document.querySelector('#transferBtn').addEventListener('click', async () => {
  try {
    const to = document.querySelector('#transferTo').value.trim();
    const amount = Number(document.querySelector('#transferAmount').value || 0);
    const signedPayload = await signTransfer({ to, amount });
    const result = await api('/api/base1/transfer', {
      method: 'POST',
      body: JSON.stringify({ signedPayload }),
    });
    setText(
      '#transferResult',
      result.ok
        ? `Transfer accepted: ${result.transfer.amount} MAZEX, fee ${result.transfer.fee}, block #${result.block.height}`
        : result.message,
    );
    await refresh();
  } catch (error) {
    setText('#transferResult', error.message);
  }
});

document.querySelector('#submitBtn').addEventListener('click', async () => {
  try {
    setText('#submitResult', 'Step 1/5: running preflight and checking the latest chain head...');
    await runSubmitPreflight();
    setText('#submitResult', 'Step 2/5: preflight passed. Refreshing current Step and signing answer package...');
    await refresh();
    const raw = document.querySelector('#candidateJson').value.trim();
    if (!raw) throw new Error('Enter a candidate answer JSON first.');
    const candidateStep = JSON.parse(raw);
    const signedPayload = await signCandidate(candidateStep);
    setText('#submitResult', 'Step 3/5: signature created. Submitting candidate to verifier...');
    const result = await api('/api/base1/submit', {
      method: 'POST',
      body: JSON.stringify({
        solver: activeNativeAddress(),
        candidateStep,
        signedPayload,
      }),
    });
    setText('#submitResult', result.correct ? `Accepted. Reward ${result.reward_amount}; next Step committed.` : result.message);
    if (result.correct) {
      setText(
        '#submitResult',
        `Step 5/5: accepted. receipt #${result.solve_receipt?.receipt_order} / step #${result.solve_receipt?.step_order}, confirm ${result.network_confirmation?.confirmation_count || 0}/${result.network_confirmation?.required_confirmation_count || 0}, fee deducted ${result.submit_fee?.amount || 0} MAZEX, net reward ${result.reward_amount}, next Step committed.`,
      );
      lastClick = null;
      lastOutput = null;
      samples = [];
      renderSampleLog();
      document.querySelector('#candidateJson').value = '';
    }
    await refresh();
  } catch (error) {
    setText('#submitResult', error.message);
  }
});

document.querySelector('#demoSolveBtn').addEventListener('click', async () => {
  try {
    const result = await api('/api/base1/demo-solve', {
      method: 'POST',
      body: JSON.stringify({ solver: solverIdentity() }),
    });
    setText(
      '#submitResult',
      `Demo solve accepted: receipt #${result.solve_receipt?.receipt_order} / step #${result.solve_receipt?.step_order}, confirm ${result.network_confirmation?.confirmation_count || 0}/${result.network_confirmation?.required_confirmation_count || 0}, reward ${result.reward_amount}.`,
    );
    lastClick = null;
    lastOutput = null;
    samples = [];
    renderSampleLog();
    await refresh();
  } catch (error) {
    setText('#submitResult', error.message);
  }
});

document.querySelector('#resetBtn')?.addEventListener('click', async () => {
  if (!confirm('Reset the local mazexchain state?')) return;
  await api('/api/base1/reset', { method: 'POST', body: '{}' });
  lastClick = null;
  lastOutput = null;
  samples = [];
  cachedGridImage = null;
  renderSampleLog();
  await refresh();
});

loadWallet();
renderWallet();
refresh().catch((error) => {
  const pre = document.createElement('pre');
  pre.textContent = error.stack || error.message;
  document.body.replaceChildren(pre);
});
setInterval(() => {
  refresh().catch((error) => {
    setText('#submitResult', `Latest block sync failed: ${error.message}`);
  });
}, 5000);
renderSampleLog();
loadDownloadManifest();
