const state = {
  sessions: [],
  currentSessionId: null,
  models: [],
  pollingTimer: null,
  cliOptions: ['opencode'],
  currentCli: 'opencode',
  activeStreams: new Map(), // Track active SSE connections
  opencodeStatus: null,
  userId: null,
  accountPlan: 'hobby',
  usageSummary: null,
};

const TOKENS_TO_WORD_RATIO = 0.75;

const el = {
  sessionList: document.getElementById('session-list'),
  chatArea: document.getElementById('chat-area'),
  chatTitle: document.getElementById('chat-title'),
  sessionId: document.getElementById('session-id'),
  sessionModel: document.getElementById('session-model'),
  sessionPending: document.getElementById('session-pending'),
  queueIndicator: document.getElementById('queue-indicator'),
  cliSelect: document.getElementById('cli-select'),
  modelSelect: document.getElementById('model-select'),
  modelIcon: document.getElementById('model-icon'),
  customModelLabel: document.getElementById('custom-model-label'),
  customModelInput: document.getElementById('custom-model-input'),
  newChat: document.getElementById('new-chat'),
  messageInput: document.getElementById('message-input'),
  uploadMediaBtn: document.getElementById('upload-media-btn'),
  uploadMediaInput: document.getElementById('upload-media-input'),
  attachmentPreview: document.getElementById('attachment-preview'),
  sendBtn: document.getElementById('send-btn'),
  statusLine: document.getElementById('status-line'),
  quickButtons: document.querySelectorAll('[data-quick]'),
  gitButtons: document.querySelectorAll('[data-git]'),
  gitOutput: document.getElementById('git-output'),
  diagnosticsButton: document.getElementById('diagnostics-button'),
  commitMessage: document.getElementById('commit-message'),
  githubButton: document.getElementById('github-button'),
  githubModal: document.getElementById('github-modal'),
  githubClose: document.getElementById('github-close'),
  modalCommitMessage: document.getElementById('modal-commit-message'),
};

console.log('DOM elements initialized:', {
  uploadMediaBtn: el.uploadMediaBtn,
  uploadMediaInput: el.uploadMediaInput,
  messageInput: el.messageInput
});

const pendingAttachments = [];

function isPaidPlanClient() {
  const plan = (state.accountPlan || '').toLowerCase();
  return plan === 'business' || plan === 'enterprise';
}

function isEnterprisePlan() {
  const plan = (state.accountPlan || '').toLowerCase();
  return plan === 'enterprise';
}

function bytesToFriendly(bytes) {
  const n = Number(bytes || 0);
  if (!Number.isFinite(n) || n <= 0) return '0 B';
  if (n < 1024) return `${n} B`;
  if (n < 1024 * 1024) return `${(n / 1024).toFixed(1)} KB`;
  return `${(n / (1024 * 1024)).toFixed(2)} MB`;
}

function renderAttachmentPreview() {
  if (!el.attachmentPreview) return;
  if (!pendingAttachments.length) {
    el.attachmentPreview.style.display = 'none';
    el.attachmentPreview.innerHTML = '';
    return;
  }
  el.attachmentPreview.style.display = 'flex';
  el.attachmentPreview.innerHTML = '';
  pendingAttachments.forEach((att, idx) => {
    const chip = document.createElement('div');
    chip.className = 'attachment-chip';
    const img = document.createElement('img');
    img.className = 'attachment-thumb';
    img.alt = att.name || 'image';
    img.src = att.previewUrl || '';
    const meta = document.createElement('div');
    meta.className = 'attachment-meta';
    const name = document.createElement('div');
    name.className = 'name';
    name.textContent = att.name || 'image';
    const size = document.createElement('div');
    size.className = 'size';
    size.textContent = `${att.type || 'image'} • ${bytesToFriendly(att.size || 0)}`;
    meta.appendChild(name);
    meta.appendChild(size);
    const remove = document.createElement('button');
    remove.className = 'attachment-remove';
    remove.type = 'button';
    remove.textContent = 'Remove';
    remove.onclick = () => {
      try {
        const removed = pendingAttachments.splice(idx, 1);
        if (removed[0] && removed[0].previewUrl && removed[0].previewUrl.startsWith('blob:')) {
          URL.revokeObjectURL(removed[0].previewUrl);
        }
      } catch (_) { }
      renderAttachmentPreview();
    };
    chip.appendChild(img);
    chip.appendChild(meta);
    chip.appendChild(remove);
    el.attachmentPreview.appendChild(chip);
  });
}

async function fileToCompressedWebpAttachment(file) {
  // Best-effort client-side compression to reduce JSON payload sizes.
  // Server will also compress on write.
  const maxDim = 1600;
  const quality = 0.8;
  const mime = (file && file.type) ? file.type : 'application/octet-stream';
  if (!file || !mime.startsWith('image/')) throw new Error('Only images are supported');

  let bitmap;
  try {
    bitmap = await createImageBitmap(file);
  } catch (_) {
    // Fallback: no bitmap support
    bitmap = null;
  }
  if (!bitmap) {
    const dataUrl = await new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onerror = () => reject(new Error('Failed to read image'));
      reader.onload = () => resolve(String(reader.result || ''));
      reader.readAsDataURL(file);
    });
    const base64 = dataUrl.split(',')[1] || '';
    return { name: file.name || 'image', type: mime, data: base64, size: Math.floor((base64.length * 3) / 4), previewUrl: dataUrl };
  }

  const scale = Math.min(1, maxDim / Math.max(bitmap.width, bitmap.height));
  const width = Math.max(1, Math.round(bitmap.width * scale));
  const height = Math.max(1, Math.round(bitmap.height * scale));
  const canvas = document.createElement('canvas');
  canvas.width = width;
  canvas.height = height;
  const ctx = canvas.getContext('2d', { alpha: false });
  ctx.drawImage(bitmap, 0, 0, width, height);
  const blob = await new Promise((resolve) => canvas.toBlob(resolve, 'image/webp', quality));
  const outBlob = blob || file;

  const dataUrl = await new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onerror = () => reject(new Error('Failed to read compressed image'));
    reader.onload = () => resolve(String(reader.result || ''));
    reader.readAsDataURL(outBlob);
  });
  const base64 = dataUrl.split(',')[1] || '';
  const previewUrl = URL.createObjectURL(outBlob);
  return { name: file.name || 'image', type: 'image/webp', data: base64, size: outBlob.size || Math.floor((base64.length * 3) / 4), previewUrl };
}

function syncUploadButtonState() {
  if (!el.uploadMediaBtn) return;
  const allowed = isPaidPlanClient();
  el.uploadMediaBtn.style.display = allowed ? 'flex' : 'none';
  el.uploadMediaBtn.title = 'Attach images';
}

function cyrb53(str, seed = 0) {
  let h1 = 0xdeadbeef ^ seed;
  let h2 = 0x41c6ce57 ^ seed;
  for (let i = 0, ch; i < str.length; i++) {
    ch = str.charCodeAt(i);
    h1 = Math.imul(h1 ^ ch, 2654435761);
    h2 = Math.imul(h2 ^ ch, 1597334677);
  }
  h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
  h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
  return 4294967296 * (2097151 & h2) + (h1 >>> 0);
}

