const ext = (typeof chrome !== 'undefined')
  ? chrome
  : (typeof browser !== 'undefined' ? browser : null);
if (!ext) {
  throw new Error('SSForum Overlay: extension API not found.');
}

const params = new URLSearchParams(window.location.search);
const SITE_BASE = String(API_BASE || '').replace(/\/api\/?$/, '');
function normalizeDomain(domain) {
  let d = (domain || '').toLowerCase().trim();
  if (d.startsWith('www.')) d = d.slice(4);
  return d || 'unknown';
}

const domain = normalizeDomain(params.get('domain'));

const domainLabel = document.getElementById('domainLabel');
const loginBtn = document.getElementById('loginBtn');
const topicsList = document.getElementById('topicsList');
const topicView = document.getElementById('topicView');
const createTopicBtn = document.getElementById('createTopicBtn');
const topicTitle = document.getElementById('topicTitle');
const topicTags = document.getElementById('topicTags');
const topicFirstCommentEditorEl = document.getElementById('topicFirstCommentEditor');
const searchInput = document.getElementById('searchInput');
const toast = document.getElementById('toast');
const userBox = document.getElementById('userBox');
const positionBtn = document.getElementById('positionBtn');
const positionMenu = document.getElementById('positionMenu');
const posButtons = Array.from(document.querySelectorAll('.pos-btn'));
const toggleCreateTopic = document.getElementById('toggleCreateTopic');
const createTopicHead = document.getElementById('createTopicHead');
const createTopicBody = document.getElementById('createTopicBody');
const versionLabel = document.getElementById('versionLabel');
const createTopicAuthNotice = document.getElementById('createTopicAuthNotice');
const createTopicFields = document.getElementById('createTopicFields');
const versionNotice = document.getElementById('versionNotice');
const versionNoticeText = document.getElementById('versionNoticeText');
const versionNoticeLink = document.getElementById('versionNoticeLink');
const scopeSwitch = document.getElementById('scopeSwitch');
const scopeCountrySelect = document.getElementById('scopeCountrySelect');
const modeSwitch = document.getElementById('modeSwitch');
const chatSection = document.getElementById('chatSection');
const createTopicSection = document.getElementById('createTopicSection');
const chatTopicSelect = document.getElementById('chatTopicSelect');
const chatNewTopicBtn = document.getElementById('chatNewTopicBtn');
const chatCreateBox = document.getElementById('chatCreateBox');
const chatNewTopicTitle = document.getElementById('chatNewTopicTitle');
const chatCreateTopicConfirmBtn = document.getElementById('chatCreateTopicConfirmBtn');
const chatCreateTopicCancelBtn = document.getElementById('chatCreateTopicCancelBtn');
const chatCreateTopicNotice = document.getElementById('chatCreateTopicNotice');
const chatMessages = document.getElementById('chatMessages');
const chatInput = document.getElementById('chatInput');
const chatSendBtn = document.getElementById('chatSendBtn');
const chatStatus = document.getElementById('chatStatus');
const chatReplyBar = document.getElementById('chatReplyBar');

let currentTopicId = null;
let jwtCache = null;
let listState = { mode: 'topics', q: '', page: 1, limit: 20, total: 0 };
let userCache = null;
let topicFirstCommentEditor = null;
let extensionVersion = '';
let versionHardBlocked = false;
let uiMode = 'forum';
let chatState = { topics: [], topicId: null, lastId: 0, running: false, allowUserTopics: true, seen: new Set(), replyTo: null };
let chatPollToken = 0;
const hasRuLocale = (() => {
  try {
    const langs = Array.isArray(navigator.languages) && navigator.languages.length
      ? navigator.languages
      : [navigator.language || navigator.userLanguage || ''];
    return langs.some((v) => {
      const s = String(v || '').toLowerCase();
      return s === 'ru' || s.startsWith('ru-');
    });
  } catch {
    return false;
  }
})();
let languageContour = hasRuLocale ? 'RU' : 'EN';
let countryScope = 'GLOBAL';
const I18N = {
  RU: {
    loginTelegram: 'Войти через Telegram',
    logout: 'Выйти',
    downloadUpdate: 'Скачать обновление',
    extTooOld: 'Версия расширения слишком старая (v{current}). Нужна минимум v{min}.',
    extUpdateAvailable: 'Доступна новая версия расширения: v{latest} (у вас v{current}).',
    updateRequired: 'Обновите расширение до актуальной версии.',
    authStart: 'Запуск...',
    authError: 'Ошибка',
    authErrorMsg: 'Ошибка авторизации',
    authWait: 'Ожидание...',
    authOpenTelegram: 'Откройте Telegram и нажмите Start',
    authDone: 'Готово',
    authLoggedIn: 'Вы вошли',
    authTimeout: 'Таймаут',
    authTimeoutMsg: 'Таймаут авторизации',
    loggedOut: 'Вы вышли',
    noTopics: 'Пока нет тем',
    representative: 'Представитель',
    comments: 'Комментарии',
    loadMore: 'Загрузить еще',
    backToTopics: 'Назад к темам',
    starterPost: 'Стартовый пост',
    needAuthComments: 'Для комментариев нужна авторизация через Telegram.',
    send: 'Отправить',
    commentAdded: 'Комментарий добавлен',
    sendError: 'Ошибка при отправке',
    yourComment: 'Ваш комментарий',
    replies: 'ответов',
    reply: 'Ответить',
    replyAdded: 'Ответ добавлен',
    yourReply: 'Ответ',
    needAuthReply: 'Для ответа нужна авторизация через Telegram.',
    imageUnavailable: 'Изображение недоступно',
    openSource: 'Открыть источник',
    loginFirst: 'Сначала войдите через Telegram',
    needTitleFirstComment: 'Нужны заголовок и первый комментарий',
    topicCreated: 'Тема создана',
    topicCreateError: 'Ошибка при создании',
    firstCommentRequired: 'Первый комментарий (обязателен)',
    createTopic: 'Создать тему',
    createTopicAuthNotice: 'Для создания темы нужна авторизация через Telegram.',
    topicTitlePlaceholder: 'Заголовок темы',
    topicTagsPlaceholder: 'Теги (через запятую)',
    searchPlaceholder: 'Поиск по темам или тегам',
    modeForum: 'Форум',
    modeChat: 'Чат',
    chatNewTopic: '+ Тема',
    chatTopicTitlePh: 'Название темы',
    chatCreate: 'Создать',
    chatCancel: 'Отмена',
    chatMessagePh: 'Сообщение...',
    chatSend: 'Отправить',
    chatNeedAuth: 'Для чата нужна авторизация через Telegram.',
    chatNoTopics: 'Нет тем чата. Создайте первую тему.',
    chatTopicCreateDisabled: 'Создание тем чата запрещено админом.',
    chatNeedTopic: 'Сначала выберите (или создайте) тему чата.',
    chatDefaultAuto: 'Сообщение будет отправлено в общий чат (создастся автоматически).',
    chatReply: 'Ответить',
    chatReplyingTo: 'Ответ пользователю {user}',
    chatReplyCleared: 'Ответ отменен',
    chatLoading: 'Загрузка...'
  },
  EN: {
    loginTelegram: 'Sign in with Telegram',
    logout: 'Sign out',
    downloadUpdate: 'Download update',
    extTooOld: 'Extension version is too old (v{current}). Minimum required is v{min}.',
    extUpdateAvailable: 'A new extension version is available: v{latest} (you have v{current}).',
    updateRequired: 'Please update the extension to the latest version.',
    authStart: 'Starting...',
    authError: 'Error',
    authErrorMsg: 'Authorization error',
    authWait: 'Waiting...',
    authOpenTelegram: 'Open Telegram and press Start',
    authDone: 'Done',
    authLoggedIn: 'Signed in',
    authTimeout: 'Timeout',
    authTimeoutMsg: 'Authorization timeout',
    loggedOut: 'Signed out',
    noTopics: 'No topics yet',
    representative: 'Representative',
    comments: 'Comments',
    loadMore: 'Load more',
    backToTopics: 'Back to topics',
    starterPost: 'Starter post',
    needAuthComments: 'Telegram sign-in is required to comment.',
    send: 'Send',
    commentAdded: 'Comment added',
    sendError: 'Send failed',
    yourComment: 'Your comment',
    replies: 'replies',
    reply: 'Reply',
    replyAdded: 'Reply added',
    yourReply: 'Reply',
    needAuthReply: 'Telegram sign-in is required to reply.',
    imageUnavailable: 'Image unavailable',
    openSource: 'Open source',
    loginFirst: 'Sign in with Telegram first',
    needTitleFirstComment: 'Title and first comment are required',
    topicCreated: 'Topic created',
    topicCreateError: 'Failed to create topic',
    firstCommentRequired: 'First comment (required)',
    createTopic: 'Create topic',
    createTopicAuthNotice: 'Telegram sign-in is required to create a topic.',
    topicTitlePlaceholder: 'Topic title',
    topicTagsPlaceholder: 'Tags (comma-separated)',
    searchPlaceholder: 'Search topics or tags',
    modeForum: 'Forum',
    modeChat: 'Chat',
    chatNewTopic: '+ Topic',
    chatTopicTitlePh: 'Topic title',
    chatCreate: 'Create',
    chatCancel: 'Cancel',
    chatMessagePh: 'Message...',
    chatSend: 'Send',
    chatNeedAuth: 'Telegram sign-in is required for chat.',
    chatNoTopics: 'No chat topics yet. Create the first topic.',
    chatTopicCreateDisabled: 'Chat topic creation is disabled by admin.',
    chatNeedTopic: 'Select (or create) a chat topic first.',
    chatDefaultAuto: 'Your message will go to the default chat (created automatically).',
    chatReply: 'Reply',
    chatReplyingTo: 'Replying to {user}',
    chatReplyCleared: 'Reply cleared',
    chatLoading: 'Loading...'
  }
};
function t(key, vars) {
  const table = I18N[languageContour] || I18N.RU;
  let s = table[key] || I18N.RU[key] || key;
  if (vars) {
    Object.keys(vars).forEach((k) => {
      s = s.replace(new RegExp('\\{' + k + '\\}', 'g'), String(vars[k]));
    });
  }
  return s;
}

