import { api, toast } from '/static/js/api.js';
import { subscribe, send as wsSend, onPollFallback } from '/static/js/ws.js';

const errorBox = document.getElementById('error');
const convList = document.getElementById('conv-list');
const msgHost = document.getElementById('chat-messages');
const form = document.getElementById('chat-form');
const composer = document.getElementById('chat-composer');

let meId = '';
let conversations = [];
let current = null;
let oldestCursor = '';
let pendingAttachments = [];
const userCache = new Map();

const esc = (s) => String(s ?? '').replace(/[&<>"']/g, (c) => ({
  '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',
}[c]));

async function userName(id) {
  if (!userCache.has(id)) {
    try {
      const res = await api('GET', `/api/v1/users/${id}/card`);
      userCache.set(id, res.card.name);
    } catch (e) { userCache.set(id, '…'); }
  }
  return userCache.get(id);
}

async function loadConversations() {
  try {
    const res = await api('GET', '/api/v1/conversations');
    conversations = res.conversations;
    renderConvList();
    const want = new URLSearchParams(location.search).get('c');
    if (!current && want) {
      const c = conversations.find((x) => x.id === want);
      if (c) openConversation(c);
    }
  } catch (e) { errorBox.textContent = e.message; }
}

function renderConvList() {
  convList.replaceChildren(...conversations.map((c) => {
    const li = document.createElement('li');
    li.className = current && current.id === c.id ? 'active' : '';
    li.innerHTML = `
      <button type="button" class="conv-item">
        <span>${esc(c.title)} <span class="muted">· ${esc(c.kind)}</span></span>
        ${c.unread ? `<span class="badge accent">${c.unread}</span>` : ''}
      </button>`;
    li.querySelector('button').addEventListener('click', () => openConversation(c));
    return li;
  }));
  if (!conversations.length) {
    convList.innerHTML = '<li class="muted">No conversations yet.</li>';
  }
}

async function openConversation(c) {
  current = c;
  oldestCursor = '';
  document.getElementById('chat-title').textContent = c.title;
  const mbtn = document.getElementById('chat-members');
  mbtn.hidden = !(c.kind !== 'dm' && c.creatorId && c.creatorId === meId);
  form.hidden = false;
  msgHost.replaceChildren();
  renderConvList();
  await loadMessages(false);
  await api('POST', `/api/v1/conversations/${c.id}/read`, {}).catch(() => {});
  c.unread = 0;
  renderConvList();
  history.replaceState(null, '', '/messages?c=' + c.id);
}

async function loadMessages(prepend) {
  const res = await api('GET',
    `/api/v1/conversations/${current.id}/messages?cursor=${prepend ? oldestCursor : ''}`);
  const msgs = res.messages;
  if (msgs.length) oldestCursor = msgs[0].id;
  const nodes = await Promise.all(msgs.map(renderMessage));
  if (prepend) msgHost.prepend(...nodes);
  else {
    msgHost.append(...nodes);
    msgHost.scrollTop = msgHost.scrollHeight;
  }
}

// ULID ids embed a 48-bit ms timestamp in the first 10 chars.
function ulidTime(id) {
  const A = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
  let ms = 0;
  for (const ch of String(id).slice(0, 10).toUpperCase()) ms = ms * 32 + A.indexOf(ch);
  return new Date(ms);
}

async function renderMessage(m) {
  const div = document.createElement('div');
  div.className = 'chat-msg' + (m.senderId === meId ? ' own' : '');
  const atts = (m.attachments || []).map((a) => a.isImage
    ? `<img src="/files/${a.fileId}" alt="${esc(a.name)}" class="chat-img" data-lightbox loading="lazy">`
    : `<a class="btn small" href="/files/${a.fileId}">📄 ${esc(a.name)}</a>`).join(' ');
  div.innerHTML = `
    <p class="muted" style="margin:0">${esc(await userName(m.senderId))}
      · ${ulidTime(m.id).toLocaleString()}</p>
    <div>${m.body || ''}</div>
    ${atts ? `<div class="mt">${atts}</div>` : ''}`;
  div.querySelectorAll('[data-lightbox]').forEach((img) => {
    img.addEventListener('click', () => {
      document.getElementById('lightbox-img').src = img.src;
      document.getElementById('lightbox').showModal();
    });
  });
  return div;
}

document.getElementById('lightbox').addEventListener('click', () => {
  document.getElementById('lightbox').close();
});

