// =============================================================================
// MATCHA SOCIAL — Renderer UI Module
// State machine: intro → login/register → keyDisplay → keyVerify → app
// App sub-views: friends, globalChat, dm, profile
// =============================================================================

const matcha = window.electronAPI?.matcha;
if (!matcha) console.warn('[Matcha] electronAPI.matcha not available');

// Logger that forwards to main process log file
const mlog = {
  log: (...args) => { console.log('[Matcha]', ...args); matcha?.log('info', ...args); },
  warn: (...args) => { console.warn('[Matcha]', ...args); matcha?.log('warn', ...args); },
  error: (...args) => { console.error('[Matcha]', ...args); matcha?.log('error', ...args); }
};

let avatarCacheBust = Date.now();
let avatarDeletedUsers = new Set(); // Track users with deleted avatars
let friendsPollTimer = null;
let requestsCollapsed = false;

let matchaState = {
  view: 'intro',        // intro | login | register | keyDisplay | keyVerify | app
  appTab: 'friends',    // friends | globalChat
  panelOpen: false,
  user: null,
  wsConnected: false,
  // Registration flow
  pendingId: null,
  proofId: null,
  pendingHandle: null,
  // Friends
  friends: [],
  incomingRequests: [],
  outgoingRequests: [],
  // Chat
  globalMessages: [],
  globalCursor: null,
  globalHasMore: true,
  // DM
  dmTarget: null,
  dmMessages: [],
  dmCursor: null,
  dmHasMore: true,
  // Unread
  unreadGlobal: 0,
  unreadDms: {},
  // Reply
  replyTo: null,
  // Loading (separate per chat type)
  globalLoading: false,
  dmLoading: false
};

// =============================================================================
// REACTIVE STATE — setUser triggers UI updates everywhere
// =============================================================================

function setUser(userData) {
  const hadUser = !!matchaState.user?.id;
  matchaState.user = userData;
  mlog.log('setUser:', userData ? `${userData.id} ${userData.handle}` : '(cleared)');
  updateHeaderAvatar();
  // If user data just became available and panel is open, re-render to fix isOwn checks etc.
  if (!hadUser && userData?.id && matchaState.panelOpen) {
    renderCurrentView();
  }
}

// =============================================================================
// PANEL TOGGLE
// =============================================================================

function toggleMatchaPanel() {
  const panel = document.getElementById('matchaPanel');
  const backdrop = document.getElementById('matchaPanelBackdrop');
  if (!panel || !backdrop) return;

  matchaState.panelOpen = !matchaState.panelOpen;

  if (matchaState.panelOpen) {
    panel.classList.add('open');
    backdrop.classList.add('active');
    // Defer heavy content rendering until after the slide-in transition completes.
    // Rendering large DOM (e.g. global chat messages) during CSS transform transition
    // causes layout thrashing in Electron's Chromium, shrinking the main content.
    let rendered = false;
    const onDone = () => {
      if (rendered) return;
      rendered = true;
      panel.removeEventListener('transitionend', onDone);
      renderCurrentView();
    };
    panel.addEventListener('transitionend', onDone);
    // Safety fallback if transitionend doesn't fire (e.g. reduced motion)
    setTimeout(onDone, 350);
  } else {
    panel.classList.remove('open');
    backdrop.classList.remove('active');
  }
}

function closeMatchaPanel() {
  matchaState.panelOpen = false;
  document.getElementById('matchaPanel')?.classList.remove('open');
  document.getElementById('matchaPanelBackdrop')?.classList.remove('active');
}

// =============================================================================
// VIEW ROUTER
// =============================================================================

function renderCurrentView() {
  const body = document.getElementById('matchaPanelBody');
  if (!body) return;

  const header = document.getElementById('matchaPanelHeaderContent');

  switch (matchaState.view) {
    case 'intro':
      renderIntro(body, header);
      break;
    case 'login':
      renderLogin(body, header);
      break;
    case 'register':
      renderRegister(body, header);
      break;
    case 'keyDisplay':
      renderKeyDisplay(body, header);
      break;
    case 'keyVerify':
      renderKeyVerify(body, header);
      break;
    case 'app':
      renderApp(body, header);
      break;
  }
}

function setView(view) {
  matchaState.view = view;
  renderCurrentView();
}

// =============================================================================
// INTRO SCREEN
// =============================================================================

function renderIntro(body, header) {
  header.innerHTML = '<span class="matcha-header-title">Social</span>';
  body.innerHTML = `
    <div class="matcha-intro">
      <div class="matcha-intro-icon">
        <i class="fas fa-users"></i>
      </div>
      <h2 class="matcha-intro-title">Matcha!</h2>
      <p class="matcha-intro-desc">Connect with other Hytale players</p>
      <div class="matcha-intro-features">
        <div class="matcha-feature"><i class="fas fa-user-friends"></i><span>Friends & Presence</span></div>
        <div class="matcha-feature"><i class="fas fa-comments"></i><span>Global Chat</span></div>
        <div class="matcha-feature"><i class="fas fa-envelope"></i><span>Direct Messages</span></div>
      </div>
      <div class="matcha-intro-actions">
        <button class="matcha-btn primary" id="matchaSignInBtn">Sign In</button>
        <button class="matcha-btn ghost" id="matchaCreateBtn">Create Account</button>
      </div>
      <p class="matcha-intro-powered">Powered by <a href="#" class="matcha-powered-link" data-url="https://butterlauncher.tech/">Matcha!</a></p>
    </div>
  `;
  body.querySelector('#matchaSignInBtn').addEventListener('click', () => setView('login'));
  body.querySelector('#matchaCreateBtn').addEventListener('click', () => setView('register'));
  const poweredLink = body.querySelector('.matcha-powered-link');
  if (poweredLink) {
    poweredLink.addEventListener('click', (e) => {
      e.preventDefault();
      window.electronAPI?.openExternal(poweredLink.dataset.url);
    });
  }
}

// =============================================================================
// LOGIN SCREEN
// =============================================================================

function renderLogin(body, header) {
  header.innerHTML = `
    <button class="matcha-back-btn" id="matchaLoginBack"><i class="fas fa-arrow-left"></i></button>
    <span class="matcha-header-title">Sign In</span>
  `;
  body.innerHTML = `
    <div class="matcha-auth-form">
      <div class="matcha-form-group">
        <label>Handle</label>
        <input type="text" class="matcha-input" id="matchaLoginHandle" placeholder="Name#0000" autocomplete="off" />
      </div>
      <div class="matcha-form-group">
        <label>Password</label>
        <input type="password" class="matcha-input" id="matchaLoginPassword" placeholder="Password" autocomplete="current-password" />
      </div>
      <div class="matcha-error" id="matchaLoginError"></div>
      <button class="matcha-btn primary full" id="matchaLoginSubmit">Sign In</button>
      <p class="matcha-auth-link">No account? <a href="#" id="matchaLoginToRegister">Create one</a></p>
    </div>
  `;

  header.querySelector('#matchaLoginBack').addEventListener('click', () => setView('intro'));
  body.querySelector('#matchaLoginToRegister').addEventListener('click', (e) => { e.preventDefault(); setView('register'); });

  const handleInput = body.querySelector('#matchaLoginHandle');
  const passInput = body.querySelector('#matchaLoginPassword');
  const submitBtn = body.querySelector('#matchaLoginSubmit');
  const errorEl = body.querySelector('#matchaLoginError');

  async function doLogin() {
    const handle = handleInput.value.trim();
    const password = passInput.value;
    if (!handle || !password) { errorEl.textContent = 'Please fill in all fields'; return; }
    submitBtn.disabled = true;
    submitBtn.textContent = 'Signing in...';
    errorEl.textContent = '';
    try {
      const res = await matcha.login(handle, password);
      mlog.log('Login result:', res.ok ? 'success' : 'failed', res.ok ? '' : res.error);
      if (res.ok) {
        setUser(res.data.user || null);
        setView('app');
        refreshAppData();
      } else {
        errorEl.textContent = res.error || 'Login failed';
        submitBtn.disabled = false;
        submitBtn.textContent = 'Sign In';
      }
    } catch (err) {
      mlog.error('Login error:', err?.message || err);
      errorEl.textContent = 'Connection error. Please try again.';
      submitBtn.disabled = false;
      submitBtn.textContent = 'Sign In';
    }
  }

  submitBtn.addEventListener('click', doLogin);
  passInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') doLogin(); });
  handleInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') passInput.focus(); });
  setTimeout(() => handleInput.focus(), 100);
}

// =============================================================================
// REGISTER SCREEN
// =============================================================================