function createEditor(element, placeholder) {
  if (!element || !window.SSForumEditor || typeof window.SSForumEditor.create !== 'function') return null;
  return window.SSForumEditor.create(element, { placeholder: placeholder || '', locale: languageContour });
}

function rebuildCreateTopicEditor() {
  if (!topicFirstCommentEditorEl) return;
  if (!(window.SSForumEditor && typeof window.SSForumEditor.create === 'function')) return;
  if (topicFirstCommentEditor && typeof topicFirstCommentEditor.destroy === 'function') {
    topicFirstCommentEditor.destroy();
    topicFirstCommentEditor = null;
  }
  topicFirstCommentEditor = createEditor(topicFirstCommentEditorEl, t('firstCommentRequired'));
}

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

function normalizeLc(v) {
  const lc = String(v || '').toUpperCase();
  return lc === 'EN' ? 'EN' : 'RU';
}

function normalizeCs(v) {
  const cs = String(v || '').toUpperCase();
  if (cs === 'GLOBAL') return 'GLOBAL';
  return /^[A-Z]{2,8}$/.test(cs) ? cs : 'GLOBAL';
}

function scopeQuery() {
  return 'lc=' + encodeURIComponent(languageContour) + '&cs=' + encodeURIComponent(countryScope);
}

function applyScopeUi() {
  if (scopeSwitch) {
    const ruChip = scopeSwitch.querySelector('[data-lc="RU"]');
    if (ruChip) ruChip.hidden = !hasRuLocale;
    if (!hasRuLocale && languageContour === 'RU') languageContour = 'EN';
    const chips = scopeSwitch.querySelectorAll('[data-lc]');
    chips.forEach((chip) => {
      chip.classList.toggle('active', normalizeLc(chip.dataset.lc) === languageContour);
    });
  }
  if (scopeCountrySelect) {
    scopeCountrySelect.value = countryScope;
  }
  applyTranslations();
}

function applyTranslations() {
  if (userCache) {
    renderUser(userCache);
  } else if (loginBtn) {
    loginBtn.textContent = t('loginTelegram');
  }
  if (versionNoticeLink) versionNoticeLink.textContent = t('downloadUpdate');
  const modeForumBtn = modeSwitch ? modeSwitch.querySelector('[data-mode=\"forum\"]') : null;
  const modeChatBtn = modeSwitch ? modeSwitch.querySelector('[data-mode=\"chat\"]') : null;
  if (modeForumBtn) modeForumBtn.textContent = t('modeForum');
  if (modeChatBtn) modeChatBtn.textContent = t('modeChat');
  const createTitleEl = document.getElementById('createTopicTitle');
  if (createTitleEl) createTitleEl.textContent = t('createTopic');
  if (createTopicAuthNotice) createTopicAuthNotice.textContent = t('createTopicAuthNotice');
  if (topicTitle) topicTitle.placeholder = t('topicTitlePlaceholder');
  if (topicTags) topicTags.placeholder = t('topicTagsPlaceholder');
  if (createTopicBtn) createTopicBtn.textContent = t('createTopic');
  if (searchInput) searchInput.placeholder = t('searchPlaceholder');
  if (chatNewTopicBtn) chatNewTopicBtn.textContent = t('chatNewTopic');
  if (chatNewTopicTitle) chatNewTopicTitle.placeholder = t('chatTopicTitlePh');
  if (chatCreateTopicConfirmBtn) chatCreateTopicConfirmBtn.textContent = t('chatCreate');
  if (chatCreateTopicCancelBtn) chatCreateTopicCancelBtn.textContent = t('chatCancel');
  if (chatInput) chatInput.placeholder = t('chatMessagePh');
  if (chatSendBtn) {
    const lbl = t('chatSend');
    chatSendBtn.innerHTML = '➤';
    chatSendBtn.setAttribute('aria-label', lbl);
    chatSendBtn.title = lbl;
  }
}

// Show extension version
try {
  const v = ext.runtime && typeof ext.runtime.getManifest === 'function'
    ? (ext.runtime.getManifest().version || '')
    : '';
  extensionVersion = String(v || '');
  if (versionLabel && v) {
    versionLabel.textContent = 'v' + v;
  }
} catch {
  // ignore
}

function parseVersion(v) {
  return String(v || '')
    .split('.')
    .map((n) => parseInt(n, 10))
    .map((n) => (Number.isFinite(n) ? n : 0));
}

function compareVersions(a, b) {
  const x = parseVersion(a);
  const y = parseVersion(b);
  const len = Math.max(x.length, y.length, 3);
  for (let i = 0; i < len; i++) {
    const xv = x[i] || 0;
    const yv = y[i] || 0;
    if (xv > yv) return 1;
    if (xv < yv) return -1;
  }
  return 0;
}

function showVersionNotice(text, downloadUrl, hard) {
  if (!versionNotice || !versionNoticeText || !versionNoticeLink) return;
  versionNoticeText.textContent = text;
  versionNoticeLink.href = downloadUrl || (SITE_BASE + '/?download=extension');
  versionNotice.classList.remove('hidden');
  versionNotice.classList.toggle('is-hard', !!hard);
}

function safeStorageGet(keys, onDone) {
  try {
    ext.storage.local.get(keys, (res) => {
      try {
        if (ext.runtime && ext.runtime.lastError) {
          onDone({});
          return;
        }
      } catch (_) {}
      onDone(res || {});
    });
  } catch (_) {
    onDone({});
  }
}