// ---- composer ----
document.querySelectorAll('[data-cmd]').forEach((btn) => {
  btn.addEventListener('click', () => {
    composer.focus();
    document.execCommand(btn.dataset.cmd, false, null);
  });
});

composer.addEventListener('keydown', (e) => {
  if (e.key === 'Enter' && !e.shiftKey) {
    e.preventDefault();
    form.requestSubmit();
  }
});

let typingTimer = null;
composer.addEventListener('input', () => {
  if (!current) return;
  clearTimeout(typingTimer);
  typingTimer = setTimeout(() => {
    wsSend('chat', 'typing', { conversationId: current.id });
  }, 250);
});

async function uploadFiles(fileList) {
  for (const file of fileList) {
    const fd = new FormData();
    fd.append('file', file);
    try {
      const res = await api('POST', '/api/v1/files', fd);
      pendingAttachments.push(res);
      renderPending();
    } catch (e) { toast(`Upload failed: ${e.message}`, 'err'); }
  }
}

function renderPending() {
  const host = document.getElementById('chat-attachments');
  host.replaceChildren(...pendingAttachments.map((a, i) => {
    const chip = document.createElement('span');
    chip.className = 'badge';
    chip.innerHTML = `${a.isImage ? '🖼' : '📄'} ${esc(a.name)} <button type="button" class="btn small ghost" aria-label="Remove attachment">×</button>`;
    chip.querySelector('button').addEventListener('click', () => {
      pendingAttachments.splice(i, 1);
      renderPending();
    });
    return chip;
  }));
}

document.getElementById('chat-file').addEventListener('change', (e) => {
  uploadFiles(e.target.files);
  e.target.value = '';
});
composer.addEventListener('dragover', (e) => e.preventDefault());
composer.addEventListener('drop', (e) => {
  e.preventDefault();
  if (e.dataTransfer.files.length) uploadFiles(e.dataTransfer.files);
});

form.addEventListener('submit', async (e) => {
  e.preventDefault();
  if (!current) return;
  const body = composer.innerHTML;
  if (!composer.textContent.trim() && !pendingAttachments.length) return;
  try {
    await api('POST', `/api/v1/conversations/${current.id}/messages`, {
      body, attachments: pendingAttachments.map((a) => a.fileId),
    });
    composer.innerHTML = '';
    pendingAttachments = [];
    renderPending();
  } catch (err) { toast(err.message, 'err'); }
});

// ---- live events ----
const typingNames = new Map();
subscribe('chat', async (event, data) => {
  if (event === 'message' && data) {
    if (current && data.conversationId === current.id) {
      msgHost.appendChild(await renderMessage(data));
      msgHost.scrollTop = msgHost.scrollHeight;
      api('POST', `/api/v1/conversations/${current.id}/read`, {}).catch(() => {});
    } else {
      loadConversations();
    }
  }
  if (event === 'typing' && data && current && data.conversationId === current.id) {
    const name = await userName(data.userId);
    typingNames.set(data.userId, Date.now());
    document.getElementById('typing-indicator').textContent = `${name} is typing…`;
    setTimeout(() => {
      if (Date.now() - (typingNames.get(data.userId) || 0) >= 2900) {
        document.getElementById('typing-indicator').textContent = '';
      }
    }, 3000);
  }
});
onPollFallback(() => {
  loadConversations();
  if (current) {
    msgHost.replaceChildren();
    oldestCursor = '';
    loadMessages(false);
  }
});

// ---- new conversation dialog ----
const dlg = document.getElementById('newconv-dialog');
const selected = new Map();
document.getElementById('conv-new').addEventListener('click', () => {
  selected.clear();
  renderSelected();
  document.getElementById('nc-search').value = '';
  dlg.showModal();
  searchPeople(); // pre-fill the fixed-height list so the dialog never jumps
});
document.getElementById('nc-cancel').addEventListener('click', () => dlg.close());
document.getElementById('nc-kind').addEventListener('change', (e) => {
  document.getElementById('nc-title-wrap').hidden = e.target.value === 'dm';
  document.getElementById('nc-customer-wrap').hidden = e.target.value !== 'project';
});