function renderRegister(body, header) {
  header.innerHTML = `
    <button class="matcha-back-btn" id="matchaRegBack"><i class="fas fa-arrow-left"></i></button>
    <span class="matcha-header-title">Create Account</span>
  `;
  body.innerHTML = `
    <div class="matcha-auth-form">
      <div class="matcha-form-group">
        <label>Username</label>
        <input type="text" class="matcha-input" id="matchaRegUsername" placeholder="Username" autocomplete="off" />
      </div>
      <div class="matcha-form-group">
        <label>Password</label>
        <input type="password" class="matcha-input" id="matchaRegPassword" placeholder="Password" autocomplete="new-password" />
      </div>
      <div class="matcha-form-group">
        <label>Confirm Password</label>
        <input type="password" class="matcha-input" id="matchaRegConfirm" placeholder="Confirm password" autocomplete="new-password" />
      </div>
      <div class="matcha-error" id="matchaRegError"></div>
      <button class="matcha-btn primary full" id="matchaRegSubmit">Create Account</button>
      <p class="matcha-auth-link">Already have an account? <a href="#" id="matchaRegToLogin">Sign in</a></p>
    </div>
  `;

  header.querySelector('#matchaRegBack').addEventListener('click', () => setView('intro'));
  body.querySelector('#matchaRegToLogin').addEventListener('click', (e) => { e.preventDefault(); setView('login'); });

  const userInput = body.querySelector('#matchaRegUsername');
  const passInput = body.querySelector('#matchaRegPassword');
  const confirmInput = body.querySelector('#matchaRegConfirm');
  const submitBtn = body.querySelector('#matchaRegSubmit');
  const errorEl = body.querySelector('#matchaRegError');

  submitBtn.addEventListener('click', async () => {
    const username = userInput.value.trim();
    const password = passInput.value;
    const password2 = confirmInput.value;
    if (!username || !password || !password2) { errorEl.textContent = 'Please fill in all fields'; return; }

    submitBtn.disabled = true;
    submitBtn.textContent = 'Creating...';
    errorEl.textContent = '';

    try {
      // Send both passwords to server for validation (matches Butter API)
      const res = await matcha.register(username, password, password2);
      mlog.log('Register result:', res.ok ? 'success' : 'failed', res.ok ? JSON.stringify(Object.keys(res.data || {})) : res.error);
      if (res.ok) {
        matchaState.pendingId = res.data.pendingId || null;
        // API may return proofId or masterKey
        matchaState.proofId = res.data.proofId || res.data.masterKey || null;
        matchaState.pendingHandle = res.data.handle || null;
        mlog.log('Registration pending:', matchaState.pendingId, 'handle:', matchaState.pendingHandle, 'hasProof:', !!matchaState.proofId);
        setView('keyDisplay');
      } else {
        errorEl.textContent = res.error || 'Registration failed';
        submitBtn.disabled = false;
        submitBtn.textContent = 'Create Account';
      }
    } catch (err) {
      mlog.error('Register error:', err?.message || err);
      errorEl.textContent = 'Connection error. Please try again.';
      submitBtn.disabled = false;
      submitBtn.textContent = 'Create Account';
    }
  });

  setTimeout(() => userInput.focus(), 100);
}

// =============================================================================
// KEY DISPLAY SCREEN
// =============================================================================

function renderKeyDisplay(body, header) {
  header.innerHTML = '<span class="matcha-header-title">Save Your Key</span>';
  body.innerHTML = `
    <div class="matcha-key-display">
      <div class="matcha-key-warning">
        <i class="fas fa-exclamation-triangle"></i>
        <span>Save this information — it cannot be recovered!</span>
      </div>
      <div class="matcha-key-field">
        <label>Your Handle</label>
        <div class="matcha-key-value">
          <code>${escapeHtml(matchaState.pendingHandle || '')}</code>
          <button class="matcha-copy-btn" data-copy="${escapeAttr(matchaState.pendingHandle || '')}">
            <i class="fas fa-copy"></i>
          </button>
        </div>
      </div>
      <div class="matcha-key-field">
        <label>Master Key</label>
        <div class="matcha-key-value">
          <code class="matcha-master-key">${escapeHtml(matchaState.proofId || '')}</code>
          <button class="matcha-copy-btn" data-copy="${escapeAttr(matchaState.proofId || '')}">
            <i class="fas fa-copy"></i>
          </button>
        </div>
      </div>
      <p class="matcha-key-note">This key is your password recovery method. Without it, you cannot reset your password.</p>
      <button class="matcha-btn primary full" id="matchaKeySaved" disabled>I've Saved My Key</button>
    </div>
  `;

  // Copy buttons
  body.querySelectorAll('.matcha-copy-btn').forEach(btn => {
    btn.addEventListener('click', () => {
      navigator.clipboard.writeText(btn.dataset.copy);
      const icon = btn.querySelector('i');
      icon.className = 'fas fa-check';
      setTimeout(() => { icon.className = 'fas fa-copy'; }, 1500);
    });
  });

  // Enable button after 5s
  const savedBtn = body.querySelector('#matchaKeySaved');
  setTimeout(() => { savedBtn.disabled = false; }, 5000);
  savedBtn.addEventListener('click', () => setView('keyVerify'));
}

// =============================================================================
// KEY VERIFY SCREEN
// =============================================================================

function renderKeyVerify(body, header) {
  header.innerHTML = `
    <button class="matcha-back-btn" id="matchaVerifyBack"><i class="fas fa-arrow-left"></i></button>
    <span class="matcha-header-title">Verify Your Key</span>
  `;
  body.innerHTML = `
    <div class="matcha-auth-form">
      <p class="matcha-verify-desc">Enter your master key to confirm you saved it.</p>
      <div class="matcha-form-group">
        <label>Master Key</label>
        <input type="text" class="matcha-input" id="matchaVerifyKey" placeholder="BM:..." autocomplete="off" />
      </div>
      <div class="matcha-error" id="matchaVerifyError"></div>
      <button class="matcha-btn primary full" id="matchaVerifySubmit">Verify & Continue</button>
    </div>
  `;

  header.querySelector('#matchaVerifyBack').addEventListener('click', () => setView('keyDisplay'));

  const keyInput = body.querySelector('#matchaVerifyKey');
  const submitBtn = body.querySelector('#matchaVerifySubmit');
  const errorEl = body.querySelector('#matchaVerifyError');

  submitBtn.addEventListener('click', async () => {
    const key = keyInput.value.trim();
    if (key !== matchaState.proofId) {
      errorEl.textContent = 'Key does not match. Please try again.';
      return;
    }
    submitBtn.disabled = true;
    submitBtn.textContent = 'Confirming...';
    errorEl.textContent = '';

    const res = await matcha.confirmRegister(matchaState.pendingId, matchaState.proofId);
    mlog.log('Confirm result:', res.ok ? 'success' : 'failed', res.ok ? '' : res.error);
    if (res.ok) {
      // API returns { ok, token, user } — user may be at res.data.user or res.data directly
      const userData = res.data?.user || null;
      if (userData) setUser(userData);
      matchaState.pendingId = null;
      matchaState.proofId = null;
      matchaState.pendingHandle = null;
      setView('app');
      refreshAppData();
    } else {
      errorEl.textContent = res.error || 'Confirmation failed';
      submitBtn.disabled = false;
      submitBtn.textContent = 'Verify & Continue';
    }
  });

  setTimeout(() => keyInput.focus(), 100);
}

// =============================================================================
// APP VIEW (post-login)
// =============================================================================

function renderApp(body, header) {
  // If viewing DM, render DM
  if (matchaState.dmTarget) {
    renderDmChat(body, header);
    return;
  }
  // If viewing profile
  if (matchaState.appTab === 'profile') {
    renderProfile(body, header);
    return;
  }

  // User bar + tabs
  const avUrl = avatarUrl(matchaState.user?.id);

  header.innerHTML = `
    <div class="matcha-user-bar" id="matchaUserBar">
      <div class="matcha-user-avatar-small">
        ${avUrl ? `<img src="${avUrl}" onerror="this.style.display='none'" />` : `<i class="fas fa-user"></i>`}
      </div>
      <span class="matcha-user-handle">${escapeHtml(matchaState.user?.handle || 'User')}</span>
      <button class="matcha-icon-btn" id="matchaProfileBtn" title="Profile"><i class="fas fa-cog"></i></button>
    </div>
  `;

  header.querySelector('#matchaProfileBtn')?.addEventListener('click', () => {
    matchaState.appTab = 'profile';
    renderCurrentView();
  });
  header.querySelector('#matchaUserBar')?.addEventListener('click', (e) => {
    if (e.target.closest('#matchaProfileBtn')) return;
    matchaState.appTab = 'profile';
    renderCurrentView();
  });

  // Tab bar + content
  if (matchaState.appTab === 'globalChat') {
    renderGlobalChat(body);
  } else {
    renderFriends(body);
  }
}

// =============================================================================
// TAB BAR
// =============================================================================

function renderTabBar(container) {
  const globalBadge = matchaState.unreadGlobal > 0
    ? `<span class="matcha-tab-badge">${matchaState.unreadGlobal}</span>` : '';

  const tabBar = document.createElement('div');
  tabBar.className = 'matcha-tab-bar';
  tabBar.innerHTML = `
    <button class="matcha-tab ${matchaState.appTab === 'friends' ? 'active' : ''}" data-tab="friends">Friends</button>
    <button class="matcha-tab ${matchaState.appTab === 'globalChat' ? 'active' : ''}" data-tab="globalChat">Global Chat${globalBadge}</button>
  `;
  tabBar.querySelectorAll('.matcha-tab').forEach(tab => {
    tab.addEventListener('click', () => {
      matchaState.appTab = tab.dataset.tab;
      renderCurrentView();
    });
  });
  container.prepend(tabBar);
}