function safeStorageSet(data, onDone) {
  try {
    ext.storage.local.set(data, () => {
      try {
        if (ext.runtime && ext.runtime.lastError) {
          onDone(false);
          return;
        }
      } catch (_) {}
      onDone(true);
    });
  } catch (_) {
    onDone(false);
  }
}

function safeStorageRemove(keys, onDone) {
  try {
    ext.storage.local.remove(keys, () => {
      try {
        if (ext.runtime && ext.runtime.lastError) {
          onDone(false);
          return;
        }
      } catch (_) {}
      onDone(true);
    });
  } catch (_) {
    onDone(false);
  }
}

async function checkExtensionVersion() {
  try {
    const info = await apiFetch('/extension/version');
    const latest = String(info.latest || '').trim();
    const minRequired = String(info.minimum_required || '').trim();
    const guideUrl = String(info.update_guide_url || '').trim();
    const downloadUrl = String(info.download_url || '').trim();
    const targetUrl = guideUrl || downloadUrl;
    if (!latest || !extensionVersion) return;

    if (minRequired && compareVersions(extensionVersion, minRequired) < 0) {
      versionHardBlocked = true;
      showVersionNotice(
        t('extTooOld', { current: extensionVersion, min: minRequired }),
        targetUrl,
        true
      );
      return;
    }

    if (compareVersions(extensionVersion, latest) < 0) {
      showVersionNotice(
        t('extUpdateAvailable', { latest: latest, current: extensionVersion }),
        targetUrl,
        false
      );
    }
  } catch {
    // ignore version check errors
  }
}

function getJwt() {
  return new Promise((resolve) => {
    safeStorageGet(['jwt'], (res) => {
      jwtCache = res.jwt || null;
      resolve(jwtCache);
    });
  });
}

function setJwt(token) {
  return new Promise((resolve) => {
    safeStorageSet({ jwt: token }, () => {
      jwtCache = token;
      resolve();
    });
  });
}

function clearJwt() {
  return new Promise((resolve) => {
    safeStorageRemove(['jwt'], () => {
      jwtCache = null;
      resolve();
    });
  });
}

function setUser(user) {
  return new Promise((resolve) => {
    safeStorageSet({ user }, () => {
      userCache = user;
      resolve();
    });
  });
}

function clearUser() {
  return new Promise((resolve) => {
    safeStorageRemove(['user'], () => {
      userCache = null;
      resolve();
    });
  });
}

function getUser() {
  return new Promise((resolve) => {
    safeStorageGet(['user'], (res) => {
      userCache = res.user || null;
      resolve(userCache);
    });
  });
}

function getScope() {
  return new Promise((resolve) => {
    safeStorageGet(['languageContour', 'countryScope'], (res) => {
      languageContour = normalizeLc(res.languageContour || languageContour);
      if (!hasRuLocale && languageContour === 'RU') languageContour = 'EN';
      countryScope = 'GLOBAL';
      resolve({ languageContour, countryScope });
    });
  });
}

function setScope(lc, cs) {
  languageContour = normalizeLc(lc);
  if (!hasRuLocale && languageContour === 'RU') languageContour = 'EN';
  countryScope = 'GLOBAL';
  return new Promise((resolve) => {
    safeStorageSet({ languageContour, countryScope }, () => resolve());
  });
}

async function apiFetch(path, options = {}) {
  const method = String(options.method || 'GET').toUpperCase();
  if (versionHardBlocked && method !== 'GET') {
    return { error: t('updateRequired') };
  }
  const headers = options.headers || {};
  headers['Content-Type'] = 'application/json';
  const jwt = jwtCache || (await getJwt());
  if (jwt) {
    headers['Authorization'] = 'Bearer ' + jwt;
  }
  const resp = await fetch(API_BASE + path, { ...options, headers });
  return resp.json();
}

function renderUser(user) {
  userBox.innerHTML = '';
  if (!user) {
    userBox.classList.remove('auth');
    loginBtn.disabled = false;
    loginBtn.textContent = t('loginTelegram');
    userBox.appendChild(loginBtn);
    syncCreateTopicAccess(null);
    syncChatComposeState();
    return;
  }
  userBox.classList.add('auth');
  const avatar = avatarEl(user);
  const name = document.createElement('div');
  name.className = 'user-name';
  name.textContent = user.username || 'user';
  const logoutBtn = document.createElement('button');
  logoutBtn.className = 'btn btn-ghost btn-sm';
  logoutBtn.type = 'button';
  logoutBtn.textContent = t('logout');
  logoutBtn.addEventListener('click', logoutFlow);
  userBox.appendChild(avatar);
  userBox.appendChild(name);
  userBox.appendChild(logoutBtn);
  syncCreateTopicAccess(user);
  if (chatNewTopicBtn) {
    chatNewTopicBtn.disabled = !canCreateChatTopic();
  }
  syncChatComposeState();
}

function syncCreateTopicAccess(user) {
  const authed = !!user;
  if (createTopicAuthNotice) {
    createTopicAuthNotice.classList.toggle('hidden', authed);
  }
  if (createTopicFields) {
    createTopicFields.classList.toggle('hidden', !authed);
  }
}

function setStatus(text) {
  loginBtn.textContent = text;
}

function notify(message, type = 'success') {
  toast.textContent = message;
  toast.className = 'toast ' + type;
  toast.classList.remove('hidden');
  clearTimeout(toast.__t);
  toast.__t = setTimeout(() => toast.classList.add('hidden'), 2500);
}

function setUiMode(next) {
  const mode = (next === 'chat') ? 'chat' : 'forum';
  uiMode = mode;
  const app = document.getElementById('app');
  if (app) app.classList.toggle('mode-chat', mode === 'chat');
  if (modeSwitch) {
    const btns = modeSwitch.querySelectorAll('[data-mode]');
    btns.forEach((b) => b.classList.toggle('active', String(b.dataset.mode) === mode));
  }
  if (chatSection) {
    chatSection.style.display = mode === 'chat' ? 'block' : 'none';
    chatSection.classList.toggle('hidden', mode !== 'chat');
  }
  // forum-only sections
  if (createTopicSection) createTopicSection.style.display = mode === 'forum' ? 'block' : 'none';
  if (searchInput && searchInput.closest('.section')) searchInput.closest('.section').style.display = mode === 'forum' ? 'block' : 'none';
  if (topicsList) topicsList.style.display = mode === 'forum' && !currentTopicId ? 'block' : 'none';
  if (topicView) topicView.style.display = mode === 'forum' && currentTopicId ? 'block' : 'none';

  if (mode !== 'chat') {
    stopChatStream();
  } else {
    loadChatTopics(true).catch(() => {});
  }
}

function stopChatStream() {
  chatState.running = false;
  chatPollToken += 1;
}

function setChatStatus(text) {
  if (!chatStatus) return;
  chatStatus.textContent = String(text || '');
}

function canCreateChatTopic() {
  const authed = !!(jwtCache && userCache);
  if (!authed) return false;
  const isRep = Array.isArray(userCache.representative_domains) && userCache.representative_domains.includes(domain);
  const isAdmin = !!userCache.is_admin;
  return !!(chatState.allowUserTopics || isRep || isAdmin);
}

function syncChatComposeState() {
  const authed = !!(jwtCache && userCache);
  const hasTopic = !!chatState.topicId;
  // If no topic exists yet, allow sending: backend will create default topic on first message.
  if (chatSendBtn) chatSendBtn.disabled = !authed;
  if (chatInput) {
    chatInput.disabled = !authed;
    if (!authed) chatInput.placeholder = t('chatNeedAuth');
    else if (!hasTopic) chatInput.placeholder = t('chatDefaultAuto');
    else chatInput.placeholder = t('chatMessagePh');
  }
}