function computeAccountId(email) {
  const normalized = (email || '').trim().toLowerCase();
  if (!normalized) return '';
  const hash = cyrb53(normalized);
  return `acct-${hash.toString(16)}`;
}

function getDeviceUserId() {
  try {
    const existing = localStorage.getItem('shopify_ai_user_id');
    if (existing) return existing;
    const uuidPart = crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2, 12);
    const generated = `user-${uuidPart}`;
    localStorage.setItem('shopify_ai_user_id', generated);
    return generated;
  } catch (_) {
    const fallbackPart = (typeof crypto !== 'undefined' && crypto.randomUUID)
      ? crypto.randomUUID()
      : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 12)}`;
    return `user-${fallbackPart}`;
  }
}

function resolveUserId() {
  try {
    // Check both shopify and wordpress keys for compatibility
    const keys = ['shopify_ai_user', 'wordpress_plugin_ai_user'];
    for (const key of keys) {
      const stored = localStorage.getItem(key);
      if (stored) {
        const parsed = JSON.parse(stored);
        if (parsed && parsed.email) {
          const accountId = parsed.accountId || computeAccountId(parsed.email);
          if (accountId && (!parsed.accountId || parsed.accountId !== accountId)) {
            try { localStorage.setItem(key, JSON.stringify({ ...parsed, accountId })); } catch (_) { }
          }
          return accountId;
        }
      }
    }
  } catch (_) { /* ignore */ }
  return '';
}

state.userId = resolveUserId();
if (!state.userId) {
  const next = encodeURIComponent(window.location.pathname + window.location.search);
  window.location.href = `/login?next=${next}`;
}
try {
  document.cookie = `chat_user=${encodeURIComponent(state.userId)}; path=/; SameSite=Lax`;
} catch (_) { /* ignore */ }

async function checkAuthAndLoadUser() {
  try {
    // Check if we have a valid session with the server
    const res = await fetch('/api/me');
    if (res.ok) {
      const data = await res.json();
      if (data.ok && data.user) {
        // Update local state with server user info
        state.userId = data.user.id;
        return data.user;
      }
    }
  } catch (_) {
    // Server auth not available, continue with device auth
  }
  return null;
}

function isFreePlan() {
  return (state.accountPlan || '').toLowerCase() === 'hobby';
}

function tokensToFriendly(limit) {
  const usage = Math.round(Math.max(0, limit || 0) * TOKENS_TO_WORD_RATIO);
  if (!usage) return '—';
  if (usage < 10_000) return `≈ ${usage.toLocaleString()} usage`;
  return `≈ ${(usage / 1000).toFixed(1)}k usage`;
}
window.TOKENS_TO_WORD_RATIO = TOKENS_TO_WORD_RATIO;
window.tokensToFriendly = tokensToFriendly;

function ensureUsageFooter() {
  let footer = document.getElementById('usage-footer');
  if (!footer) {
    footer = document.createElement('div');
    footer.id = 'usage-footer';
    footer.style.position = 'fixed';
    footer.style.left = '0';
    footer.style.right = '0';
    footer.style.bottom = '0';
    footer.style.zIndex = '9998';
    footer.style.background = '#0f172a';
    footer.style.color = '#f8fafc';
    footer.style.padding = '10px 14px';
    footer.style.boxShadow = '0 -8px 30px rgba(0,0,0,0.2)';
    footer.style.fontSize = '13px';
    footer.style.display = 'none';
    document.body.appendChild(footer);
  }
  return footer;
}
window.ensureUsageFooter = window.ensureUsageFooter || ensureUsageFooter;

function renderUsageFooter(summary) {
  const footer = window.ensureUsageFooter ? window.ensureUsageFooter() : ensureUsageFooter();

  if (!summary) {
    footer.style.display = 'none';
    return;
  }

  // Handle both simple format (used by builder.js) and tiered format (used by app.js)
  const isSimpleFormat = !summary.tiers;
  const isTieredFormat = summary.tiers && Object.keys(summary.tiers).length > 0;

  if (isSimpleFormat) {
    footer.style.display = 'none';
    return;
  }

  // Tiered format: { tiers: { free: {...}, plus: {...}, pro: {...} }, plan }
  const applicable = Object.entries(summary.tiers).filter(([, data]) => (data.limit || 0) > 0);
  if (!applicable.length) {
    footer.style.display = 'none';
    return;
  }
  const tierMeta = {
    free: { label: '1x models', color: '#34d399', blurb: 'Standard burn (1x)', multiplier: 1 },
    plus: { label: '2x models', color: '#38bdf8', blurb: 'Advanced burn (2x)', multiplier: 2 },
    pro: { label: '3x models', color: '#22d3ee', blurb: 'Premium burn (3x)', multiplier: 3 },
  };
  const upgradeCta = summary.plan === 'enterprise' ? '' : `<a href="/upgrade" style="color:#a5f3fc; font-weight:700; text-decoration:underline;">Upgrade</a>`;
  footer.innerHTML = `
    <div style="display:flex; gap:10px; align-items:flex-start; flex-wrap:wrap; justify-content:space-between;">
      <div style="display:flex; gap:10px; flex-wrap:wrap; align-items:center;">
        ${applicable.map(([tier, data]) => {
    const remainingRatio = data.limit > 0 ? (data.remaining / data.limit) : 0;
    const nearOut = remainingRatio <= 0.1;
    const meta = tierMeta[tier] || tierMeta.free;
    const burnMultiplier = data.multiplier || meta.multiplier;
    return `
            <div style="min-width:220px; background:#0b1221; border:1px solid rgba(255,255,255,0.08); border-radius:10px; padding:10px;">
              <div style="display:flex; justify-content:space-between; align-items:center; gap:6px;">
                <strong>${meta.label}</strong>
                <span style="color:${nearOut ? '#fcd34d' : '#cbd5e1'};">${data.used.toLocaleString()} / ${data.limit.toLocaleString()} • ${burnMultiplier}x</span>
              </div>
              <div style="height:8px; background:rgba(255,255,255,0.07); border-radius:999px; overflow:hidden; margin:8px 0 4px;">
                <div style="width:${data.percent}%; height:100%; background:${meta.color};"></div>
              </div>
              <div style="display:flex; justify-content:space-between; align-items:center; color:#cbd5e1; gap:6px;">
                <span>${tokensToFriendly(data.limit)} left at ${burnMultiplier}x: ${data.remaining.toLocaleString()}${nearOut ? ' • almost out' : ''}</span>
                <button data-boost="${tier}" style="background:transparent; border:1px solid rgba(255,255,255,0.25); color:#f8fafc; border-radius:8px; padding:4px 8px; cursor:pointer;">Add boost</button>
              </div>
              <div style="color:#9ca3af; font-size:11px; margin-top:4px;">${meta.blurb}</div>
            </div>
          `;
  }).join('')}
      </div>
      <div style="display:flex; align-items:center; gap:10px; color:#cbd5e1; flex:1; min-width:200px; justify-content:flex-end;">
        <span>${upgradeCta ? 'Need more runway?' : 'You are on the top tier.'} ${upgradeCta || ''}</span>
      </div>
    </div>
  `;
  footer.style.display = 'block';
  footer.querySelectorAll('[data-boost]').forEach((btn) => {
    btn.onclick = async () => {
      btn.disabled = true;
      btn.textContent = 'Adding...';
      try {
        await buyBoost(btn.dataset.boost);
      } catch (err) {
        alert(err.message || 'Unable to add boost');
      } finally {
        btn.disabled = false;
        btn.textContent = 'Add boost';
      }
    };
  });
}
window.renderUsageFooter = window.renderUsageFooter || renderUsageFooter;

async function loadUsageSummary() {
  try {
    const data = await api('/api/account/usage');
    if (data?.summary) {
      state.usageSummary = data.summary;
      renderUsageFooter(state.usageSummary);
    }
  } catch (_) {
    // Ignore silently
  }
}

async function buyBoost(tier) {
  const payload = tier ? { tier } : {};
  const res = await api('/api/account/boost', { method: 'POST', body: JSON.stringify(payload) });
  if (res?.summary) {
    state.usageSummary = res.summary;
    renderUsageFooter(state.usageSummary);
    setStatus('Added extra AI energy to your account');
  }
}

async function loadAccountPlan() {
  try {
    // Start fetching account info but avoid blocking UI indefinitely
    const accountPromise = api('/api/account');

    const timeoutMs = 3000;
    const timeoutPromise = new Promise((resolve) => setTimeout(() => resolve(null), timeoutMs));
    const data = await Promise.race([accountPromise, timeoutPromise]);

    if (!data) {
      // Timed out — keep default plan visible and continue fetching in background
      state.accountPlan = state.accountPlan || 'hobby';

      accountPromise.then((fullData) => {
        if (fullData?.account?.plan) state.accountPlan = fullData.account.plan;
        if (fullData?.account?.tokenUsage) {
          state.usageSummary = fullData.account.tokenUsage;
          renderUsageFooter(state.usageSummary);
        } else {
          loadUsageSummary().catch(() => {});
        }
      }).catch((err) => {
        setStatus('Account fetch failed');
        console.warn('loadAccountPlan background fetch failed:', err);
      });

      return;
    }

    if (data?.account?.plan) {
      state.accountPlan = data.account.plan;
      if (data.account.tokenUsage) {
        state.usageSummary = data.account.tokenUsage;
        renderUsageFooter(state.usageSummary);
      } else {
        await loadUsageSummary();
      }
    } else {
      if (window.location.pathname === "/apps" || window.location.pathname === "/apps/") {
        window.location.href = "/select-plan";
      }
    }
  } catch (err) {
    // Ignore failures but log for debugging
    console.warn('loadAccountPlan failed:', err);
  }
}

let modelPreviewOverlay = null;
let modelPreviewAttached = false;
function showBlurredModelPreview() {
  if (!isFreePlan()) return;
  if (modelPreviewOverlay) {
    try { modelPreviewOverlay.remove(); } catch (_) { }
  }
  modelPreviewOverlay = document.createElement('div');
  modelPreviewOverlay.style.position = 'fixed';
  modelPreviewOverlay.style.inset = '0';
  modelPreviewOverlay.style.background = 'rgba(0,0,0,0.35)';
  modelPreviewOverlay.style.display = 'flex';
  modelPreviewOverlay.style.alignItems = 'center';
  modelPreviewOverlay.style.justifyContent = 'center';
  modelPreviewOverlay.style.zIndex = '9999';

  const panel = document.createElement('div');
  panel.style.background = '#fff';
  panel.style.borderRadius = '12px';
  panel.style.padding = '20px';
  panel.style.maxWidth = '420px';
  panel.style.width = '90%';
  panel.style.boxShadow = '0 12px 40px rgba(0,0,0,0.16)';

  const title = document.createElement('div');
  title.style.fontWeight = '700';
  title.style.marginBottom = '6px';
  title.textContent = 'Models are auto-selected on the hobby plan';
  const subtitle = document.createElement('div');
  subtitle.style.color = '#6b7280';
  subtitle.style.fontSize = '13px';
  subtitle.style.marginBottom = '12px';
  subtitle.textContent = 'Upgrade to choose a specific model. Here are the available options:';

  const list = document.createElement('div');
  list.style.display = 'grid';
  list.style.gap = '8px';
  list.style.filter = 'blur(4px)';
  list.style.pointerEvents = 'none';
  (state.models || []).forEach((m) => {
    const row = document.createElement('div');
    row.style.padding = '10px 12px';
    row.style.border = '1px solid #e5e7eb';
    row.style.borderRadius = '10px';
    row.textContent = m.label || m.name || m.id || 'Model';
    list.appendChild(row);
  });
  if (!list.children.length) {
    const placeholder = document.createElement('div');
    placeholder.style.padding = '10px 12px';
    placeholder.style.border = '1px dashed #e5e7eb';
    placeholder.style.borderRadius = '10px';
    placeholder.textContent = 'Admin has not published models yet';
    list.appendChild(placeholder);
  }

  const closeBtn = document.createElement('button');
  closeBtn.textContent = 'Close';
  closeBtn.style.marginTop = '14px';
  closeBtn.className = 'ghost';
  closeBtn.addEventListener('click', () => {
    if (modelPreviewOverlay) {
      modelPreviewOverlay.remove();
      modelPreviewOverlay = null;
    }
  });

  panel.appendChild(title);
  panel.appendChild(subtitle);
  panel.appendChild(list);
  panel.appendChild(closeBtn);
  modelPreviewOverlay.appendChild(panel);
  modelPreviewOverlay.addEventListener('click', (e) => {
    if (e.target === modelPreviewOverlay) {
      modelPreviewOverlay.remove();
      modelPreviewOverlay = null;
    }
  });
  document.body.appendChild(modelPreviewOverlay);
}

function applyPlanModelLock() {
  if (!el.modelSelect) return;
  syncUploadButtonState();
  if (!isFreePlan()) {
    const hasUsableOptions = Array.from(el.modelSelect.options || []).some((opt) => !opt.disabled);
    if (!hasUsableOptions) return;
    if (modelPreviewAttached) {
      el.modelSelect.removeEventListener('click', showBlurredModelPreview);
      modelPreviewAttached = false;
    }
    el.modelSelect.disabled = false;
    el.modelSelect.dataset.locked = '';
    renderModelIcon(el.modelSelect.value);
    return;
  }
  el.modelSelect.innerHTML = '';
  const opt = document.createElement('option');
  opt.value = 'auto';
  opt.textContent = 'Auto (admin managed)';
  el.modelSelect.appendChild(opt);
  el.modelSelect.value = 'auto';
  el.modelSelect.dataset.locked = 'true';
  el.modelSelect.disabled = false;
  if (!modelPreviewAttached) {
    el.modelSelect.addEventListener('click', showBlurredModelPreview);
    modelPreviewAttached = true;
  }
  renderModelIcon(null);
}

function setStatus(msg) {
  el.statusLine.textContent = msg || '';
}

async function api(path, options = {}) {
  // Check for session token in localStorage
  let sessionToken = null;
  try {
    const storedUser = localStorage.getItem('wordpress_plugin_ai_user');
    if (storedUser) {
      const parsed = JSON.parse(storedUser);
      if (parsed && parsed.sessionToken) {
        sessionToken = parsed.sessionToken;
      }
    }
  } catch (_) { /* ignore */ }

  const headers = {
    'Content-Type': 'application/json',
    ...(sessionToken ? { 'Authorization': `Bearer ${sessionToken}` } : {}),
    ...(options.headers || {}),
  };

  // Fallback to old user ID header if no session token
  if (!sessionToken && state.userId) {
    headers['X-User-Id'] = state.userId;
  }

  const res = await fetch(path, {
    headers,
    ...options,
  });

  // Handle authentication errors
  if (res.status === 401) {
    // Clear invalid session and redirect to login
    try {
      localStorage.removeItem('wordpress_plugin_ai_user');
    } catch (_) { /* ignore */ }
    const next = encodeURIComponent(window.location.pathname + window.location.search);
    window.location.href = `/login?next=${next}`;
    return;
  }

  const text = await res.text();
  const json = text ? JSON.parse(text) : {};
  if (!res.ok) {
    const err = new Error(json.error || res.statusText);
    if (json.stdout) err.stdout = json.stdout;
    if (json.stderr) err.stderr = json.stderr;
    throw err;
  }
  return json;
}

function populateCliSelect() {
  if (!el.cliSelect) return;
  el.cliSelect.innerHTML = '';
  state.cliOptions.forEach((cli) => {
    const opt = document.createElement('option');
    opt.value = cli;
    opt.textContent = cli.toUpperCase();
    el.cliSelect.appendChild(opt);
  });
  el.cliSelect.value = state.currentCli;
}

function renderSessions() {
  el.sessionList.innerHTML = '';
  if (!state.sessions.length) {
    el.sessionList.innerHTML = '<div class="muted">No sessions yet.</div>';
    return;
  }

  state.sessions.forEach((session) => {
    const div = document.createElement('div');
    div.className = `session-item ${session.id === state.currentSessionId ? 'active' : ''}`;
    div.innerHTML = `
      <div class="session-title">${session.title || 'Chat'}</div>
      <div class="session-meta">
        <span class="badge">${session.cli || 'opencode'}</span>
        <span class="badge">${session.model}</span>
        <span class="badge ${session.pending ? 'accent' : ''}">${session.pending || 0} queued</span>
      </div>
      <div class="session-delete">
        <button title="Delete" class="delete-btn">🗑</button>
        <div class="delete-menu" role="dialog" aria-label="Confirm delete chat">
          <div class="title">Delete this chat?</div>
          <div class="actions">
            <button class="cancel-delete ghost">Cancel</button>
            <button class="confirm-delete">Delete</button>
          </div>
        </div>
      </div>
    `;
    div.addEventListener('click', () => selectSession(session.id));
    // Stop clicks from the delete button bubbling up and opening the session
    const deleteBtn = div.querySelector('.delete-btn');
    const deleteMenu = div.querySelector('.delete-menu');
    const cancelBtn = div.querySelector('.cancel-delete');
    const confirmBtn = div.querySelector('.confirm-delete');
    if (deleteBtn) {
      deleteBtn.addEventListener('click', (e) => {
        e.stopPropagation();
        // Toggle the inline menu
        const isOpen = div.classList.toggle('show-delete-menu');
        if (isOpen) {
          // close any other menus
          document.querySelectorAll('.session-item.show-delete-menu').forEach((item) => { if (item !== div) item.classList.remove('show-delete-menu'); });
          // attach an outside click handler to close
          const onDocClick = (ev) => {
            if (!div.contains(ev.target)) div.classList.remove('show-delete-menu');
          };
          setTimeout(() => document.addEventListener('click', onDocClick, { once: true }), 0);
        }
      });
    }
    if (cancelBtn) { cancelBtn.addEventListener('click', (e) => { e.stopPropagation(); div.classList.remove('show-delete-menu'); }); }
    if (confirmBtn) {
      confirmBtn.addEventListener('click', async (e) => {
        e.stopPropagation();
        try {
          await api(`/api/sessions/${session.id}`, { method: 'DELETE' });
          // Remove the session from state and re-render
          state.sessions = state.sessions.filter((s) => s.id !== session.id);
          if (state.currentSessionId === session.id) {
            // Pick another session or create new
            if (state.sessions.length) await selectSession(state.sessions[0].id); else { state.currentSessionId = null; el.sessionList.innerHTML = ''; await createSession(); }
          } else {
            renderSessions();
          }
          setStatus('Chat deleted');
        } catch (error) {
          setStatus(`Failed to delete chat: ${error.message}`);
        }
      });
    }
    el.sessionList.appendChild(div);
  });
}

function renderSessionMeta(session) {
  el.sessionId.textContent = session.id;
  el.sessionModel.textContent = `${session.cli || 'opencode'} / ${session.model}`;
  el.sessionPending.textContent = session.pending || 0;
  el.queueIndicator.textContent = session.pending ? `${session.pending} queued` : 'Idle';
  el.queueIndicator.style.borderColor = session.pending ? 'var(--accent)' : 'var(--border)';
  el.chatTitle.textContent = session.title || 'Chat';
  if (session.cli && el.cliSelect && el.cliSelect.value !== session.cli) {
    el.cliSelect.value = session.cli;
    state.currentCli = session.cli;
  }
  if (session.model && el.modelSelect.value !== session.model) {
    el.modelSelect.value = session.model;
  }
}

function renderMessages(session) {
  el.chatArea.innerHTML = '';
  if (!session.messages || !session.messages.length) {
    el.chatArea.innerHTML = '<div class="muted empty-message">Send a message to start the conversation.</div>';
    return;
  }

  session.messages.forEach((msg) => {
    const status = msg.status || 'done';

    const userCard = document.createElement('div');
    userCard.className = 'message user';
    const userMeta = document.createElement('div');
    userMeta.className = 'meta';
    userMeta.innerHTML = `
      <span>You</span>
      <span class="badge">${msg.model || session.model}</span>
      <span class="status-chip ${status}">${status}</span>
    `;
    const userBody = document.createElement('div');
    userBody.className = 'body';
    userBody.appendChild(renderContentWithTodos(msg.displayContent || msg.content || ''));
    userCard.appendChild(userMeta);
    userCard.appendChild(userBody);
    // Attachments
    if (Array.isArray(msg.attachments) && msg.attachments.length) {
      const attachWrap = document.createElement('div');
      attachWrap.className = 'attachments';
      msg.attachments.forEach((a) => {
        if (a && a.url && (a.type || '').startsWith('image/')) {
          const img = document.createElement('img');
          img.className = 'attachment-image';
          img.src = a.url;
          img.alt = a.name || 'image';
          img.style.maxWidth = '400px';
          img.style.display = 'block';
          img.style.marginTop = '8px';
          attachWrap.appendChild(img);
        }
      });
      userCard.appendChild(attachWrap);
    }
    el.chatArea.appendChild(userCard);

    if (msg.reply || msg.error || (status === 'running' && msg.partialOutput)) {
      const assistantCard = document.createElement('div');
      assistantCard.className = 'message assistant';
      const assistantMeta = document.createElement('div');
      assistantMeta.className = 'meta';
      assistantMeta.innerHTML = `<span>${(session.cli || 'opencode').toUpperCase()}</span>`;
      const rawBtn = document.createElement('button');
      rawBtn.className = 'ghost';
      rawBtn.style.marginLeft = '8px';
      rawBtn.textContent = 'Raw';
      assistantMeta.appendChild(rawBtn);
      const assistantBody = document.createElement('div');
      assistantBody.className = 'body';
      assistantBody.appendChild(renderContentWithTodos(msg.reply || msg.partialOutput || msg.opencodeSummary || ''));
      assistantCard.appendChild(assistantMeta);
      assistantCard.appendChild(assistantBody);
      if (Array.isArray(msg.attachments) && msg.attachments.length) {
        const attachWrap = document.createElement('div');
        attachWrap.className = 'attachments';
        msg.attachments.forEach((a) => {
          if (a && a.url && (a.type || '').startsWith('image/')) {
            const img = document.createElement('img');
            img.className = 'attachment-image';
            img.src = a.url;
            img.alt = a.name || 'image';
            img.style.maxWidth = '400px';
            img.style.display = 'block';
            img.style.marginTop = '8px';
            attachWrap.appendChild(img);
          }
        });
        assistantCard.appendChild(attachWrap);
      }
      if (msg.error) {
        const err = document.createElement('div');
        err.className = 'body';
        err.style.color = 'var(--danger)';
        err.textContent = msg.error;
        assistantCard.appendChild(err);
      }
      if ((!msg.reply || !msg.reply.length) && (!msg.partialOutput || !msg.partialOutput.length) && msg.opencodeSummary) {
        const summary = document.createElement('div');
        summary.className = 'body';
        summary.style.color = 'var(--muted)';
        summary.textContent = `Opencode output: ${msg.opencodeSummary}`;
        assistantCard.appendChild(summary);
      }
      const rawPre = document.createElement('pre');
      rawPre.className = 'raw-output muted';
      rawPre.style.display = 'none';
      rawPre.textContent = [(msg.partialOutput || ''), (msg.opencodeSummary || '')].filter(Boolean).join('\n\n');
      assistantCard.appendChild(rawPre);
      rawBtn.addEventListener('click', () => { rawPre.style.display = rawPre.style.display === 'none' ? 'block' : 'none'; });
      el.chatArea.appendChild(assistantCard);
    }
  });
  el.chatArea.scrollTop = el.chatArea.scrollHeight;
}

// Helper: render text content and convert markdown task-list lines to actual checkboxes
function renderContentWithTodos(text) {
  const wrapper = document.createElement('div');
  if (!text) return document.createTextNode('');
  const processedText = String(text).replace(/\.\s+/g, '.\n');
  const lines = processedText.split(/\r?\n/);
  let currentList = null;
  for (const line of lines) {
    const taskMatch = line.match(/^\s*[-*]\s*\[( |x|X)\]\s*(.*)$/);
    if (taskMatch) {
      if (!currentList) { currentList = document.createElement('ul'); wrapper.appendChild(currentList); }
      const li = document.createElement('li');
      const label = document.createElement('label');
      const checkbox = document.createElement('input');
      checkbox.type = 'checkbox';
      checkbox.disabled = true;
      checkbox.checked = !!taskMatch[1].trim();
      label.appendChild(checkbox);
      label.appendChild(document.createTextNode(' ' + taskMatch[2]));
      li.appendChild(label);
      currentList.appendChild(li);
    } else {
      if (currentList) currentList = null;
      const p = document.createElement('div');
      p.textContent = line;
      wrapper.appendChild(p);
    }
  }
  return wrapper;
}

function renderModelIcon(selectedValue) {
  if (!el.modelIcon) return;
  if (!selectedValue) { el.modelIcon.src = ''; el.modelIcon.style.display = 'none'; el.modelIcon.title = ''; return; }
  const model = state.models.find((m) => (m.name || m.id || m) === selectedValue);
  if (!model || !model.icon) { el.modelIcon.src = ''; el.modelIcon.style.display = 'none'; el.modelIcon.title = model ? (model.label || model.name || selectedValue) : ''; return; }
  el.modelIcon.src = model.icon;
  el.modelIcon.alt = model.label || model.name || selectedValue;
  el.modelIcon.title = model.label || model.name || selectedValue;
  el.modelIcon.style.display = 'inline-block';
}

async function loadModels(cli = state.currentCli || 'opencode') {
  try {
    state.currentCli = cli;
    if (el.cliSelect && el.cliSelect.value !== cli) el.cliSelect.value = cli;
    const data = await api(`/api/models?cli=${encodeURIComponent(cli)}`);
    state.models = data.models || [];

    el.modelSelect.innerHTML = '';
    if (!state.models.length) {
      const opt = document.createElement('option');
      opt.value = '';
      opt.textContent = 'No models configured (ask admin)';
      opt.disabled = true;
      opt.selected = true;
      el.modelSelect.appendChild(opt);
      el.modelSelect.disabled = true;
      renderModelIcon(null);
      setStatus('No models configured. Ask an admin to add models in the admin panel.');
      return;
    }

    el.modelSelect.disabled = false;
    state.models.forEach((m) => {
      const option = document.createElement('option');
      option.value = m.name || m.id || m;
      const multiplierLabel = m.multiplier ? ` (${m.multiplier}x)` : '';
      option.textContent = `${m.label || m.name || m.id || m}${multiplierLabel}`;
      if (m.icon) option.dataset.icon = m.icon;
      if (m.multiplier) option.dataset.multiplier = m.multiplier;
      el.modelSelect.appendChild(option);
    });

    if (el.modelSelect.value === '' && state.models.length > 0) {
      el.modelSelect.value = state.models[0].name || state.models[0].id || 'default';
    }
    renderModelIcon(el.modelSelect.value);
    applyPlanModelLock();
  } catch (error) {
    setStatus(`Model load failed: ${error.message}`);
    state.models = [];
    el.modelSelect.innerHTML = '';
    const opt = document.createElement('option');
    opt.value = '';
    opt.textContent = 'No models available';
    opt.disabled = true;
    opt.selected = true;
    el.modelSelect.appendChild(opt);
    el.modelSelect.disabled = true;
    renderModelIcon(null);
    applyPlanModelLock();
  }
}

async function loadSessions() {
  const data = await api('/api/sessions');
  state.sessions = data.sessions || [];
  if (state.sessions.length && !state.currentCli) {
    state.currentCli = state.sessions[0].cli || 'opencode';
    if (el.cliSelect) el.cliSelect.value = state.currentCli;
  }
  renderSessions();
  if (!state.currentSessionId && state.sessions.length) {
    await selectSession(state.sessions[0].id);
  }
}

async function selectSession(id) {
  state.currentSessionId = id;
  const session = state.sessions.find((s) => s.id === id);
  if (session) {
    state.currentCli = session.cli || 'opencode';
    if (el.cliSelect) el.cliSelect.value = state.currentCli;
    await loadModels(state.currentCli);
  }
  renderSessions();
  await refreshCurrentSession();
  setPollingInterval(2500);
}

// Set up SSE stream for a message
function streamMessage(sessionId, messageId) {
  // Close existing stream if any
  if (state.activeStreams.has(messageId)) {
    const existing = state.activeStreams.get(messageId);
    existing.close();
    state.activeStreams.delete(messageId);
  }

  const url = `/api/sessions/${sessionId}/messages/${messageId}/stream`;
  const eventSource = new EventSource(url);

  eventSource.onopen = () => {
    console.log('SSE stream opened for message', messageId);
  };

  eventSource.onmessage = (event) => {
    try {
      const data = JSON.parse(event.data);

      // Update the session with streaming data
      const session = state.sessions.find(s => s.id === sessionId);
      if (!session) return;

      const message = session.messages.find(m => m.id === messageId);
      if (!message) return;

      if (data.type === 'server-restart') {
        message.status = 'queued';
        setStatus('Server restarting, your session will be restored...');
        eventSource.close();
        state.activeStreams.delete(messageId);
        renderMessages(session);
        return;
      } else if (data.type === 'start') {
        message.status = 'running';
        setStatus('OpenCode is responding...');
      } else if (data.type === 'chunk') {
        // Update partial output immediately
        message.partialOutput = data.filtered || data.partialOutput || data.content;
        message.outputType = data.outputType;
        message.partialUpdatedAt = data.timestamp;
        message.status = 'running';

        // Re-render messages to show new content immediately
        renderMessages(session);
        setStatus('Streaming response...');
      } else if (data.type === 'health') {
        // Sync status from server heartbeat
        if (data.status && message.status !== data.status) {
          console.log('Syncing message status from health event', { 
            messageId, 
            oldStatus: message.status, 
            newStatus: data.status 
          });
          message.status = data.status;
          renderMessages(session);
        }
      } else if (data.type === 'complete') {
        message.reply = data.content;
        message.status = 'done';
        message.finishedAt = data.timestamp;
        message.outputType = data.outputType;
        message.opencodeExitCode = data.exitCode;
        eventSource.close();
        state.activeStreams.delete(messageId);
        renderMessages(session);
        setStatus('Complete');

        // Update session list
        renderSessions();

        // Update usage summary to show token count
        loadUsageSummary().catch(() => {});
      } else if (data.type === 'error') {
        message.error = data.error;
        message.status = 'error';
        message.finishedAt = data.timestamp;
        message.opencodeExitCode = data.code;
        eventSource.close();
        state.activeStreams.delete(messageId);
        renderMessages(session);
        setStatus('Error: ' + data.error);

        // Update session list
        renderSessions();

        // Update usage summary even on error
        loadUsageSummary().catch(() => {});
      }
    } catch (err) {
      console.error('Failed to parse SSE message', err);
    }
  };

  eventSource.onerror = (err) => {
    console.error('SSE error', err);
    eventSource.close();
    state.activeStreams.delete(messageId);

    // Check if server is restarting by attempting to reconnect
    const session = state.sessions.find(s => s.id === sessionId);
    const message = session?.messages.find(m => m.id === messageId);
    
    if (message && message.status === 'queued') {
      // Server restart was signaled, poll for reconnection
      let reconnectAttempts = 0;
      const maxReconnectAttempts = 30; // 30 seconds max
      const reconnectInterval = setInterval(() => {
        reconnectAttempts++;
        refreshCurrentSession().then(() => {
          // Successfully reconnected and refreshed
          const updatedSession = state.sessions.find(s => s.id === sessionId);
          const updatedMessage = updatedSession?.messages.find(m => m.id === messageId);
          if (updatedMessage && updatedMessage.status !== 'queued') {
            clearInterval(reconnectInterval);
            setStatus('Reconnected to server');
          }
        }).catch(() => {
          // Server still down
          if (reconnectAttempts >= maxReconnectAttempts) {
            clearInterval(reconnectInterval);
            if (message) {
              message.status = 'error';
              message.error = 'Server restart took too long. Please try again.';
              renderMessages(session);
            }
            setStatus('Server reconnection failed');
          }
        });
      }, 1000);
    } else {
      // Fall back to polling for this message
      setTimeout(() => refreshCurrentSession(), 1000);
    }
  };

  state.activeStreams.set(messageId, eventSource);
}

async function refreshCurrentSession() {
  if (!state.currentSessionId) return;
  try {
    const { session } = await api(`/api/sessions/${state.currentSessionId}`);

    // Preserve optimistic "temp-" messages that may have been added locally
    const old = state.sessions.find((s) => s.id === session.id);
    const tempMsgs = (old && Array.isArray(old.messages)) ? old.messages.filter(m => String(m.id).startsWith('temp-')) : [];
    if (tempMsgs.length) {
      session.messages = session.messages || [];
      const existingIds = new Set((session.messages || []).map((m) => m.id));
      tempMsgs.forEach((m) => {
        if (!existingIds.has(m.id)) session.messages.push(m);
      });

      // De-duplicate if server returned a real message with same content
      const realContents = new Set((session.messages || []).filter(m => !String(m.id).startsWith('temp-')).map(m => (m.displayContent || m.content || '').trim()));
      session.messages = (session.messages || []).filter(m => {
        if (String(m.id).startsWith('temp-')) {
          return !realContents.has((m.displayContent || m.content || '').trim());
        }
        return true;
      });

      session.messages.sort((a, b) => new Date(a.createdAt || 0) - new Date(b.createdAt || 0));
    }

    state.sessions = state.sessions.map((s) => (s.id === session.id ? session : s));
    renderSessions();
    renderSessionMeta(session);
    renderMessages(session);

    // Set up streaming for any running messages that don't have streams yet
    const running = (session.messages || []).filter((m) => m.status === 'running' || m.status === 'queued');
    running.forEach(msg => {
      if (!state.activeStreams.has(msg.id)) {
        streamMessage(session.id, msg.id);
      }
    });

    // Adjust polling - slower when using SSE
    if (running.length > 0) setPollingInterval(2000);
    else setPollingInterval(5000);
  } catch (error) {
    setStatus(error.message);
  }
}

function setPollingInterval(intervalMs) {
  if (!intervalMs) return;
  if (state.pollingInterval === intervalMs) return;
  if (state.pollingTimer) clearInterval(state.pollingTimer);
  state.pollingInterval = intervalMs;
  state.pollingTimer = setInterval(refreshCurrentSession, state.pollingInterval);
}

async function createSession(options = {}) {
  const cli = el.cliSelect ? el.cliSelect.value : state.currentCli || 'opencode';
  state.currentCli = cli;
  const model = el.modelSelect.value;
  if (!model) {
    setStatus('No models available. Ask an admin to add models.');
    return;
  }
  let session;
  try {
    const payload = { model, cli };
    
    // When creating a new chat within an existing app, preserve the app title
    if (options.appId && options.reuseAppId) {
      const currentSession = state.sessions.find(s => s.id === state.currentSessionId);
      if (currentSession && currentSession.title) {
        payload.title = currentSession.title;
      }
    }
    
    if (options.appId) {
      payload.appId = options.appId;
      payload.reuseAppId = true;
    }
    const data = await api('/api/sessions', {
      method: 'POST',
      body: JSON.stringify(payload),
    });
    session = data.session;
  } catch (err) {
    setStatus(err.message || 'Unable to create app');
    throw err;
  }
  state.sessions.unshift(session);
  renderSessions();
  await selectSession(session.id);
}

async function checkOpencodeStatus() {
  try {
    const status = await api('/api/opencode/status');
    state.opencodeStatus = status;

    if (!status.available) {
      setStatus(`Warning: OpenCode CLI not available - ${status.error || 'unknown error'}`);
    }

    return status;
  } catch (error) {
    console.error('Failed to check opencode status', error);
    return null;
  }
}

async function sendMessage() {
  const content = el.messageInput.value.trim();
  if (!content && !pendingAttachments.length) return;
  if (!state.currentSessionId) {
    try {
      await createSession();
    } catch (_) {
      return;
    }
  }
  const cli = el.cliSelect ? el.cliSelect.value : state.currentCli || 'opencode';
  state.currentCli = cli;
  const model = el.modelSelect.value;
  if (!model) {
    setStatus('Select a model configured by your admin');
    return;
  }
  el.sendBtn.disabled = true;
  setStatus('Sending...');
  try {
    const attachments = pendingAttachments.map((a) => ({ name: a.name, type: a.type, data: a.data }));
    const payload = { content, displayContent: content, model, cli, attachments: attachments.length ? attachments : undefined };
    // Preserve opencodeSessionId to continue in the same session
    const currentSession = state.sessions.find(s => s.id === state.currentSessionId);
    if (currentSession && currentSession.opencodeSessionId) {
      payload.opencodeSessionId = currentSession.opencodeSessionId;
      console.log('[APP] Preserving opencodeSessionId:', currentSession.opencodeSessionId);
    }

    const response = await api(`/api/sessions/${state.currentSessionId}/messages`, {
      method: 'POST',
      body: JSON.stringify(payload),
    });
    el.messageInput.value = '';

    // Clear pending attachments after send
    while (pendingAttachments.length) {
      const removed = pendingAttachments.pop();
      if (removed && removed.previewUrl && removed.previewUrl.startsWith('blob:')) {
        try { URL.revokeObjectURL(removed.previewUrl); } catch (_) { }
      }
    }
    renderAttachmentPreview();

    // Start streaming immediately for the new message
    if (response.message && response.message.id) {
      streamMessage(state.currentSessionId, response.message.id);
    }

    await refreshCurrentSession();
    await loadUsageSummary();
  } catch (error) {
    setStatus(error.message);
  } finally {
    el.sendBtn.disabled = false;
  }
}

function hookEvents() {
  el.newChat.addEventListener('click', async () => {
    const currentSession = state.sessions.find(s => s.id === state.currentSessionId);
    const currentAppId = currentSession?.appId;
    if (currentAppId) {
      await createSession({ appId: currentAppId, reuseAppId: true });
    } else {
      await createSession({});
    }
  });
  el.sendBtn.addEventListener('click', sendMessage);

  if (el.uploadMediaBtn && el.uploadMediaInput) {
    console.log('Upload media elements found, attaching event listeners');
    el.uploadMediaBtn.addEventListener('click', (e) => {
      console.log('Upload media button clicked, isPaidPlanClient:', isPaidPlanClient());
      e.preventDefault();
      e.stopPropagation();
      
      // Check if user is on free plan
      if (!isPaidPlanClient()) {
        // Show upgrade modal instead of redirecting to pricing
        if (typeof window.showUpgradeModal === 'function' && !isEnterprisePlan()) {
          console.log('Showing upgrade modal');
          window.showUpgradeModal();
        } else if (isEnterprisePlan()) {
          setStatus('You are already on the Enterprise plan with full access.');
        } else {
          window.location.href = '/upgrade';
        }
        return;
      }
      
      // Check if model supports media
      if (!currentModelSupportsMedia()) {
        setStatus('This model does not support image uploads. Please select a different model that supports media.');
        return;
      }
      
      // For paid users with media-supporting models, trigger file input click
      console.log('Triggering file input click');
      el.uploadMediaInput.value = '';
      el.uploadMediaInput.click();
    });
    el.uploadMediaInput.addEventListener('change', async () => {
      console.log('File input changed, files:', el.uploadMediaInput.files);
      const files = el.uploadMediaInput.files ? Array.from(el.uploadMediaInput.files) : [];
      
      // Reset input immediately to allow same file selection again
      el.uploadMediaInput.value = '';
      
      if (!files.length) return;
      if (!isPaidPlanClient()) {
        // Show upgrade modal instead of just showing status
        if (typeof window.showUpgradeModal === 'function' && !isEnterprisePlan()) {
          window.showUpgradeModal();
        } else if (isEnterprisePlan()) {
          setStatus('Upload media is available on your Enterprise plan.');
        } else {
          setStatus('Upload media is available on Professional/Enterprise plans');
        }
        return;
      }
      setStatus('Preparing images...');
      el.sendBtn.disabled = true;
      try {
        for (const file of files.slice(0, 6)) {
          if (!file || !(file.type || '').startsWith('image/')) continue;
          const att = await fileToCompressedWebpAttachment(file);
          pendingAttachments.push(att);
        }
        renderAttachmentPreview();
        
        // Show visual feedback
        const attachedCount = pendingAttachments.length;
        setStatus(`✓ ${attachedCount} image${attachedCount > 1 ? 's' : ''} attached successfully!`);
        
        // Briefly highlight the upload button to show feedback
        el.uploadMediaBtn.style.color = '#4ade80';
        el.uploadMediaBtn.style.fontWeight = 'bold';
        setTimeout(() => {
          el.uploadMediaBtn.style.color = '';
          el.uploadMediaBtn.style.fontWeight = '';
        }, 2000);
      } catch (err) {
        setStatus(err.message || 'Failed to attach image');
      } finally {
        el.sendBtn.disabled = false;
      }
    });
  } else {
    console.log('Upload media elements NOT found. el.uploadMediaBtn:', el.uploadMediaBtn, 'el.uploadMediaInput:', el.uploadMediaInput);
  }
  if (el.cliSelect) {
    el.cliSelect.addEventListener('change', async () => {
      state.currentCli = el.cliSelect.value;
      await loadModels(state.currentCli);
      if (state.currentSessionId) {
        try {
          await api(`/api/sessions/${state.currentSessionId}`, { method: 'PATCH', body: JSON.stringify({ cli: state.currentCli }) });
          await refreshCurrentSession();
        } catch (err) { setStatus(`Failed to update CLI: ${err.message}`); }
      }
    });
  }
  el.messageInput.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
      sendMessage();
    }
  });
  // Support paste images
  el.messageInput.addEventListener('paste', async (e) => {
    try {
      const items = e.clipboardData && e.clipboardData.items ? Array.from(e.clipboardData.items) : [];
      const imageItem = items.find(it => it.type && it.type.startsWith('image/'));
      if (!imageItem) return;
      e.preventDefault();

      if (!isPaidPlanClient()) {
        setStatus('Paste image is available on Business/Enterprise plans');
        return;
      }
      const blob = imageItem.getAsFile();
      if (!blob) return;

      const att = await fileToCompressedWebpAttachment(blob);
      pendingAttachments.push(att);
      renderAttachmentPreview();
      setStatus(`${pendingAttachments.length} image(s) attached`);
    } catch (err) { console.error('Paste handler error', err); }
  });

  el.quickButtons.forEach((btn) => {
    btn.addEventListener('click', () => {
      const tag = btn.dataset.quick;
      const map = {
        shorter: 'Please condense the last answer.',
        more: 'Tell me more about this topic.',
      };
      el.messageInput.value = map[tag] || '';
      el.messageInput.focus();
    });
  });

  el.gitButtons.forEach((btn) => {
    btn.addEventListener('click', async () => {
      const action = btn.dataset.git;
      el.gitOutput.textContent = `Running ${action}...`;
      try {
        const payload = {};
        // Disable git buttons while running to prevent duplicates
        el.gitButtons.forEach((b) => b.disabled = true);
        if (action === 'push' || action === 'sync') {
          payload.message = (el.commitMessage && el.commitMessage.value) ? el.commitMessage.value : (el.modalCommitMessage && el.modalCommitMessage.value) || 'Update from web UI';
        }
        const data = await api(`/api/git/${action}`, {
          method: 'POST',
          body: JSON.stringify(payload),
        });
        const out = data.output || data.stdout || data.stderr || 'Done';
        el.gitOutput.textContent = out;
      } catch (error) {
        const lines = [];
        lines.push(error.message);
        if (error.stdout) lines.push('\nSTDOUT:\n' + error.stdout.trim());
        if (error.stderr) lines.push('\nSTDERR:\n' + error.stderr.trim());
        el.gitOutput.textContent = lines.join('\n');
      }
      // Re-enable git buttons regardless of outcome
      el.gitButtons.forEach((b) => b.disabled = false);
    });
  });
  if (el.githubButton) {
    el.githubButton.addEventListener('click', () => {
      console.log('GitHub button clicked, showing modal');
      el.githubModal.style.display = 'flex';
    });
  } else {
    console.error('GitHub button element not found');
  }
  if (el.githubClose) {
    el.githubClose.addEventListener('click', () => {
      console.log('GitHub close button clicked');
      el.githubModal.style.display = 'none';
    });
  }
  const modalButtons = document.querySelectorAll('#github-modal [data-git]');
  modalButtons.forEach((btn) => {
    btn.addEventListener('click', async () => {
      const action = btn.dataset.git;
      el.gitOutput.textContent = `Running ${action}...`;
      try {
        const payload = {};
        // Disable all git buttons while running
        el.gitButtons.forEach((b) => b.disabled = true);
        if (action === 'push' || action === 'sync') payload.message = (el.modalCommitMessage && el.modalCommitMessage.value) ? el.modalCommitMessage.value : 'Update from web UI';
        const data = await api(`/api/git/${action}`, { method: 'POST', body: JSON.stringify(payload) });
        const out = data.output || data.stdout || data.stderr || 'Done';
        el.gitOutput.textContent = out;
      } catch (error) {
        const lines = [];
        lines.push(error.message);
        if (error.stdout) lines.push('\nSTDOUT:\n' + error.stdout.trim());
        if (error.stderr) lines.push('\nSTDERR:\n' + error.stderr.trim());
        el.gitOutput.textContent = lines.join('\n');
      }
      // Re-enable git buttons
      el.gitButtons.forEach((b) => b.disabled = false);
    });
  });
  if (el.diagnosticsButton) {
    el.diagnosticsButton.addEventListener('click', async () => {
      el.gitOutput.textContent = 'Running diagnostics...';
      try {
        const data = await api('/api/diagnostics');
        const out = `Version:\n${data.version || ''}\n\nModels Output:\n${data.modelsOutput || ''}`;
        el.gitOutput.textContent = out;
      } catch (error) {
        el.gitOutput.textContent = `Diagnostics failed: ${error.message}`;
      }
    });
  }
  el.modelSelect.addEventListener('change', async () => {
    const selected = el.modelSelect.value;
    renderModelIcon(selected);
    if (isFreePlan()) {
      showBlurredModelPreview();
      setStatus('Model selection is automatic on the hobby plan');
      return;
    }
    if (!selected) return;
    if (state.currentSessionId) {
      try {
        await api(`/api/sessions/${state.currentSessionId}`, { method: 'PATCH', body: JSON.stringify({ model: selected }) });
        await refreshCurrentSession();
      } catch (e) { setStatus(`Failed to update model: ${e.message}`); }
    }
  });

  // Upgrade header button functionality (for builder page)
  const upgradeHeaderBtn = document.getElementById('upgrade-header-btn');
  if (upgradeHeaderBtn) {
    upgradeHeaderBtn.addEventListener('click', () => {
      if (typeof window.showUpgradeModal === 'function' && !isEnterprisePlan()) {
        window.showUpgradeModal();
      } else if (isEnterprisePlan()) {
        alert('You are already on the Enterprise plan with full access.');
      } else {
        window.location.href = '/upgrade';
      }
    });
  }
}

// Handle page visibility changes to maintain polling
document.addEventListener('visibilitychange', () => {
  const isVisible = document.visibilityState === 'visible';
  if (isVisible) {
    // User came back to the page, refresh immediately
    console.log('Page became visible, refreshing...');
    refreshCurrentSession().catch(err => console.error('Refresh failed', err));
    // Ensure polling interval is set
    if (!state.pollingTimer) {
      setPollingInterval(2500);
    }
  } else {
    // User left the page, but keep polling in the background at a slower rate
    console.log('Page became hidden, maintaining background polling...');
    if (state.pollingTimer) {
      setPollingInterval(5000); // Slower polling in background
    }
  }
});

// Handle page unload gracefully
window.addEventListener('beforeunload', (e) => {
  // Check if there are running processes
  const running = state.sessions.flatMap(s => s.messages || []).filter(m => m.status === 'running' || m.status === 'queued');
  if (running.length > 0) {
    console.log('Page unloading with running processes. They will continue on the server.');
    // Don't prevent unload, just log it
  }
});

// When user comes back to the page after a long time, ensure we reconnect to running processes
window.addEventListener('focus', () => {
  console.log('Window focused, checking for running processes to reconnect...');
  if (state.currentSessionId) {
    refreshCurrentSession().catch(err => console.error('Refresh on focus failed', err));
  }
});

(async function init() {
  populateCliSelect();
  hookEvents();

  // Check opencode status on startup
  checkOpencodeStatus();

  await loadAccountPlan();
  await loadModels(state.currentCli);
  await loadSessions();
  if (!state.sessions.length) {
    await createSession();
  }

  // Periodically check opencode status (reduced frequency to reduce CPU usage)
  setInterval(checkOpencodeStatus, 300000);

  // Keep polling going even in background (for running processes)
  // Start with reasonable interval that will be adjusted by refreshCurrentSession
  setPollingInterval(5000);
})(); 