// =============================================================================
// FRIENDS VIEW
// =============================================================================

function renderFriends(body) {
  body.innerHTML = `
    <div class="matcha-friends-view">
      <div class="matcha-add-friend">
        <input type="text" class="matcha-input" id="matchaAddFriendInput" placeholder="Add friend (Name#0000)" />
        <button class="matcha-icon-btn primary" id="matchaAddFriendBtn"><i class="fas fa-plus"></i></button>
      </div>
      <div id="matchaFriendRequests"></div>
      <div id="matchaFriendsList">
        <div class="matcha-loading"><i class="fas fa-spinner fa-spin"></i> Loading friends...</div>
      </div>
    </div>
  `;
  renderTabBar(body);

  const addInput = body.querySelector('#matchaAddFriendInput');
  const addBtn = body.querySelector('#matchaAddFriendBtn');

  async function addFriend() {
    const handle = addInput.value.trim();
    if (!handle) return;
    addBtn.disabled = true;
    const origIcon = addBtn.innerHTML;
    addBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
    const res = await matcha.friendRequest(handle);
    addBtn.disabled = false;
    addBtn.innerHTML = origIcon;
    if (res.ok) {
      addInput.value = '';
      showToast('Friend request sent!');
      loadFriends();
    } else {
      showToast(res.error || 'Failed to send request');
    }
  }

  addBtn.addEventListener('click', addFriend);
  addInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') addFriend(); });

  loadFriends();
}

async function loadFriends() {
  try {
  const res = await matcha.getFriends();
  if (!res.ok) { mlog.warn('loadFriends failed:', res.error); return; }

  const data = res.data;
  matchaState.friends = data.friends || [];
  matchaState.incomingRequests = data.incoming || [];
  matchaState.outgoingRequests = data.outgoing || [];
  mlog.log('Friends loaded:', matchaState.friends.length, 'friends,', matchaState.incomingRequests.length, 'incoming,', matchaState.outgoingRequests.length, 'outgoing');

  renderFriendRequests();
  renderFriendsList();
  updateUnreadBadges();
  } catch (err) {
    mlog.error('loadFriends exception:', err?.message || err);
  }
}