function renderChatReplyBar() {
  if (!chatReplyBar) return;
  const rt = chatState.replyTo;
  if (!rt) {
    chatReplyBar.classList.add('hidden');
    chatReplyBar.innerHTML = '';
    return;
  }
  chatReplyBar.classList.remove('hidden');
  const who = rt.username ? String(rt.username) : 'user';
  chatReplyBar.innerHTML = '<div class="txt">' + esc(t('chatReplyingTo', { user: who })) + '</div>' +
    '<button type="button" class="x" aria-label="Cancel" title="Cancel">×</button>';
  const btn = chatReplyBar.querySelector('button.x');
  if (btn) {
    btn.addEventListener('click', () => {
      chatState.replyTo = null;
      renderChatReplyBar();
      syncChatComposeState();
      notify(t('chatReplyCleared'), 'success');
    }, { once: true });
  }
}

function setChatReplyTo(message) {
  if (!message || !message.id) return;
  const u = message.user || {};
  chatState.replyTo = {
    id: Number(message.id),
    username: u.username ? String(u.username) : 'user',
    user_id: u.id ? Number(u.id) : 0,
  };
  renderChatReplyBar();
  syncChatComposeState();
  if (chatInput) chatInput.focus();
}

function renderChatTopicOptions() {
  if (!chatTopicSelect) return;
  chatTopicSelect.innerHTML = '';
  const topics = Array.isArray(chatState.topics) ? chatState.topics : [];
  if (!topics.length) {
    chatState.topicId = 0;
    const opt = document.createElement('option');
    opt.value = '';
    opt.textContent = t('chatNoTopics');
    chatTopicSelect.appendChild(opt);
    syncChatComposeState();
    return;
  }
  topics.forEach((tp) => {
    const opt = document.createElement('option');
    opt.value = String(tp.id);
    opt.textContent = tp.title + ' (' + Number(tp.messages_count || 0) + ')';
    chatTopicSelect.appendChild(opt);
  });
  if (chatState.topicId) {
    chatTopicSelect.value = String(chatState.topicId);
  } else if (topics[0]) {
    chatTopicSelect.value = String(topics[0].id);
  }
  syncChatComposeState();
}

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

function renderChatMessages(list, reset) {
  if (!chatMessages) return;
  if (reset) {
    chatMessages.innerHTML = '';
    chatState.seen = new Set();
  }
  const atBottom = Math.abs(chatMessages.scrollHeight - chatMessages.scrollTop - chatMessages.clientHeight) < 40;
  (list || []).forEach((m) => {
    const mid = Number(m && m.id ? m.id : 0);
    if (mid && chatState.seen && chatState.seen.has(mid)) return;
    if (mid && chatState.seen) chatState.seen.add(mid);
    const row = document.createElement('div');
    row.className = 'chat-msg';
    row.dataset.chatMid = String(mid || '');
    row.appendChild(avatarEl(m.user || null));
    const main = document.createElement('div');
    main.className = 'chat-msg-main';
    const meta = document.createElement('div');
    meta.className = 'chat-msg-meta';
    meta.innerHTML = '<span class="chat-msg-name">' + esc(m.user && m.user.username ? m.user.username : 'user') + '</span>' +
      '<span>•</span>' + esc(m.created_at || '');
    const text = document.createElement('div');
    text.className = 'chat-msg-text';
    if (m.reply && m.reply.id) {
      const prev = document.createElement('div');
      prev.className = 'chat-reply-preview';
      const who = (m.reply.user && m.reply.user.username) ? m.reply.user.username : 'user';
      const snippet = m.reply.text ? String(m.reply.text) : '';
      prev.innerHTML = '↪ <span class="who">' + esc(who) + '</span>' + (snippet ? (': ' + esc(snippet)) : '');
      prev.addEventListener('click', (e) => {
        e.preventDefault();
        e.stopPropagation();
        const el = chatMessages.querySelector('[data-chat-mid=\"' + String(m.reply.id) + '\"]');
        if (el) el.scrollIntoView({ block: 'center' });
      });
      main.appendChild(prev);
    }
    text.innerHTML = m.html || esc(m.text || '');
    main.appendChild(meta);
    main.appendChild(text);
    row.appendChild(main);
    const replyBtn = document.createElement('button');
    replyBtn.type = 'button';
    replyBtn.className = 'chat-reply-btn';
    replyBtn.textContent = t('chatReply');
    replyBtn.title = t('chatReply');
    replyBtn.addEventListener('click', (e) => {
      e.preventDefault();
      e.stopPropagation();
      setChatReplyTo(m);
    });
    row.appendChild(replyBtn);
    row.addEventListener('click', () => {
      // For touch devices: tap to show reply button.
      const prevActive = chatMessages.querySelector('.chat-msg.is-active');
      if (prevActive && prevActive !== row) prevActive.classList.remove('is-active');
      row.classList.toggle('is-active');
    });
    chatMessages.appendChild(row);
  });
  if (atBottom) {
    chatMessages.scrollTop = chatMessages.scrollHeight;
  }
}

async function loadChatTopics(autoSelect) {
  if (!chatSection) return;
  setChatStatus(t('chatLoading'));
  const data = await apiFetch('/chat/topics?domain=' + encodeURIComponent(domain) + '&' + scopeQuery());
  if (data && data.error) {
    setChatStatus(String(data.error));
    return;
  }
  chatState.topics = data.topics || [];
  chatState.allowUserTopics = data.allow_user_topics !== undefined ? !!data.allow_user_topics : true;
  renderChatTopicOptions();
  if (chatNewTopicBtn) {
    chatNewTopicBtn.disabled = !canCreateChatTopic();
  }
  if (chatCreateTopicNotice) {
    const shouldShow = !chatState.allowUserTopics && !(userCache && (userCache.is_admin || (userCache.representative_domains || []).includes(domain)));
    chatCreateTopicNotice.classList.toggle('hidden', !shouldShow);
    chatCreateTopicNotice.textContent = t('chatTopicCreateDisabled');
  }
  setChatStatus('');
  if (autoSelect) {
    const val = chatTopicSelect ? String(chatTopicSelect.value || '') : '';
    const nextId = val ? Number(val) : (chatState.topics[0] ? Number(chatState.topics[0].id) : 0);
    if (nextId) {
      await openChatTopic(nextId);
    } else {
      chatState.topicId = 0;
      stopChatStream();
      if (chatMessages) chatMessages.innerHTML = '';
      syncChatComposeState();
    }
  }
}

async function openChatTopic(topicId) {
  const id = Number(topicId || 0);
  if (!id) return;
  chatState.topicId = id;
  chatState.lastId = 0;
  chatState.seen = new Set();
  renderChatTopicOptions();
  if (chatMessages) chatMessages.innerHTML = '';
  setChatStatus(t('chatLoading'));
  const first = await apiFetch('/chat/topics/' + id + '/messages?after_id=0&tail=1&limit=50&wait=0&' + scopeQuery());
  if (first && first.error) {
    setChatStatus(String(first.error));
    return;
  }
  const msgs = first.messages || [];
  if (msgs.length) chatState.lastId = Number(msgs[msgs.length - 1].id || 0);
  renderChatMessages(msgs, true);
  setChatStatus('');
  syncChatComposeState();
  renderChatReplyBar();
  startChatStream();
}

function startChatStream() {
  stopChatStream();
  chatState.running = true;
  const token = ++chatPollToken;
  (async function loop() {
    while (chatState.running && token === chatPollToken && uiMode === 'chat' && chatState.topicId) {
      const after = chatState.lastId || 0;
      const res = await apiFetch('/chat/topics/' + chatState.topicId + '/messages?after_id=' + after + '&wait=20&limit=200&' + scopeQuery());
      if (!chatState.running || token !== chatPollToken || uiMode !== 'chat') return;
      if (res && res.error) {
        setChatStatus(String(res.error));
        await new Promise((r) => setTimeout(r, 1200));
        continue;
      }
      const msgs = res.messages || [];
      if (msgs.length) {
        chatState.lastId = Number(msgs[msgs.length - 1].id || chatState.lastId);
        renderChatMessages(msgs, false);
      }
    }
  })();
}