let searchTimer = null;
async function searchPeople() {
  const q = document.getElementById('nc-search').value.trim();
  const res = await api('GET', `/api/v1/users?q=${encodeURIComponent(q)}`);
  const host = document.getElementById('nc-results');
  const rows = res.users.filter((u) => u.id !== meId && !selected.has(u.id)).map((u) => {
    const b = document.createElement('button');
    b.type = 'button';
    b.className = 'nc-row';
    b.setAttribute('role', 'option');
    b.textContent = `${u.name} — ${u.email}`;
    b.addEventListener('click', () => {
      selected.set(u.id, u);
      renderSelected();
      searchPeople(); // refresh list so picked people disappear
    });
    return b;
  });
  host.replaceChildren(...rows);
  if (!rows.length) {
    host.innerHTML = '<p class="nc-empty">No matches — try a name or email.</p>';
  }
}
document.getElementById('nc-search').addEventListener('input', () => {
  clearTimeout(searchTimer);
  searchTimer = setTimeout(searchPeople, 250);
});

function renderSelected() {
  const host = document.getElementById('nc-selected');
  host.replaceChildren(...[...selected.values()].map((u) => {
    const chip = document.createElement('span');
    chip.className = 'badge';
    chip.textContent = u.name + ' ×';
    chip.style.cursor = 'pointer';
    chip.addEventListener('click', () => { selected.delete(u.id); renderSelected(); });
    return chip;
  }));
}

document.getElementById('newconv-form').addEventListener('submit', async (e) => {
  e.preventDefault();
  try {
    const kind = document.getElementById('nc-kind').value;
    const res = await api('POST', '/api/v1/conversations', {
      kind,
      title: document.getElementById('nc-title').value.trim(),
      customerId: document.getElementById('nc-customer').value.trim(),
      participantIds: [...selected.keys()],
    });
    dlg.close();
    await loadConversations();
    const conv = conversations.find((c) => c.id === res.conversation.id);
    if (conv) openConversation(conv);
  } catch (err) { toast(err.message, 'err'); }
});

// ---- manage members (group creator only) ----
const membersDlg = document.getElementById('members-dialog');
let mmMemberIds = new Set();

async function renderMembers() {
  const res = await api('GET', `/api/v1/conversations/${current.id}/members`);
  mmMemberIds = new Set(res.members.map((u) => u.id));
  const host = document.getElementById('mm-list');
  host.replaceChildren(...res.members.map((u) => {
    const li = document.createElement('li');
    li.className = 'conv-item';
    li.style.justifyContent = 'space-between';
    li.innerHTML = `<span>${esc(u.name)} ${u.isCreator ? '<span class="badge accent">creator</span>' : ''}
      <br><span class="muted">${esc(u.email)}</span></span>`;
    if (res.canManage && !u.isCreator) {
      const rm = document.createElement('button');
      rm.className = 'btn small';
      rm.textContent = 'Remove';
      rm.addEventListener('click', async () => {
        try {
          await api('DELETE', `/api/v1/conversations/${current.id}/members/${u.id}`);
          await renderMembers();
        } catch (e) { toast(e.message, 'err'); }
      });
      li.appendChild(rm);
    }
    return li;
  }));
  document.getElementById('mm-add-wrap').hidden = !res.canManage;
}

let mmTimer = null;
async function mmSearch() {
  const q = document.getElementById('mm-search').value.trim();
  const res = await api('GET', `/api/v1/users?q=${encodeURIComponent(q)}`);
  const host = document.getElementById('mm-results');
  const rows = res.users.filter((u) => !mmMemberIds.has(u.id)).map((u) => {
    const b = document.createElement('button');
    b.type = 'button';
    b.className = 'nc-row';
    b.textContent = `${u.name} — ${u.email}`;
    b.addEventListener('click', async () => {
      try {
        await api('POST', `/api/v1/conversations/${current.id}/members`, { userId: u.id });
        document.getElementById('mm-search').value = '';
        host.replaceChildren();
        await renderMembers();
      } catch (e) { toast(e.message, 'err'); }
    });
    return b;
  });
  host.replaceChildren(...rows);
}

document.getElementById('chat-members').addEventListener('click', async () => {
  if (!current) return;
  document.getElementById('mm-search').value = '';
  document.getElementById('mm-results').replaceChildren();
  try { await renderMembers(); membersDlg.showModal(); }
  catch (e) { toast(e.message, 'err'); }
});
document.getElementById('mm-close').addEventListener('click', () => membersDlg.close());
document.getElementById('mm-search').addEventListener('input', () => {
  clearTimeout(mmTimer);
  mmTimer = setTimeout(mmSearch, 250);
});

(async () => {
  try {
    const me = await api('GET', '/api/v1/auth/me');
    meId = me.user.id;
  } catch (e) { /* page guard handles */ }
  loadConversations();
})();