function renderFriendRequests() {
  const container = document.getElementById('matchaFriendRequests');
  if (!container) return;

  const incoming = matchaState.incomingRequests;
  const outgoing = matchaState.outgoingRequests;

  const totalRequests = incoming.length + outgoing.length;
  if (totalRequests === 0) {
    container.innerHTML = '';
    return;
  }

  const chevronIcon = requestsCollapsed ? 'fa-chevron-right' : 'fa-chevron-down';
  let html = `<div class="matcha-requests-section">
    <div class="matcha-requests-toggle${incoming.length > 0 ? ' has-incoming' : ''}" id="matchaRequestsToggle">
      <i class="fas ${chevronIcon}"></i>
      <span>Requests (${totalRequests})</span>
      ${incoming.length > 0 ? `<span class="matcha-requests-badge">${incoming.length} new</span>` : ''}
    </div>`;

  if (!requestsCollapsed) {
    if (incoming.length > 0) {
      html += `<div class="matcha-section-subheader"><i class="fas fa-inbox"></i> Incoming (${incoming.length})</div>`;
      incoming.forEach(req => {
        const avUrl = avatarUrl(req.fromId);
        html += `
          <div class="matcha-friend-row request">
            <div class="matcha-friend-avatar">${avUrl ? `<img src="${avUrl}" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'" /><div class="matcha-avatar-placeholder" style="display:none"><i class="fas fa-user"></i></div>` : `<div class="matcha-avatar-placeholder"><i class="fas fa-user"></i></div>`}</div>
            <span class="matcha-friend-name">${escapeHtml(req.fromHandle || 'Unknown')}</span>
            <div class="matcha-request-actions">
              <button class="matcha-icon-btn success" data-accept="${req.id}" title="Accept"><i class="fas fa-check"></i></button>
              <button class="matcha-icon-btn danger" data-reject="${req.id}" title="Reject"><i class="fas fa-times"></i></button>
            </div>
          </div>`;
      });
    }

    if (outgoing.length > 0) {
      html += `<div class="matcha-section-subheader"><i class="fas fa-paper-plane"></i> Sent (${outgoing.length})</div>`;
      outgoing.forEach(req => {
        const avUrl = avatarUrl(req.toId);
        html += `
          <div class="matcha-friend-row request">
            <div class="matcha-friend-avatar">${avUrl ? `<img src="${avUrl}" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'" /><div class="matcha-avatar-placeholder" style="display:none"><i class="fas fa-user"></i></div>` : `<div class="matcha-avatar-placeholder"><i class="fas fa-user"></i></div>`}</div>
            <span class="matcha-friend-name">${escapeHtml(req.toHandle || 'Unknown')}</span>
            <button class="matcha-icon-btn muted" data-cancel="${req.id}" title="Cancel request"><i class="fas fa-times"></i></button>
          </div>`;
      });
    }
  }

  html += '</div>';
  container.innerHTML = html;

  // Toggle collapse
  container.querySelector('#matchaRequestsToggle')?.addEventListener('click', () => {
    requestsCollapsed = !requestsCollapsed;
    renderFriendRequests();
  });

  container.querySelectorAll('[data-accept]').forEach(btn => {
    btn.addEventListener('click', async () => {
      btn.disabled = true;
      btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
      const res = await matcha.friendAccept(btn.dataset.accept);
      if (res.ok) {
        showToast('Friend request accepted!');
      } else {
        showToast(res.error || 'Failed');
        btn.disabled = false;
        btn.innerHTML = '<i class="fas fa-check"></i>';
      }
      loadFriends();
    });
  });
  container.querySelectorAll('[data-reject]').forEach(btn => {
    btn.addEventListener('click', async () => {
      btn.disabled = true;
      btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
      const res = await matcha.friendReject(btn.dataset.reject);
      if (res.ok) {
        showToast('Request rejected');
      } else {
        showToast(res.error || 'Failed');
        btn.disabled = false;
        btn.innerHTML = '<i class="fas fa-times"></i>';
      }
      loadFriends();
    });
  });
  container.querySelectorAll('[data-cancel]').forEach(btn => {
    btn.addEventListener('click', async () => {
      btn.disabled = true;
      btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
      const res = await matcha.friendCancel(btn.dataset.cancel);
      if (res.ok) {
        showToast('Request cancelled');
      } else {
        showToast(res.error || 'Failed');
        btn.disabled = false;
        btn.innerHTML = '<i class="fas fa-times"></i>';
      }
      loadFriends();
    });
  });
}

function renderFriendsList() {
  const container = document.getElementById('matchaFriendsList');
  if (!container) return;

  const friends = matchaState.friends;
  if (friends.length === 0) {
    container.innerHTML = '<div class="matcha-empty"><i class="fas fa-user-friends"></i><p>No friends yet. Add someone!</p></div>';
    return;
  }

  // Sort: online first, then alphabetical
  const sorted = [...friends].sort((a, b) => {
    const aOnline = a.state !== 'offline' ? 1 : 0;
    const bOnline = b.state !== 'offline' ? 1 : 0;
    if (aOnline !== bOnline) return bOnline - aOnline;
    return (a.handle || '').localeCompare(b.handle || '');
  });

  const onlineCount = sorted.filter(f => f.state !== 'offline').length;

  let html = `<div class="matcha-section-header">Friends \u2014 ${onlineCount} connected (${friends.length} total)</div>`;

  sorted.forEach(f => {
    const presenceClass = f.state === 'offline' ? 'offline' : (f.state === 'in_game' ? 'ingame' : 'online');
    const presenceText = f.state === 'offline' ? 'Offline' : (f.state === 'in_game' ? 'In Game' : 'Online');
    const unread = matchaState.unreadDms[f.id] || 0;
    const unreadBadge = unread > 0 ? `<span class="matcha-unread-dot">${unread}</span>` : '';
    const avUrl = avatarUrl(f.id);

    html += `
      <div class="matcha-friend-row" data-friend-id="${f.id}" data-friend-handle="${escapeAttr(f.handle)}">
        <div class="matcha-friend-avatar">
          ${avUrl ? `<img src="${avUrl}" onerror="this.style.display='none'" />` : `<i class="fas fa-user"></i>`}
          <span class="matcha-presence-dot ${presenceClass}"></span>
        </div>
        <div class="matcha-friend-info">
          <span class="matcha-friend-name">${escapeHtml(f.handle || 'Unknown')}</span>
          <span class="matcha-friend-status">${presenceText}</span>
        </div>
        ${unreadBadge}
        <div class="matcha-friend-actions">
          <button class="matcha-icon-btn" data-dm="${f.id}" data-dm-handle="${escapeAttr(f.handle)}" title="Open DM"><i class="fas fa-comment"></i></button>
          <button class="matcha-icon-btn danger" data-remove="${f.id}" data-remove-handle="${escapeAttr(f.handle)}" title="Remove Friend"><i class="fas fa-user-minus"></i></button>
        </div>
      </div>`;
  });

  container.innerHTML = html;

  // DM button clicks
  container.querySelectorAll('[data-dm]').forEach(btn => {
    btn.addEventListener('click', (e) => {
      e.stopPropagation();
      matchaState.dmTarget = {
        id: btn.dataset.dm,
        handle: btn.dataset.dmHandle
      };
      matchaState.dmMessages = [];
      matchaState.dmCursor = null;
      matchaState.dmHasMore = true;
      renderCurrentView();
    });
  });

  // Remove friend button clicks
  container.querySelectorAll('[data-remove]').forEach(btn => {
    btn.addEventListener('click', async (e) => {
      e.stopPropagation();
      const handle = btn.dataset.removeHandle;
      if (!confirm(`Remove ${handle} from friends?`)) return;
      btn.disabled = true;
      btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i>';
      const res = await matcha.friendRemove(btn.dataset.remove);
      if (res.ok) {
        showToast(`${handle} removed`);
        loadFriends();
      } else {
        showToast(res.error || 'Failed to remove friend');
        btn.disabled = false;
        btn.innerHTML = '<i class="fas fa-user-minus"></i>';
      }
    });
  });

  // Row click also opens DM
  container.querySelectorAll('.matcha-friend-row').forEach(row => {
    row.addEventListener('click', (e) => {
      if (e.target.closest('.matcha-friend-actions')) return;
      matchaState.dmTarget = {
        id: row.dataset.friendId,
        handle: row.dataset.friendHandle
      };
      matchaState.dmMessages = [];
      matchaState.dmCursor = null;
      matchaState.dmHasMore = true;
      renderCurrentView();
    });
  });
}

// =============================================================================
// GLOBAL CHAT
// =============================================================================

function renderGlobalChat(body) {
  body.innerHTML = `
    <div class="matcha-chat-view">
      <div class="matcha-messages" id="matchaGlobalMessages">
        <div class="matcha-loading"><i class="fas fa-spinner fa-spin"></i> Loading messages...</div>
      </div>
      <div class="matcha-reply-bar" id="matchaReplyBar" style="display:none">
        <span id="matchaReplyText"></span>
        <button class="matcha-icon-btn" id="matchaCancelReply"><i class="fas fa-times"></i></button>
      </div>
      <div class="matcha-chat-input">
        <input type="text" class="matcha-input" id="matchaGlobalInput" placeholder="Type a message..." maxlength="500" />
        <button class="matcha-icon-btn primary" id="matchaGlobalSend"><i class="fas fa-paper-plane"></i></button>
      </div>
    </div>
  `;
  renderTabBar(body);

  const input = body.querySelector('#matchaGlobalInput');
  const sendBtn = body.querySelector('#matchaGlobalSend');
  const cancelReply = body.querySelector('#matchaCancelReply');
  const messagesEl = body.querySelector('#matchaGlobalMessages');

  async function sendMsg() {
    const text = input.value.trim();
    if (!text) return;
    sendBtn.disabled = true;

    // Build reply preview for optimistic message
    let replySnippet = null;
    let replyFromHandle = null;
    if (matchaState.replyTo) {
      const repliedMsg = matchaState.globalMessages.find(m => m.id === matchaState.replyTo);
      if (repliedMsg) {
        replySnippet = (repliedMsg.body || '').substring(0, 80);
        replyFromHandle = repliedMsg.fromHandle || 'Unknown';
      }
    }

    // Optimistic render
    const optimisticMsg = {
      id: 'opt_' + Date.now() + '_' + Math.random().toString(36).slice(2, 8),
      fromId: matchaState.user?.id,
      fromHandle: matchaState.user?.handle || 'You',
      fromAvatarHash: matchaState.user?.avatarHash,
      toId: 'global',
      body: text,
      replyToId: matchaState.replyTo,
      replyToSnippet: replySnippet,
      replyToFromHandle: replyFromHandle,
      createdAt: new Date().toISOString(),
      _optimistic: true
    };
    matchaState.globalMessages.push(optimisticMsg);
    appendMessageToList('matchaGlobalMessages', optimisticMsg);

    const res = await matcha.sendMessage('global', text, matchaState.replyTo);
    sendBtn.disabled = false;
    if (res.ok) {
      input.value = '';
      matchaState.replyTo = null;
      body.querySelector('#matchaReplyBar').style.display = 'none';
    } else {
      showToast(res.error || 'Failed to send');
      // Remove optimistic message on failure
      matchaState.globalMessages = matchaState.globalMessages.filter(m => m.id !== optimisticMsg.id);
      const optEl = document.querySelector(`[data-msg-id="${optimisticMsg.id}"]`);
      if (optEl) optEl.remove();
    }
  }

  sendBtn.addEventListener('click', sendMsg);
  input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !sendBtn.disabled) sendMsg(); });
  cancelReply.addEventListener('click', () => {
    matchaState.replyTo = null;
    body.querySelector('#matchaReplyBar').style.display = 'none';
  });

  // Scroll to load more
  messagesEl.addEventListener('scroll', () => {
    if (messagesEl.scrollTop === 0 && matchaState.globalHasMore && !matchaState.globalLoading) {
      loadOlderGlobalMessages();
    }
  });

  // Clear unread
  matchaState.unreadGlobal = 0;
  matcha.clearUnread('global').catch(() => {});
  updateUnreadBadges();

  loadGlobalMessages();
  setTimeout(() => input.focus(), 100);
}

async function loadGlobalMessages() {
  matchaState.globalLoading = true;
  const res = await matcha.getMessages('global');
  matchaState.globalLoading = false;
  if (!res.ok) { mlog.warn('loadGlobalMessages failed:', res.error); return; }
  mlog.log('Global messages loaded:', (res.data.messages || []).length);

  // API returns newest-first, we need oldest-first for display
  const msgs = res.data.messages || [];
  matchaState.globalMessages = sortMessagesOldestFirst(msgs);
  matchaState.globalCursor = res.data.nextCursor || res.data.cursor || null;
  matchaState.globalHasMore = !!(res.data.nextCursor || res.data.cursor);
  renderGlobalMessageList();
  scrollToBottom('matchaGlobalMessages');
}

async function loadOlderGlobalMessages() {
  if (!matchaState.globalCursor || matchaState.globalLoading) return;
  matchaState.globalLoading = true;
  const res = await matcha.getMessages('global', matchaState.globalCursor);
  matchaState.globalLoading = false;
  if (!res.ok) return;

  const older = sortMessagesOldestFirst(res.data.messages || []);
  matchaState.globalMessages = [...older, ...matchaState.globalMessages];
  matchaState.globalCursor = res.data.nextCursor || res.data.cursor || null;
  matchaState.globalHasMore = !!(res.data.nextCursor || res.data.cursor);
  // Preserve scroll position when prepending older messages
  const container = document.getElementById('matchaGlobalMessages');
  const prevScrollHeight = container ? container.scrollHeight : 0;
  renderGlobalMessageList();
  if (container) container.scrollTop = container.scrollHeight - prevScrollHeight;
}

function renderGlobalMessageList() {
  const container = document.getElementById('matchaGlobalMessages');
  if (!container) return;

  if (matchaState.globalMessages.length === 0) {
    container.innerHTML = '<div class="matcha-empty"><p>No messages yet. Say hello!</p></div>';
    return;
  }

  container.innerHTML = matchaState.globalMessages.map(m => renderMessage(m)).join('');
  attachMessageListeners(container, 'global');
}

// =============================================================================
// DM CHAT
// =============================================================================

function renderDmChat(body, header) {
  const target = matchaState.dmTarget;
  // Find friend for presence/avatar info
  const friend = matchaState.friends.find(f => f.id === target.id);
  const presenceClass = friend?.state === 'offline' ? 'offline' : (friend?.state === 'in_game' ? 'ingame' : 'online');
  const presenceText = friend?.state === 'offline' ? 'Offline' : (friend?.state === 'in_game' ? 'In Game' : 'Online');
  const avUrl = avatarUrl(friend?.id);

  header.innerHTML = `
    <button class="matcha-back-btn" id="matchaDmBack"><i class="fas fa-arrow-left"></i></button>
    <div class="matcha-dm-header-info">
      <div class="matcha-dm-header-avatar">
        ${avUrl ? `<img src="${avUrl}" onerror="this.style.display='none'" />` : `<i class="fas fa-user"></i>`}
        <span class="matcha-presence-dot ${presenceClass}"></span>
      </div>
      <div class="matcha-dm-header-text">
        <span class="matcha-header-title">${escapeHtml(target.handle)}</span>
        <span class="matcha-dm-header-status">${presenceText}</span>
      </div>
    </div>
  `;

  body.innerHTML = `
    <div class="matcha-chat-view">
      <div class="matcha-messages" id="matchaDmMessages">
        <div class="matcha-loading"><i class="fas fa-spinner fa-spin"></i> Loading messages...</div>
      </div>
      <div class="matcha-reply-bar" id="matchaDmReplyBar" style="display:none">
        <span id="matchaDmReplyText"></span>
        <button class="matcha-icon-btn" id="matchaDmCancelReply"><i class="fas fa-times"></i></button>
      </div>
      <div class="matcha-chat-input">
        <input type="text" class="matcha-input" id="matchaDmInput" placeholder="Message ${escapeAttr(target.handle)}..." maxlength="500" />
        <button class="matcha-icon-btn primary" id="matchaDmSend"><i class="fas fa-paper-plane"></i></button>
      </div>
    </div>
  `;

  header.querySelector('#matchaDmBack').addEventListener('click', () => {
    matchaState.dmTarget = null;
    matchaState.replyTo = null;
    matchaState.appTab = 'friends';
    renderCurrentView();
  });

  const input = body.querySelector('#matchaDmInput');
  const sendBtn = body.querySelector('#matchaDmSend');
  const cancelReply = body.querySelector('#matchaDmCancelReply');
  const messagesEl = body.querySelector('#matchaDmMessages');

  async function sendDm() {
    const text = input.value.trim();
    if (!text) return;
    sendBtn.disabled = true;

    // Build reply preview for optimistic message
    let replySnippet = null;
    let replyFromHandle = null;
    if (matchaState.replyTo) {
      const repliedMsg = matchaState.dmMessages.find(m => m.id === matchaState.replyTo);
      if (repliedMsg) {
        replySnippet = (repliedMsg.body || '').substring(0, 80);
        replyFromHandle = repliedMsg.fromHandle || 'Unknown';
      }
    }

    // Optimistic render
    const optimisticMsg = {
      id: 'opt_' + Date.now() + '_' + Math.random().toString(36).slice(2, 8),
      fromId: matchaState.user?.id,
      fromHandle: matchaState.user?.handle || 'You',
      fromAvatarHash: matchaState.user?.avatarHash,
      toId: target.id,
      body: text,
      replyToId: matchaState.replyTo,
      replyToSnippet: replySnippet,
      replyToFromHandle: replyFromHandle,
      createdAt: new Date().toISOString(),
      _optimistic: true
    };
    matchaState.dmMessages.push(optimisticMsg);
    appendMessageToList('matchaDmMessages', optimisticMsg);

    const res = await matcha.sendMessage(target.handle, text, matchaState.replyTo);
    sendBtn.disabled = false;
    if (res.ok) {
      input.value = '';
      matchaState.replyTo = null;
      body.querySelector('#matchaDmReplyBar').style.display = 'none';
    } else {
      showToast(res.error || 'Failed to send');
      matchaState.dmMessages = matchaState.dmMessages.filter(m => m.id !== optimisticMsg.id);
      const optEl = document.querySelector(`[data-msg-id="${optimisticMsg.id}"]`);
      if (optEl) optEl.remove();
    }
  }

  sendBtn.addEventListener('click', sendDm);
  input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !sendBtn.disabled) sendDm(); });
  cancelReply.addEventListener('click', () => {
    matchaState.replyTo = null;
    body.querySelector('#matchaDmReplyBar').style.display = 'none';
  });

  messagesEl.addEventListener('scroll', () => {
    if (messagesEl.scrollTop === 0 && matchaState.dmHasMore && !matchaState.dmLoading) {
      loadOlderDmMessages();
    }
  });

  // Clear unread for this conversation
  delete matchaState.unreadDms[target.id];
  matcha.clearUnread(target.id).catch(() => {});
  updateUnreadBadges();

  loadDmMessages();
  setTimeout(() => input.focus(), 100);
}

async function loadDmMessages() {
  if (!matchaState.dmTarget) return;
  matchaState.dmLoading = true;
  const res = await matcha.getMessages(matchaState.dmTarget.id);
  matchaState.dmLoading = false;
  if (!res.ok) return;

  matchaState.dmMessages = sortMessagesOldestFirst(res.data.messages || []);
  matchaState.dmCursor = res.data.nextCursor || res.data.cursor || null;
  matchaState.dmHasMore = !!(res.data.nextCursor || res.data.cursor);
  renderDmMessageList();
  scrollToBottom('matchaDmMessages');
}

async function loadOlderDmMessages() {
  if (!matchaState.dmCursor || matchaState.dmLoading) return;
  matchaState.dmLoading = true;
  const res = await matcha.getMessages(matchaState.dmTarget.id, matchaState.dmCursor);
  matchaState.dmLoading = false;
  if (!res.ok) return;

  const older = sortMessagesOldestFirst(res.data.messages || []);
  matchaState.dmMessages = [...older, ...matchaState.dmMessages];
  matchaState.dmCursor = res.data.nextCursor || res.data.cursor || null;
  matchaState.dmHasMore = !!(res.data.nextCursor || res.data.cursor);
  const container = document.getElementById('matchaDmMessages');
  const prevScrollHeight = container ? container.scrollHeight : 0;
  renderDmMessageList();
  if (container) container.scrollTop = container.scrollHeight - prevScrollHeight;
}

function renderDmMessageList() {
  const container = document.getElementById('matchaDmMessages');
  if (!container) return;

  if (matchaState.dmMessages.length === 0) {
    container.innerHTML = '<div class="matcha-empty"><p>No messages yet. Say hello!</p></div>';
    return;
  }

  container.innerHTML = matchaState.dmMessages.map(m => renderMessage(m)).join('');
  attachMessageListeners(container, 'dm');
}

// =============================================================================
// MESSAGE RENDERING
// =============================================================================

function renderMessage(msg) {
  // Own check: optimistic messages are always own; otherwise compare IDs as strings
  const isOwn = msg._optimistic || (!!msg.fromId && !!matchaState.user?.id && String(msg.fromId) === String(matchaState.user.id));
  const time = formatTime(msg.createdAt);
  const fullTime = new Date(msg.createdAt).toLocaleString();
  // For own messages, fall back to current user ID for avatar (optimistic msgs may lack fromId)
  const avUrl = avatarUrl(msg.fromId || (isOwn ? matchaState.user?.id : null));
  const badgeHtml = msg.fromIsDev ? '<span class="matcha-dev-badge">DEV</span>' : '';
  const replyHtml = msg.replyToSnippet
    ? `<div class="matcha-reply-preview"><i class="fas fa-reply"></i> ${escapeHtml(msg.replyToFromHandle || '')} : ${escapeHtml(msg.replyToSnippet)}</div>`
    : '';

  const clickable = !isOwn && msg.fromId ? ' matcha-clickable-user' : '';
  const avatarHtml = `<div class="matcha-msg-avatar${clickable}" ${msg.fromId ? `data-user-id="${msg.fromId}"` : ''}>${avUrl ? `<img src="${avUrl}" onerror="this.style.display='none';this.nextElementSibling.style.display='block'" /><i class="fas fa-user" style="display:none"></i>` : `<i class="fas fa-user"></i>`}</div>`;

  const actionsHtml = !msg.deleted ? `
    <div class="matcha-msg-actions">
      <button class="matcha-msg-action" data-action="reply" title="Reply"><i class="fas fa-reply"></i></button>
      ${isOwn ? `<button class="matcha-msg-action danger" data-action="delete" title="Delete"><i class="fas fa-trash"></i></button>` : ''}
    </div>` : '';

  if (isOwn) {
    // Own messages: right-aligned, avatar on right
    const ownHandle = msg.fromHandle || matchaState.user?.handle || 'You';
    return `
      <div class="matcha-message own ${msg.deleted ? 'deleted' : ''}" data-msg-id="${msg.id}" data-from-handle="${escapeAttr(msg.fromHandle || '')}">
        ${replyHtml}
        <div class="matcha-message-row">
          ${actionsHtml}
          <div class="matcha-msg-content">
            <div class="matcha-msg-header own-header">
              <span class="matcha-msg-time" title="${fullTime}">${time}</span>
              <span class="matcha-msg-author">${escapeHtml(ownHandle)}</span>
              ${badgeHtml}
            </div>
            <div class="matcha-msg-body">${msg.deleted ? '<em>Message deleted</em>' : linkify(msg.body || '')}</div>
          </div>
          ${avatarHtml}
        </div>
      </div>`;
  }

  // Others' messages: left-aligned, avatar on left
  const authorClickable = msg.fromId ? ` matcha-clickable-user" data-user-id="${msg.fromId}` : '';
  return `
    <div class="matcha-message ${msg.deleted ? 'deleted' : ''}" data-msg-id="${msg.id}" data-from-handle="${escapeAttr(msg.fromHandle || '')}">
      ${replyHtml}
      <div class="matcha-message-row">
        ${avatarHtml}
        <div class="matcha-msg-content">
          <div class="matcha-msg-header">
            <span class="matcha-msg-author${authorClickable}">${escapeHtml(msg.fromHandle || 'Unknown')}</span>
            ${badgeHtml}
            <span class="matcha-msg-time" title="${fullTime}">${time}</span>
          </div>
          <div class="matcha-msg-body">${msg.deleted ? '<em>Message deleted</em>' : linkify(msg.body || '')}</div>
        </div>
        ${actionsHtml}
      </div>
    </div>`;
}

function attachMessageListeners(container, chatType) {
  container.querySelectorAll('.matcha-msg-action').forEach(btn => {
    btn.addEventListener('click', (e) => {
      const msgEl = e.target.closest('.matcha-message');
      const msgId = msgEl.dataset.msgId;
      const action = btn.dataset.action;

      if (action === 'reply') {
        matchaState.replyTo = msgId;
        const replyBarId = chatType === 'dm' ? 'matchaDmReplyBar' : 'matchaReplyBar';
        const replyTextId = chatType === 'dm' ? 'matchaDmReplyText' : 'matchaReplyText';
        const bar = document.getElementById(replyBarId);
        const text = document.getElementById(replyTextId);
        if (bar && text) {
          text.textContent = `Replying to ${msgEl.dataset.fromHandle}`;
          bar.style.display = 'flex';
        }
      } else if (action === 'delete') {
        matcha.deleteMessage(msgId).then(res => {
          if (res.ok) {
            msgEl.classList.add('deleted');
            const bodyEl = msgEl.querySelector('.matcha-msg-body');
            if (bodyEl) bodyEl.innerHTML = '<em>Message deleted</em>';
            const actionsEl = msgEl.querySelector('.matcha-msg-actions');
            if (actionsEl) actionsEl.remove();
          } else {
            showToast(res.error || 'Failed to delete message');
          }
        }).catch(() => showToast('Failed to delete message'));
      }
    });
  });

  // Clickable usernames and avatars → open user profile popup
  container.querySelectorAll('.matcha-clickable-user').forEach(el => {
    el.addEventListener('click', (e) => {
      e.stopPropagation();
      const userId = el.dataset.userId;
      if (userId) openUserProfile(userId);
    });
  });

  // Clickable links → open in external browser
  container.querySelectorAll('.matcha-link').forEach(link => {
    link.addEventListener('click', (e) => {
      e.preventDefault();
      e.stopPropagation();
      const url = link.dataset.url;
      if (url && window.electronAPI?.openExternal) {
        window.electronAPI.openExternal(url);
      }
    });
  });
}

// =============================================================================
// PROFILE VIEW
// =============================================================================

function renderProfile(body, header) {
  header.innerHTML = `
    <button class="matcha-back-btn" id="matchaProfileBack"><i class="fas fa-arrow-left"></i></button>
    <span class="matcha-header-title">Profile</span>
  `;

  const user = matchaState.user || {};
  const avUrl = avatarUrl(user.id);

  body.innerHTML = `
    <div class="matcha-profile-view">
      <div class="matcha-profile-avatar">
        ${avUrl ? `<img src="${avUrl}" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'" /><div class="matcha-avatar-placeholder" style="display:none"><i class="fas fa-user"></i></div>` : `<div class="matcha-avatar-placeholder"><i class="fas fa-user"></i></div>`}
      </div>
      <div class="matcha-profile-info">
        <div class="matcha-profile-field">
          <label>Handle</label>
          <div class="matcha-profile-value">
            <span id="matchaProfileHandle">${escapeHtml(user.handle || 'Loading...')}</span>
            <button class="matcha-copy-btn" data-copy="${escapeAttr(user.handle || '')}"><i class="fas fa-copy"></i></button>
          </div>
        </div>
      </div>
      <div class="matcha-profile-actions">
        <button class="matcha-btn ghost full" id="matchaUploadHytaleAvatar"><i class="fas fa-gamepad"></i> Upload Hytale Avatar</button>
        <button class="matcha-btn ghost full" id="matchaUploadCustomAvatar"><i class="fas fa-image"></i> Upload Custom Avatar</button>
        <button class="matcha-btn ghost danger full" id="matchaDeleteAvatar"><i class="fas fa-trash"></i> Delete Avatar</button>
      </div>
      <div class="matcha-profile-divider"></div>
      <button class="matcha-btn ghost danger full" id="matchaSignOut"><i class="fas fa-sign-out-alt"></i> Sign Out</button>
    </div>
  `;



  header.querySelector('#matchaProfileBack').addEventListener('click', () => {
    matchaState.appTab = 'friends';
    renderCurrentView();
  });

  // Copy
  body.querySelectorAll('.matcha-copy-btn').forEach(btn => {
    btn.addEventListener('click', () => {
      navigator.clipboard.writeText(btn.dataset.copy);
      const icon = btn.querySelector('i');
      icon.className = 'fas fa-check';
      setTimeout(() => { icon.className = 'fas fa-copy'; }, 1500);
    });
  });

  // Avatars
  body.querySelector('#matchaUploadHytaleAvatar')?.addEventListener('click', async function() {
    this.disabled = true;
    this.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Uploading...';
    const res = await matcha.uploadAvatar('hytale');
    if (res.ok) {
      if (matchaState.user?.id) avatarDeletedUsers.delete(String(matchaState.user.id));
      avatarCacheBust = Date.now();
      await refreshProfile();
      renderCurrentView();
      showToast('Avatar updated!');
    } else if (res.error !== 'Cancelled') {
      this.disabled = false;
      this.innerHTML = '<i class="fas fa-gamepad"></i> Upload Hytale Avatar';
      showToast(res.error || 'Upload failed');
    } else {
      this.disabled = false;
      this.innerHTML = '<i class="fas fa-gamepad"></i> Upload Hytale Avatar';
    }
  });

  body.querySelector('#matchaUploadCustomAvatar')?.addEventListener('click', async function() {
    this.disabled = true;
    this.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Uploading...';
    const res = await matcha.uploadAvatar('custom');
    if (res.ok) {
      if (matchaState.user?.id) avatarDeletedUsers.delete(String(matchaState.user.id));
      avatarCacheBust = Date.now();
      await refreshProfile();
      renderCurrentView();
      showToast('Avatar updated!');
    } else if (res.error !== 'Cancelled') {
      this.disabled = false;
      this.innerHTML = '<i class="fas fa-image"></i> Upload Custom Avatar';
      showToast(res.error || 'Upload failed');
    } else {
      this.disabled = false;
      this.innerHTML = '<i class="fas fa-image"></i> Upload Custom Avatar';
    }
  });

  body.querySelector('#matchaDeleteAvatar')?.addEventListener('click', async function() {
    this.disabled = true;
    this.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Deleting...';
    const res = await matcha.deleteAvatar();
    if (res.ok) {
      if (matchaState.user?.id) avatarDeletedUsers.add(String(matchaState.user.id));
      avatarCacheBust = Date.now();
      updateHeaderAvatar();
      renderCurrentView();
      showToast('Avatar deleted');
      refreshProfile();
    } else {
      this.disabled = false;
      this.innerHTML = '<i class="fas fa-trash"></i> Delete Avatar';
      showToast(res.error || 'Failed');
    }
  });

  // Sign out
  body.querySelector('#matchaSignOut').addEventListener('click', async function() {
    this.disabled = true;
    this.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Signing out...';
    await matcha.logout();
    stopFriendsPoll();
    setUser(null);
    matchaState.friends = [];
    matchaState.incomingRequests = [];
    matchaState.outgoingRequests = [];
    matchaState.globalMessages = [];
    matchaState.globalCursor = null;
    matchaState.globalHasMore = true;
    matchaState.dmTarget = null;
    matchaState.dmMessages = [];
    matchaState.dmCursor = null;
    matchaState.dmHasMore = true;
    matchaState.unreadGlobal = 0;
    matchaState.unreadDms = {};
    matchaState.replyTo = null;
    matchaState.appTab = 'friends';
    avatarDeletedUsers.clear();
    updateUnreadBadges();
    updateHeaderAvatar();
    setView('intro');
  });
}

// =============================================================================
// USER PROFILE POPUP
// =============================================================================

async function openUserProfile(userId) {
  if (!userId || !matcha) return;
  // Don't show popup for self — go to own profile view instead
  if (matchaState.user?.id && String(userId) === String(matchaState.user.id)) {
    matchaState.appTab = 'profile';
    renderCurrentView();
    return;
  }

  const overlay = document.getElementById('matchaUserProfileOverlay');
  const body = document.getElementById('matchaUserProfileBody');
  if (!overlay || !body) return;

  body.innerHTML = '<div class="matcha-loading"><i class="fas fa-spinner fa-spin"></i></div>';
  overlay.style.display = 'flex';

  // Close on backdrop click
  overlay.onclick = (e) => {
    if (e.target === overlay) closeUserProfile();
  };

  const res = await matcha.getUser(userId);
  if (!res.ok) {
    body.innerHTML = `<p class="matcha-error-text">${escapeHtml(res.error || 'Failed to load profile')}</p>
      <button class="matcha-btn ghost" id="matchaProfileCloseErr">Close</button>`;
    body.querySelector('#matchaProfileCloseErr')?.addEventListener('click', closeUserProfile);
    return;
  }

  const user = res.data?.user || res.data;
  const avUrl = avatarUrl(user.id);
  const roleBadge = user.role === 'dev'
    ? '<span class="matcha-dev-badge">DEV</span>'
    : (user.role === 'mod' ? '<span class="matcha-mod-badge">MOD</span>' : '');
  const createdDate = user.createdAt ? new Date(user.createdAt).toLocaleString() : 'Unknown';
  const msgCount = user.messagesSentTotal != null ? Number(user.messagesSentTotal).toLocaleString() : '0';

  // Determine friend status
  const isFriend = matchaState.friends.some(f => String(f.id) === String(user.id));
  const pendingOut = matchaState.outgoingRequests.some(r => String(r.toId || '') === String(user.id));
  const pendingIn = matchaState.incomingRequests.some(r => String(r.fromId || '') === String(user.id));
  const pending = pendingOut || pendingIn;

  let actionBtn = '';
  if (isFriend) {
    actionBtn = `<button class="matcha-btn ghost full" disabled>Already friends</button>`;
  } else if (pending) {
    actionBtn = `<button class="matcha-btn ghost full" disabled>Request pending</button>`;
  } else {
    actionBtn = `<button class="matcha-btn primary full" id="matchaProfileSendRequest">Send friend request</button>`;
  }

  body.innerHTML = `
    <div class="matcha-up-header">
      <span class="matcha-up-label">USER PROFILE</span>
      <h3 class="matcha-up-handle">${escapeHtml(user.handle || 'Unknown')} ${roleBadge}</h3>
    </div>
    <div class="matcha-up-main">
      <div class="matcha-up-avatar">
        ${avUrl ? `<img src="${avUrl}" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'" /><div class="matcha-avatar-placeholder" style="display:none"><i class="fas fa-user"></i></div>` : `<div class="matcha-avatar-placeholder"><i class="fas fa-user"></i></div>`}
      </div>
      <div class="matcha-up-stats">
        <div class="matcha-up-stat">
          <span class="matcha-up-stat-label">CREATED</span>
          <span class="matcha-up-stat-value">${createdDate}</span>
        </div>
        <div class="matcha-up-stat">
          <span class="matcha-up-stat-label">TOTAL MESSAGES SENT</span>
          <span class="matcha-up-stat-value">${msgCount}</span>
        </div>
      </div>
    </div>
    <div class="matcha-up-actions">
      ${actionBtn}
      <button class="matcha-btn ghost" id="matchaProfileClose">Close</button>
    </div>
  `;

  body.querySelector('#matchaProfileClose')?.addEventListener('click', closeUserProfile);
  body.querySelector('#matchaProfileSendRequest')?.addEventListener('click', async (e) => {
    const btn = e.target.closest('button');
    btn.disabled = true;
    btn.textContent = 'Sending...';
    const reqRes = await matcha.friendRequest(user.handle);
    if (reqRes.ok) {
      btn.textContent = 'Request sent!';
      loadFriends(); // Refresh to get updated outgoing requests
    } else {
      btn.disabled = false;
      btn.textContent = 'Send friend request';
      showToast(reqRes.error || 'Failed to send request');
    }
  });
}

function closeUserProfile() {
  const overlay = document.getElementById('matchaUserProfileOverlay');
  if (overlay) overlay.style.display = 'none';
}

// =============================================================================
// REFRESH / DATA LOADING
// =============================================================================

async function refreshAppData() {
  await refreshProfile();
  loadFriends();
  loadUnread();
  startFriendsPoll();
}

function startFriendsPoll() {
  stopFriendsPoll();
  friendsPollTimer = setInterval(() => {
    if (matchaState.view === 'app' && matchaState.user) {
      loadFriends();
      loadUnread();
    }
  }, 12000); // 12s like Butter
}

function stopFriendsPoll() {
  if (friendsPollTimer) {
    clearInterval(friendsPollTimer);
    friendsPollTimer = null;
  }
}

let _refreshProfileInFlight = false;
async function refreshProfile() {
  if (_refreshProfileInFlight) return;
  _refreshProfileInFlight = true;
  try {
    const res = await matcha.getMe();
    if (res && res.ok) {
      // res.data is the API response: { ok, user: { id, handle, ... }, system: {...} }
      const userData = res.data?.user || res.data;
      mlog.log('refreshProfile user:', userData?.id, userData?.handle);
      if (userData?.id) setUser(userData);
    } else {
      mlog.warn('refreshProfile failed:', res?.error);
    }
  } catch (err) {
    mlog.error('refreshProfile exception:', err);
  } finally {
    _refreshProfileInFlight = false;
  }
}

async function loadUnread() {
  try {
    const res = await matcha.getUnread();
    if (!res.ok) { mlog.warn('loadUnread failed:', res.error); return; }
    const data = res.data;
    matchaState.unreadGlobal = data.global || 0;
    matchaState.unreadDms = data.dm || data.dms || {};
    mlog.log('Unread loaded: global=' + matchaState.unreadGlobal, 'dms=' + JSON.stringify(matchaState.unreadDms));
    updateUnreadBadges();
  } catch (err) {
    mlog.error('loadUnread exception:', err?.message || err);
  }
}

// =============================================================================
// UNREAD BADGES
// =============================================================================

function updateUnreadBadges() {
  const msgTotal = matchaState.unreadGlobal + Object.values(matchaState.unreadDms).reduce((a, b) => a + b, 0);
  const reqCount = matchaState.incomingRequests.length;
  const total = msgTotal + reqCount;
  const badge = document.getElementById('matchaUnreadBadge');
  if (badge) {
    if (total > 0) {
      badge.textContent = total > 99 ? '99+' : total;
      badge.style.display = 'flex';
      // Yellow tint when friend requests pending, purple otherwise
      badge.className = `matcha-unread-badge${reqCount > 0 ? ' has-requests' : ''}`;
    } else {
      badge.style.display = 'none';
    }
  }
}

// =============================================================================
// HEADER AVATAR
// =============================================================================

function updateHeaderAvatar() {
  const icon = document.getElementById('matchaNavIcon');
  const img = document.getElementById('matchaNavAvatar');
  const dot = document.getElementById('matchaNavStatus');
  if (!icon || !img) return;

  if (matchaState.user?.id) {
    const avSrc = avatarUrl(matchaState.user.id);
    if (avSrc) {
      img.src = avSrc;
      img.style.display = 'block';
      img.onerror = () => { img.style.display = 'none'; icon.style.display = ''; };
      icon.style.display = 'none';
    } else {
      img.style.display = 'none';
      icon.style.display = '';
    }
    if (dot) {
      dot.style.display = '';
      dot.className = `matcha-nav-status ${matchaState.wsConnected ? 'online' : 'offline'}`;
    }
  } else {
    img.style.display = 'none';
    icon.style.display = '';
    if (dot) dot.style.display = 'none';
  }
}

// =============================================================================
// WS EVENT HANDLERS
// =============================================================================

function setupWsListeners() {
  if (!matcha) return;

  matcha.onWsConnected(async (data) => {
    mlog.log('WS connected, user:', data?.user?.handle);
    matchaState.wsConnected = true;
    if (data?.user) setUser(data.user);
    updateHeaderAvatar();
    const banner = document.getElementById('matchaReconnectBanner');
    if (banner) banner.style.display = 'none';
    // Also fetch full profile for complete data
    await refreshProfile();
    loadFriends();
    loadUnread();
    startFriendsPoll();
  });

  matcha.onWsDisconnected(() => {
    mlog.log('WS disconnected');
    matchaState.wsConnected = false;
    stopFriendsPoll();
    updateHeaderAvatar();
    if (matchaState.panelOpen && matchaState.view === 'app') {
      showReconnectBanner();
    }
  });

  matcha.onWsMessage((data) => {
    // WS data has: type:"message", convo:"global"|<convoId>, message:{...}
    const msg = data.message || data;
    const isOwnMessage = !!msg.fromId && String(msg.fromId) === String(matchaState.user?.id || '');
    // Use data.convo (Butter's WS format) as primary routing, fallback to msg fields
    const isGlobal = data.convo === 'global' || msg.toId === 'global' || msg.to === 'global';

    mlog.log('WS msg:', msg.fromHandle, '-> convo:', data.convo, 'toId:', msg.toId, '| own:', isOwnMessage, '| fromId:', msg.fromId, '| userId:', matchaState.user?.id);

    if (isGlobal) {
      // Skip if own message (already rendered optimistically)
      if (isOwnMessage) {
        // Replace optimistic message ID with real one in state
        const optIdx = matchaState.globalMessages.findIndex(m => m._optimistic && m.body === msg.body);
        if (optIdx !== -1) {
          const oldId = matchaState.globalMessages[optIdx].id;
          matchaState.globalMessages[optIdx] = msg;
          // Update DOM element's data-msg-id so reply/delete work
          const optEl = document.querySelector(`[data-msg-id="${oldId}"]`) ||
                        document.querySelector('.matcha-message.own:last-child');
          if (optEl) optEl.setAttribute('data-msg-id', msg.id);
        }
        return;
      }
      // Check for exact duplicate
      if (matchaState.globalMessages.some(m => m.id === msg.id)) return;

      matchaState.globalMessages.push(msg);
      if (matchaState.panelOpen && matchaState.appTab === 'globalChat') {
        appendMessageToList('matchaGlobalMessages', msg);
        matcha.clearUnread('global').catch(() => {});
      } else {
        matchaState.unreadGlobal++;
        updateUnreadBadges();
      }
    } else {
      // DM — use data.convo as conversation identifier, fallback to msg fields
      const otherId = data.convo || (isOwnMessage ? msg.toId : msg.fromId);

      // Skip own messages (already rendered optimistically)
      if (isOwnMessage) {
        const optIdx = matchaState.dmMessages.findIndex(m => m._optimistic && m.body === msg.body);
        if (optIdx !== -1) {
          const oldId = matchaState.dmMessages[optIdx].id;
          matchaState.dmMessages[optIdx] = msg;
          const optEl = document.querySelector(`[data-msg-id="${oldId}"]`);
          if (optEl) optEl.setAttribute('data-msg-id', msg.id);
        }
        return;
      }
      // Check for exact duplicate
      if (matchaState.dmMessages.some(m => m.id === msg.id)) return;

      if (matchaState.dmTarget && matchaState.dmTarget.id === otherId) {
        matchaState.dmMessages.push(msg);
        appendMessageToList('matchaDmMessages', msg);
        matcha.clearUnread(otherId).catch(() => {});
      } else {
        matchaState.unreadDms[otherId] = (matchaState.unreadDms[otherId] || 0) + 1;
        updateUnreadBadges();
      }
    }
  });

  matcha.onMessageDeleted((data) => {
    const msgId = data.messageId || data.id;
    const el = document.querySelector(`.matcha-message[data-msg-id="${msgId}"]`);
    if (el) {
      el.classList.add('deleted');
      const bodyEl = el.querySelector('.matcha-msg-body');
      if (bodyEl) bodyEl.innerHTML = '<em>Message deleted</em>';
      const actionsEl = el.querySelector('.matcha-msg-actions');
      if (actionsEl) actionsEl.remove();
    }
  });

  matcha.onAvatarUpdated((data) => {
    // Clear deleted flag if avatar was updated (by us or someone else)
    if (data?.userId) avatarDeletedUsers.delete(String(data.userId));
    avatarCacheBust = Date.now();
    updateHeaderAvatar();
    if (matchaState.panelOpen) {
      if (matchaState.appTab === 'friends') loadFriends();
      if (matchaState.appTab === 'profile') renderCurrentView();
    }
  });

  matcha.onBanned((data) => {
    stopFriendsPoll();
    setUser(null);
    matchaState.friends = [];
    matchaState.incomingRequests = [];
    matchaState.outgoingRequests = [];
    matchaState.globalMessages = [];
    matchaState.globalCursor = null;
    matchaState.globalHasMore = true;
    matchaState.dmTarget = null;
    matchaState.dmMessages = [];
    matchaState.dmCursor = null;
    matchaState.dmHasMore = true;
    matchaState.unreadGlobal = 0;
    matchaState.unreadDms = {};
    matchaState.replyTo = null;
    matchaState.appTab = 'friends';
    avatarDeletedUsers.clear();
    matchaState.view = 'intro';
    updateUnreadBadges();
    updateHeaderAvatar();
    const reason = data?.reason || 'Your account has been banned.';
    showToast(reason);
    renderCurrentView();
  });

  matcha.onAnnouncement((data) => {
    const msg = data.message || data;
    if (matchaState.panelOpen && matchaState.appTab === 'globalChat') {
      const container = document.getElementById('matchaGlobalMessages');
      if (container) {
        const el = document.createElement('div');
        el.className = 'matcha-announcement';
        el.textContent = typeof msg === 'string' ? msg : (msg.body || 'Announcement');
        container.appendChild(el);
        scrollToBottom('matchaGlobalMessages');
      }
    }
  });

  matcha.onMaxRetries(() => {
    showReconnectBanner(true);
  });
}

function showReconnectBanner(showRetry) {
  let banner = document.getElementById('matchaReconnectBanner');
  if (!banner) {
    banner = document.createElement('div');
    banner.id = 'matchaReconnectBanner';
    banner.className = 'matcha-reconnect-banner';
    const panelBody = document.getElementById('matchaPanelBody');
    if (panelBody) panelBody.prepend(banner);
  }
  banner.style.display = 'flex';
  banner.innerHTML = showRetry
    ? `<span>Connection lost.</span> <button class="matcha-retry-btn" id="matchaRetryBtn">Retry</button>`
    : `<span>Reconnecting...</span>`;

  if (showRetry) {
    banner.querySelector('#matchaRetryBtn')?.addEventListener('click', () => {
      matcha.reconnect();
      banner.innerHTML = '<span>Reconnecting...</span>';
    });
  }
}

function appendMessageToList(containerId, msg) {
  const container = document.getElementById(containerId);
  if (!container) return;
  // Remove empty state
  const empty = container.querySelector('.matcha-empty');
  if (empty) empty.remove();

  const el = document.createElement('div');
  el.innerHTML = renderMessage(msg);
  const msgEl = el.firstElementChild;
  container.appendChild(msgEl);
  // Only attach listeners to the new message element, not the whole container
  attachMessageListeners(msgEl, containerId.includes('Dm') ? 'dm' : 'global');

  // Auto-scroll if near bottom
  const isNearBottom = container.scrollHeight - container.scrollTop - container.clientHeight < 100;
  if (isNearBottom) scrollToBottom(containerId);
}

// =============================================================================
// UTILITIES
// =============================================================================

function sortMessagesOldestFirst(messages) {
  return [...messages].sort((a, b) => {
    const dateA = new Date(a.createdAt || 0).getTime();
    const dateB = new Date(b.createdAt || 0).getTime();
    return dateA - dateB;
  });
}

function avatarUrl(userId) {
  if (!userId) return '';
  if (avatarDeletedUsers.has(String(userId))) return '';
  return `https://butter.lat/api/matcha/avatar/${userId}?v=${avatarCacheBust}`;
}

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

function linkify(rawText) {
  // Find URLs in raw text, escape parts separately so & in URLs isn't broken
  const urlRegex = /https?:\/\/[^\s<]+/gi;
  let result = '';
  let lastIdx = 0;
  let match;
  while ((match = urlRegex.exec(rawText)) !== null) {
    // Escape text before the URL
    result += escapeHtml(rawText.slice(lastIdx, match.index));
    const url = match[0].replace(/[.,;:!?)]+$/, ''); // trim trailing punctuation
    urlRegex.lastIndex = match.index + url.length; // adjust position after trim
    result += `<a href="#" class="matcha-link" data-url="${escapeAttr(url)}">${escapeHtml(url)}</a>`;
    lastIdx = match.index + url.length;
  }
  result += escapeHtml(rawText.slice(lastIdx));
  return result;
}

function escapeAttr(str) {
  return (str || '').replace(/&/g, '&amp;').replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}

function formatTime(dateStr) {
  if (!dateStr) return '';
  const date = new Date(dateStr);
  const now = new Date();
  const diffMs = now - date;
  const diffMins = Math.floor(diffMs / 60000);
  if (diffMins < 1) return 'now';
  if (diffMins < 60) return `${diffMins}m ago`;
  const diffHours = Math.floor(diffMins / 60);
  if (diffHours < 24) return `${diffHours}h ago`;
  return date.toLocaleDateString();
}

function scrollToBottom(containerId) {
  const el = document.getElementById(containerId);
  if (el) el.scrollTop = el.scrollHeight;
}

function showToast(message) {
  let toast = document.getElementById('matchaToast');
  if (!toast) {
    toast = document.createElement('div');
    toast.id = 'matchaToast';
    toast.className = 'matcha-toast';
    document.body.appendChild(toast);
  }
  toast.textContent = message;
  toast.classList.add('show');
  setTimeout(() => toast.classList.remove('show'), 3000);
}

// =============================================================================
// KEYBOARD SHORTCUTS
// =============================================================================

document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape') {
    const profileOverlay = document.getElementById('matchaUserProfileOverlay');
    if (profileOverlay && profileOverlay.style.display !== 'none') {
      closeUserProfile();
      return;
    }
    if (matchaState.panelOpen) closeMatchaPanel();
  }
});