function avatarEl(user) {
  if (user && user.avatar) {
    const img = document.createElement('img');
    img.className = 'avatar';
    img.src = user.avatar;
    img.alt = user.username || 'user';
    img.onerror = () => {
      // Avoid broken-image icons in UI
      const fallback = document.createElement('span');
      fallback.className = 'avatar-fallback';
      const name = (user && user.username) ? user.username : 'U';
      fallback.textContent = name.substring(0, 1).toUpperCase();
      img.replaceWith(fallback);
    };
    return img;
  }
  const fallback = document.createElement('span');
  fallback.className = 'avatar-fallback';
  const name = (user && user.username) ? user.username : 'U';
  fallback.textContent = name.substring(0, 1).toUpperCase();
  return fallback;
}

async function loginFlow() {
  setStatus(t('authStart'));
  const init = await apiFetch('/auth/telegram/init', { method: 'POST' });
  if (!init.start_url || !init.token) {
    setStatus(t('authError'));
    notify(t('authErrorMsg'), 'error');
    return;
  }

  openAuthTab(init.start_url);
  setStatus(t('authWait'));
  notify(t('authOpenTelegram'), 'success');

  const startedAt = Date.now();
  const interval = setInterval(async () => {
    const status = await apiFetch('/auth/telegram/status?token=' + encodeURIComponent(init.token));
    if (status.status === 'ok' && status.jwt) {
      clearInterval(interval);
      await setJwt(status.jwt);
      if (status.user) {
        await setUser(status.user);
        renderUser(status.user);
      }
      setStatus(t('authDone'));
      notify(t('authLoggedIn'), 'success');
      if (currentTopicId) {
        await loadTopic(currentTopicId);
      }
    }
    if (Date.now() - startedAt > 5 * 60 * 1000) {
      clearInterval(interval);
      setStatus(t('authTimeout'));
      notify(t('authTimeoutMsg'), 'error');
    }
  }, 2000);
}

async function logoutFlow() {
  await clearJwt();
  await clearUser();
  renderUser(null);
  notify(t('loggedOut'), 'success');
  if (uiMode === 'chat') {
    if (chatNewTopicBtn) chatNewTopicBtn.disabled = true;
  }
  if (currentTopicId) {
    await loadTopic(currentTopicId);
  } else if (listState.mode === 'search' && listState.q) {
    await searchTopics(true);
  } else {
    await loadTopics(true);
  }
}

function openAuthTab(url) {
  try {
    if (ext && ext.tabs && typeof ext.tabs.create === 'function') {
      ext.tabs.create({ url }, () => {
        if (ext.runtime && ext.runtime.lastError) {
          window.open(url, '_blank', 'noopener');
        }
      });
      return;
    }
  } catch {
    // ignore and fallback
  }
  window.open(url, '_blank', 'noopener');
}

async function loadTopics(reset = true) {
  if (reset) {
    listState = { mode: 'topics', q: '', page: 1, limit: 20, total: 0 };
  }
  const data = await apiFetch('/topics?domain=' + encodeURIComponent(domain) + '&page=' + listState.page + '&limit=' + listState.limit + '&' + scopeQuery());
  listState.total = data.total || 0;
  renderTopics(data.topics || [], reset, data.total || 0, data.page || 1, data.limit || 20);
}

async function searchTopics(reset = true) {
  const q = searchInput.value.trim();
  if (!q) {
    return loadTopics(true);
  }
  if (reset) {
    listState = { mode: 'search', q, page: 1, limit: 20, total: 0 };
  }
  const data = await apiFetch('/search?q=' + encodeURIComponent(q) + '&domain=' + encodeURIComponent(domain) + '&page=' + listState.page + '&limit=' + listState.limit + '&' + scopeQuery());
  listState.total = data.total || 0;
  renderTopics(data.topics || [], reset, data.total || 0, data.page || 1, data.limit || 20);
}

function renderTopics(topics, reset, total, page, limit) {
  document.body.classList.remove('topic-open');
  if (reset) {
    topicsList.innerHTML = '';
  }
  topicView.style.display = 'none';
  topicsList.style.display = 'block';

  if (!topics.length && reset) {
    topicsList.innerHTML = '<div class="card">' + escHtml(t('noTopics')) + '</div>';
    return;
  }

  topics.forEach((topic) => {
    const card = document.createElement('div');
    card.className = 'card topic-card';

    const row = document.createElement('div');
    row.className = 'row';
    row.appendChild(avatarEl(topic.user));

    const textWrap = document.createElement('div');
    textWrap.className = 'topic-main';

    const title = document.createElement('div');
    title.className = 'topic-title';
    title.textContent = topic.title;

    const meta = document.createElement('div');
    meta.className = 'topic-meta';
    meta.innerHTML =
      escHtml(topic.user?.username || 'user') +
      (topic.user && topic.user.is_representative ? ' <span class="rep-badge">' + escHtml(t('representative')) + '</span>' : '') +
      ' • ' + escHtml(topic.created_at || '');

    const count = document.createElement('div');
    count.className = 'topic-count';
    count.title = t('comments');
    count.textContent = '💬 ' + Number(topic.comments_count ?? 0);

    textWrap.appendChild(title);
    textWrap.appendChild(meta);
    row.appendChild(textWrap);
    row.appendChild(count);

    const tags = document.createElement('div');
    tags.className = 'tags';
    (topic.tags || []).forEach((tag) => {
      const el = document.createElement('span');
      el.className = 'tag';
      el.textContent = tag;
      tags.appendChild(el);
    });

    card.appendChild(row);
    if (topic.tags && topic.tags.length) card.appendChild(tags);

    card.addEventListener('click', () => loadTopic(topic.id));
    topicsList.appendChild(card);
  });

  const already = page * limit;
  const hasMore = already < total;
  const existing = document.getElementById('loadMoreBtn');
  if (existing) existing.remove();

  if (hasMore) {
    const btn = document.createElement('button');
    btn.id = 'loadMoreBtn';
    btn.className = 'load-more';
    btn.textContent = t('loadMore');
    btn.addEventListener('click', async () => {
      listState.page += 1;
      if (listState.mode === 'search') {
        await searchTopics(false);
      } else {
        await loadTopics(false);
      }
    });
    topicsList.appendChild(btn);
  }
}

async function loadTopic(id) {
  currentTopicId = id;
  const data = await apiFetch('/topics/' + id + '?' + scopeQuery());
  const commentsData = await apiFetch('/topics/' + id + '/comments?' + scopeQuery());
  renderTopic(data, commentsData.comments || []);
}