// =============================================================================
// INITIALIZATION
// =============================================================================

async function initMatcha() {
  mlog.log('Initializing...');

  // Wire up sidebar button first (before any async calls)
  const navItem = document.getElementById('matchaNavItem');
  if (navItem) {
    navItem.addEventListener('click', (e) => {
      e.stopPropagation();
      toggleMatchaPanel();
    });
    mlog.log('Nav item listener attached');
  } else {
    mlog.warn('matchaNavItem not found in DOM');
  }

  // Wire up panel close button and backdrop
  const panelCloseBtn = document.getElementById('matchaPanelCloseBtn');
  if (panelCloseBtn) panelCloseBtn.addEventListener('click', closeMatchaPanel);
  const backdrop = document.getElementById('matchaPanelBackdrop');
  if (backdrop) backdrop.addEventListener('click', closeMatchaPanel);

  if (!matcha) {
    mlog.warn('API not available, skipping init');
    return;
  }

  setupWsListeners();
  mlog.log('WS listeners set up');

  // Check auth state on startup
  try {
    const state = await matcha.getAuthState();
    mlog.log('Auth state:', state?.authenticated ? 'authenticated' : 'not authenticated', 'wsConnected:', state?.wsConnected);
    if (state?.authenticated) {
      matchaState.view = 'app';
      matchaState.wsConnected = state.wsConnected;
      if (state.user) setUser(state.user);
      refreshProfile();
      loadFriends();
      loadUnread();
      startFriendsPoll();
    }
  } catch (err) {
    mlog.error('getAuthState failed:', err?.message || err);
  }

  mlog.log('Init complete');
}

// Export for script.js
export { initMatcha };