function renderTopic(topic, comments) {
  document.body.classList.add('topic-open');
  topicsList.style.display = 'none';
  topicView.style.display = 'block';
  topicView.innerHTML = '';

  const back = document.createElement('div');
  back.className = 'link';
  back.textContent = t('backToTopics');
  back.addEventListener('click', () => {
    if (listState.mode === 'search' && listState.q) {
      searchTopics(true);
    } else {
      loadTopics(true);
    }
  });

  const card = document.createElement('div');
  card.className = 'card';

  const row = document.createElement('div');
  row.className = 'row';
  row.appendChild(avatarEl(topic.user));

  const textWrap = document.createElement('div');

  const title = document.createElement('div');
  title.className = 'topic-title';
  title.textContent = topic.title;

  const meta = document.createElement('div');
  meta.className = 'topic-meta';
  meta.innerHTML =
    escHtml(topic.user?.username || 'user') +
    (topic.user && topic.user.is_representative ? ' <span class="rep-badge">' + escHtml(t('representative')) + '</span>' : '') +
    ' • ' + escHtml(topic.created_at || '');

  textWrap.appendChild(title);
  textWrap.appendChild(meta);
  row.appendChild(textWrap);

  card.appendChild(row);

  const roots = Array.isArray(comments) ? comments : [];
  let opComment = null;
  let discussion = roots;
  if (roots.length > 0) {
    const first = roots[0];
    const topicAuthorId = Number(topic && topic.user ? topic.user.id || 0 : 0);
    const firstAuthorId = Number(first && first.user ? first.user.id || 0 : 0);
    if (topicAuthorId > 0 && firstAuthorId === topicAuthorId) {
      opComment = first;
      const fromOp = Array.isArray(first.children) ? first.children : [];
      discussion = fromOp.concat(roots.slice(1));
    }
  }

  if (opComment) {
    const leadLabel = document.createElement('div');
    leadLabel.className = 'topic-lead-label';
    leadLabel.textContent = t('starterPost');
    card.appendChild(leadLabel);

    const leadText = document.createElement('div');
    leadText.className = 'topic-comment';
    leadText.innerHTML = opComment.html || escHtml(opComment.text || '');
    card.appendChild(leadText);
  }

  const list = document.createElement('div');
  list.className = 'section';

  discussion.forEach((c) => list.appendChild(renderComment(c)));

  const form = document.createElement('div');
  form.className = 'section';
  const canPost = !!(jwtCache && userCache);

  if (!canPost) {
    const notice = document.createElement('div');
    notice.className = 'auth-notice compact';
    notice.textContent = t('needAuthComments');
    form.appendChild(notice);
  } else {
    const editorHost = document.createElement('div');
    editorHost.className = 'editor-box';
    const editorId = 'topicCommentEditor_' + String(currentTopicId) + '_' + Math.random().toString(36).slice(2, 8);
    editorHost.id = editorId;
    const btn = document.createElement('button');
    btn.className = 'btn';
    btn.textContent = t('send');
    let commentEditor = null;
    btn.addEventListener('click', async () => {
      if (!commentEditor || commentEditor.isEmpty()) return;
      const res = await apiFetch('/topics/' + currentTopicId + '/comments?' + scopeQuery(), {
        method: 'POST',
        body: JSON.stringify({ text: commentEditor.getHtml() })
      });
      if (res.id) {
        commentEditor.clear();
        notify(t('commentAdded'), 'success');
        await loadTopic(currentTopicId);
      } else {
        notify(t('sendError'), 'error');
      }
    });

    form.appendChild(editorHost);
    form.appendChild(btn);
    setTimeout(() => {
      commentEditor = createEditor(editorHost, t('yourComment'));
    }, 0);
  }

  topicView.appendChild(back);
  topicView.appendChild(card);
  topicView.appendChild(list);
  topicView.appendChild(form);
}

function renderComment(comment) {
  const wrap = document.createElement('div');
  wrap.className = 'comment';

  const row = document.createElement('div');
  row.className = 'row';
  row.appendChild(avatarEl(comment.user));

  const meta = document.createElement('div');
  meta.className = 'comment-meta';
  meta.innerHTML =
    escHtml(comment.user?.username || 'user') +
    (comment.user && comment.user.is_representative ? ' <span class="rep-badge">' + escHtml(t('representative')) + '</span>' : '') +
    ' • ' + escHtml(comment.created_at || '') +
    ' • ' + t('replies') + ': ' + Number(comment.replies_count || 0);

  row.appendChild(meta);

  const text = document.createElement('div');
  text.className = 'comment-text';
  text.innerHTML = comment.html || escHtml(comment.text || '');

  const canReply = !!(jwtCache && userCache);
  let replyLink = null;
  let replyBox = null;
  if (canReply) {
    replyLink = document.createElement('div');
    replyLink.className = 'link';
    replyLink.textContent = t('reply');

    replyBox = document.createElement('div');
    replyBox.className = 'reply-box hidden';

    const replyEditorHost = document.createElement('div');
    replyEditorHost.className = 'editor-box editor-box-inline';
    const replyEditorId = 'replyEditor_' + String(comment.id) + '_' + Math.random().toString(36).slice(2, 8);
    replyEditorHost.id = replyEditorId;
    let replyEditor = null;

    const replyBtn = document.createElement('button');
    replyBtn.className = 'btn';
    replyBtn.textContent = t('send');

    replyBtn.addEventListener('click', async () => {
      if (!replyEditor || replyEditor.isEmpty()) return;
      const res = await apiFetch('/topics/' + currentTopicId + '/comments?' + scopeQuery(), {
        method: 'POST',
        body: JSON.stringify({ text: replyEditor.getHtml(), parent_id: comment.id })
      });
      if (res.id) {
        notify(t('replyAdded'), 'success');
        await loadTopic(currentTopicId);
      } else {
        notify(t('sendError'), 'error');
      }
    });

    replyLink.addEventListener('click', () => {
      const isHidden = replyBox.classList.contains('hidden');
      if (isHidden) {
        const opened = topicView.querySelectorAll('.reply-box:not(.hidden)');
        opened.forEach((el) => {
          if (el !== replyBox) el.classList.add('hidden');
        });
        replyBox.classList.remove('hidden');
      } else {
        replyBox.classList.add('hidden');
      }
    });

    replyBox.appendChild(replyEditorHost);
    replyBox.appendChild(replyBtn);
    setTimeout(() => {
      replyEditor = createEditor(replyEditorHost, t('yourReply'));
    }, 0);
  }

  wrap.appendChild(row);
  wrap.appendChild(text);
  if (replyLink && replyBox) {
    wrap.appendChild(replyLink);
    wrap.appendChild(replyBox);
  } else {
    const notice = document.createElement('div');
    notice.className = 'auth-notice compact';
    notice.textContent = t('needAuthReply');
    wrap.appendChild(notice);
  }

  if (comment.children && comment.children.length) {
    comment.children.forEach((child) => {
      wrap.appendChild(renderComment(child));
    });
  }

  return wrap;
}

function renderRichText(container, text) {
  const urlRegex = /https?:\/\/[^\s<]+/ig;
  let lastIndex = 0;
  let match;

  const appendText = (t) => {
    if (!t) return;
    container.appendChild(document.createTextNode(t));
  };

  while ((match = urlRegex.exec(text)) !== null) {
    const raw = match[0];
    const start = match.index;
    appendText(text.slice(lastIndex, start));

    const norm = normalizeUrl(raw);
    const url = norm.url;

    if (!/^https?:\/\//i.test(url)) {
      appendText(raw);
      lastIndex = start + raw.length;
      continue;
    }

    if (isImageUrl(url)) {
      if (norm.lead) appendText(norm.lead);
      const a = document.createElement('a');
      a.href = url;
      a.target = '_blank';
      a.rel = 'noopener noreferrer nofollow';
      const img = document.createElement('img');
      img.className = 'mf-img';
      img.loading = 'lazy';
      img.decoding = 'async';
      img.referrerPolicy = 'no-referrer';
      img.src = API_BASE + '/image?url=' + encodeURIComponent(url);
      a.appendChild(img);
      container.appendChild(a);
      if (norm.trail) appendText(norm.trail);
    } else {
      const yt = youtubeId(url) || youtubeIdFromText(raw);
      if (yt) {
        if (norm.lead) appendText(norm.lead);
        container.appendChild(buildEmbed(buildWidgetYoutubeSrc(yt), 'youtube'));
        if (norm.trail) appendText(norm.trail);
      } else {
        const rt = rutubeId(url);
        if (rt) {
          if (norm.lead) appendText(norm.lead);
          container.appendChild(buildEmbed('https://rutube.ru/play/embed/' + rt, 'rutube'));
          if (norm.trail) appendText(norm.trail);
        } else {
          const vk = vkIds(url) || vkIdsFromText(raw);
          if (vk) {
            if (norm.lead) appendText(norm.lead);
            let src = 'https://vk.com/video_ext.php?oid=' + encodeURIComponent(vk.oid) + '&id=' + encodeURIComponent(vk.id) + '&hd=2';
            if (vk.hash) {
              src += '&hash=' + encodeURIComponent(vk.hash);
            }
            container.appendChild(buildEmbed(src, 'vk'));
            if (norm.trail) appendText(norm.trail);
          } else {
            const a = document.createElement('a');
            a.href = url;
            a.target = '_blank';
            a.rel = 'noopener noreferrer nofollow';
            a.textContent = url;
            if (norm.lead) appendText(norm.lead);
            container.appendChild(a);
            if (norm.trail) appendText(norm.trail);
          }
        }
      }
    }

    lastIndex = start + raw.length;
  }

  appendText(text.slice(lastIndex));
}

function isImageUrl(url) {
  try {
    const u = new URL(url);
    const path = (u.pathname || '').toLowerCase();
    return /\.(jpg|jpeg|png|gif|webp)$/.test(path);
  } catch {
    return false;
  }
}

function replaceBrokenMfImg(img) {
  if (!img || img.__mfBrokenHandled) return;
  img.__mfBrokenHandled = true;

  const a = img.closest('a');
  const href = a ? a.getAttribute('href') : '';

  const box = document.createElement('div');
  box.className = 'mf-broken';

  const title = document.createElement('div');
  title.className = 'mf-broken-title';
  title.textContent = t('imageUnavailable');

  const actions = document.createElement('div');
  actions.className = 'mf-broken-actions';

  if (href) {
    const link = document.createElement('a');
    link.className = 'mf-broken-link';
    link.href = href;
    link.target = '_blank';
    link.rel = 'noopener noreferrer nofollow';
    link.textContent = t('openSource');
    actions.appendChild(link);
  }

  box.appendChild(title);
  box.appendChild(actions);

  if (a) {
    a.replaceWith(box);
  } else {
    img.replaceWith(box);
  }
}

// Capture broken images rendered inside rich HTML/embeds.
window.addEventListener('error', (e) => {
  const t = e && e.target;
  if (!t || t.tagName !== 'IMG') return;
  if (!t.classList || !t.classList.contains('mf-img')) return;
  replaceBrokenMfImg(t);
}, true);

function normalizeUrl(raw) {
  let url = raw || '';
  let lead = '';
  let trail = '';
  while (/^[\"'\(\[\{<«»“”„]/.test(url)) {
    lead += url[0];
    url = url.slice(1);
  }
  while (/[\"'\)\]\}>«»“”„\.,!?:;]$/.test(url)) {
    trail = url.slice(-1) + trail;
    url = url.slice(0, -1);
  }
  return { url, lead, trail };
}

function youtubeId(url) {
  try {
    const u = new URL(url);
    if (u.hostname.includes('youtu.be')) return u.pathname.replace('/', '');
    if (u.searchParams.get('v')) return u.searchParams.get('v');
    const parts = u.pathname.split('/');
    const idx = parts.indexOf('shorts');
    if (idx !== -1 && parts[idx + 1]) return parts[idx + 1];
    return null;
  } catch {
    return null;
  }
}

function youtubeIdFromText(text) {
  if (!text) return null;
  let m = text.match(/https?:\/\/youtu\.be\/([\w-]+)/i);
  if (m) return m[1];
  m = text.match(/https?:\/\/www\.youtube\.com\/watch\?v=([\w-]+)/i);
  if (m) return m[1];
  m = text.match(/https?:\/\/www\.youtube\.com\/shorts\/([\w-]+)/i);
  if (m) return m[1];
  return null;
}

function rutubeId(url) {
  try {
    const u = new URL(url);
    if (!u.hostname.includes('rutube.ru')) return null;
    const parts = u.pathname.split('/');
    const idx = parts.indexOf('video');
    if (idx !== -1 && parts[idx + 1]) return parts[idx + 1];
    const sidx = parts.indexOf('shorts');
    if (sidx !== -1 && parts[sidx + 1]) return parts[sidx + 1];
    return null;
  } catch {
    return null;
  }
}

function vkIds(url) {
  try {
    const u = new URL(url);
    if (!(u.hostname.includes('vk.com') || u.hostname.includes('vkvideo.ru'))) return null;
    const oid = u.searchParams.get('oid');
    const id = u.searchParams.get('id');
    const hash = u.searchParams.get('hash');
    if (oid && id) return { oid, id, hash };
    const m = u.pathname.match(/video(-?\\d+)_([0-9]+)/);
    if (m) return { oid: m[1], id: m[2], hash: u.searchParams.get('hash') };
    return null;
  } catch {
    return null;
  }
}

function vkIdsFromText(text) {
  if (!text) return null;
  const m1 = text.match(/https?:\/\/(?:www\.)?vkvideo\.ru\/video(-?\d+_\d+)/i);
  if (m1) {
    const parts = m1[1].split('_');
    return { oid: parts[0], id: parts[1], hash: null };
  }
  const m2 = text.match(/https?:\/\/(?:www\.)?vk\.com\/video(-?\d+_\d+)/i);
  if (m2) {
    const parts = m2[1].split('_');
    return { oid: parts[0], id: parts[1], hash: null };
  }
  return null;
}

function buildEmbed(src, kind) {
  const wrap = document.createElement('div');
  wrap.className = 'mf-embed';
  const iframe = document.createElement('iframe');
  iframe.src = src;
  if (kind === 'youtube') {
    iframe.allow = 'fullscreen; accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture';
  } else {
    iframe.allow = 'fullscreen; autoplay; encrypted-media; picture-in-picture';
  }
  iframe.setAttribute('title', kind + ' player');
  wrap.appendChild(iframe);
  return wrap;
}

function buildWidgetYoutubeSrc(id) {
  const safeId = String(id || '').trim();
  const siteBase = SITE_BASE || 'https://ff.vadim.online';
  return siteBase + '/player.php?provider=youtube&id=' + encodeURIComponent(safeId);
}

// VK embed is built directly from oid/id (hash optional)

// YouTube embed uses plain https://www.youtube.com/embed/{id}

async function createTopic() {
  if (!userCache || !jwtCache) {
    notify(t('loginFirst'), 'error');
    return;
  }
  const title = topicTitle.value.trim();
  const tags = topicTags.value.split(',').map(t => t.trim()).filter(Boolean);
  const firstCommentHtml = topicFirstCommentEditor ? topicFirstCommentEditor.getHtml() : '';
  const firstCommentText = topicFirstCommentEditor ? topicFirstCommentEditor.getText() : '';
  if (!title || !firstCommentText) {
    notify(t('needTitleFirstComment'), 'error');
    return;
  }

  const res = await apiFetch('/topics', {
    method: 'POST',
    body: JSON.stringify({
      domain,
      title,
      tags,
      first_comment: firstCommentHtml,
      language_contour: languageContour,
      country_scope: countryScope
    })
  });

  if (res.id) {
    topicTitle.value = '';
    topicTags.value = '';
    if (topicFirstCommentEditor) topicFirstCommentEditor.clear();
    notify(t('topicCreated'), 'success');
    await loadTopics(true);
  } else {
    notify(t('topicCreateError'), 'error');
  }
}

domainLabel.textContent = domain;
loginBtn.addEventListener('click', loginFlow);
createTopicBtn.addEventListener('click', createTopic);
rebuildCreateTopicEditor();
searchInput.addEventListener('input', () => {
  clearTimeout(searchInput.__t);
  searchInput.__t = setTimeout(() => searchTopics(true), 300);
});

if (modeSwitch) {
  modeSwitch.addEventListener('click', (e) => {
    const btn = e.target.closest('[data-mode]');
    if (!btn) return;
    const next = String(btn.dataset.mode || '');
    setUiMode(next);
  });
}

if (chatTopicSelect) {
  chatTopicSelect.addEventListener('change', async () => {
    const id = Number(chatTopicSelect.value || 0);
    if (!id) {
      chatState.topicId = 0;
      stopChatStream();
      if (chatMessages) chatMessages.innerHTML = '';
      syncChatComposeState();
      return;
    }
    await openChatTopic(id);
  });
}

if (chatNewTopicBtn) {
  chatNewTopicBtn.addEventListener('click', () => {
    if (!chatCreateBox) return;
    chatCreateBox.classList.toggle('hidden');
    if (chatNewTopicTitle) chatNewTopicTitle.focus();
  });
}

if (chatCreateTopicCancelBtn) {
  chatCreateTopicCancelBtn.addEventListener('click', () => {
    if (chatCreateBox) chatCreateBox.classList.add('hidden');
    if (chatNewTopicTitle) chatNewTopicTitle.value = '';
  });
}

if (chatCreateTopicConfirmBtn) {
  chatCreateTopicConfirmBtn.addEventListener('click', async () => {
    if (!userCache || !jwtCache) {
      notify(t('chatNeedAuth'), 'error');
      return;
    }
    const title = String(chatNewTopicTitle ? chatNewTopicTitle.value : '').trim();
    if (!title) return;
    const res = await apiFetch('/chat/topics', {
      method: 'POST',
      body: JSON.stringify({ domain, title, lc: languageContour, cs: countryScope })
    });
    if (res && res.id) {
      if (chatNewTopicTitle) chatNewTopicTitle.value = '';
      if (chatCreateBox) chatCreateBox.classList.add('hidden');
      notify(t('authDone'), 'success');
      await loadChatTopics(true);
      await openChatTopic(res.id);
    } else {
      notify(String((res && res.error) ? res.error : 'Error'), 'error');
    }
  });
}

if (chatSendBtn) {
  chatSendBtn.addEventListener('click', async () => {
    if (!userCache || !jwtCache) {
      notify(t('chatNeedAuth'), 'error');
      return;
    }
    const text = String(chatInput ? chatInput.value : '').trim();
    if (!text) return;
    if (chatSendBtn.disabled) return;
    chatSendBtn.disabled = true;
    // Avoid UI duplicates: stop the stream while we POST+refresh.
    stopChatStream();
    const hadTopicBeforeSend = !!chatState.topicId;
    const postTopicId = chatState.topicId ? Number(chatState.topicId) : 0;
    const replyToId = chatState.replyTo && chatState.replyTo.id ? Number(chatState.replyTo.id) : 0;
    const res = await apiFetch('/chat/topics/' + postTopicId + '/messages?' + scopeQuery(), {
      method: 'POST',
      body: JSON.stringify({ text, domain, reply_to_id: replyToId || undefined })
    });
    chatSendBtn.disabled = false;
    if (res && res.id) {
      if (chatInput) chatInput.value = '';
      chatState.replyTo = null;
      renderChatReplyBar();
      if (!hadTopicBeforeSend && res.topic_id) {
        // Default topic created on first message.
        chatState.topicId = Number(res.topic_id);
        chatState.lastId = 0;
        chatState.seen = new Set();
        if (chatMessages) chatMessages.innerHTML = '';
        await loadChatTopics(false);
        await openChatTopic(chatState.topicId);
        return;
      }
      if (!res.deduped) {
        const tp = (chatState.topics || []).find((x) => Number(x.id) === Number(chatState.topicId));
        if (tp) tp.messages_count = Number(tp.messages_count || 0) + 1;
        renderChatTopicOptions();
      }
      // Fast refresh: pull immediately after sending.
      const r = await apiFetch('/chat/topics/' + chatState.topicId + '/messages?after_id=' + (chatState.lastId || 0) + '&wait=0&limit=200&' + scopeQuery());
      const msgs = (r && r.messages) ? r.messages : [];
      if (msgs.length) {
        chatState.lastId = Number(msgs[msgs.length - 1].id || chatState.lastId);
        renderChatMessages(msgs, false);
      }
      // Ensure topic list is up to date (new default topic, new counters).
      await loadChatTopics(false);
      startChatStream();
    } else {
      startChatStream();
      notify(String((res && res.error) ? res.error : 'Error'), 'error');
    }
  });
}

if (chatInput) {
  chatInput.addEventListener('keydown', (e) => {
    if (e.key === 'Enter' && !e.shiftKey) {
      e.preventDefault();
      if (chatSendBtn) chatSendBtn.click();
    }
  });
}

if (scopeSwitch) {
  scopeSwitch.addEventListener('click', async (e) => {
    const chip = e.target.closest('[data-lc]');
    if (!chip) return;
    const lc = normalizeLc(chip.dataset.lc || '');
    if (!hasRuLocale && lc === 'RU') return;
    if (lc === languageContour) return;
    await setScope(lc, countryScope);
    applyScopeUi();
    rebuildCreateTopicEditor();
    if (uiMode === 'chat') {
      stopChatStream();
      await loadChatTopics(true);
      return;
    }
    if (currentTopicId) await loadTopic(currentTopicId);
    else if (listState.mode === 'search' && listState.q) await searchTopics(true);
    else await loadTopics(true);
  });
}

if (scopeCountrySelect) {
  scopeCountrySelect.addEventListener('change', async () => {
    const cs = normalizeCs(scopeCountrySelect.value || 'GLOBAL');
    if (cs === countryScope) return;
    await setScope(languageContour, cs);
    applyScopeUi();
    rebuildCreateTopicEditor();
    if (uiMode === 'chat') {
      stopChatStream();
      await loadChatTopics(true);
      return;
    }
    if (currentTopicId) await loadTopic(currentTopicId);
    else if (listState.mode === 'search' && listState.q) await searchTopics(true);
    else await loadTopics(true);
  });
}

getUser().then((user) => {
  renderUser(user);
  syncCreateTopicAccess(user);
});

function setActivePosition(pos) {
  posButtons.forEach((btn) => {
    btn.classList.toggle('active', btn.dataset.pos === pos);
  });
}

if (posButtons.length) {
  safeStorageGet(['widgetPosition'], (res) => {
    const pos = res.widgetPosition || 'br';
    setActivePosition(pos);
  });
}

// Event delegation for controls
document.addEventListener('click', (e) => {
  const toggleEl = e.target.closest('[data-action="toggle-create"]');
  if (toggleEl && createTopicBody) {
    e.preventDefault();
    createTopicBody.classList.toggle('hidden');
    if (toggleCreateTopic) {
      toggleCreateTopic.textContent = createTopicBody.classList.contains('hidden') ? '▸' : '▾';
    }
    safeStorageSet({ createTopicCollapsed: createTopicBody.classList.contains('hidden') }, () => {});
    return;
  }
  const posBtn = e.target.closest('[data-action="position-menu"]');
  if (posBtn && positionMenu) {
    e.preventDefault();
    positionMenu.classList.toggle('hidden');
    return;
  }
  const p = e.target.closest('.pos-btn');
  if (p && positionMenu) {
    e.preventDefault();
    const pos = p.dataset.pos || 'br';
    safeStorageSet({ widgetPosition: pos }, () => {});
    setActivePosition(pos);
    positionMenu.classList.add('hidden');
  }
});

// Restore create-topic collapsed state (default: collapsed)
safeStorageGet(['createTopicCollapsed'], (res) => {
  const collapsed = res.createTopicCollapsed !== undefined ? res.createTopicCollapsed : true;
  if (createTopicBody) {
    createTopicBody.classList.toggle('hidden', collapsed);
  }
  if (toggleCreateTopic) {
    toggleCreateTopic.textContent = collapsed ? '▸' : '▾';
  }
});

getJwt().then(async (jwt) => {
  if (jwt) {
    const me = await apiFetch('/auth/me');
    if (me && me.id) {
      await setUser(me);
      renderUser(me);
    } else {
      await clearJwt();
      await clearUser();
      renderUser(null);
      syncCreateTopicAccess(null);
    }
  } else {
    syncCreateTopicAccess(userCache);
  }
});

(async () => {
  await getScope();
  applyScopeUi();
  rebuildCreateTopicEditor();
  checkExtensionVersion();
  await loadTopics(true);
  setUiMode('forum');
})();
