import { navigate, getRoute, PRE_AUTH_PAGES } from './router.js';
import { renderToolbar } from './components/toolbar.js';
import { captureClientError, setClientErrorSentNotifier, setClientErrorTransport } from './services/client-error-reporter.js';
import { initPwaInstallPromptHandling } from './services/pwa-install-service.js';
import { initPwaPush } from './services/pwa-push-service.js';
import { initCallUiOverlay } from './services/call-ui-service.js';
import { showToast } from './services/channels-ux.js';
import {
  handleCallPushAction,
  handleIncomingCallInvite,
  handleIncomingCallPush,
  handleIncomingCallSignal,
  handleStopCallPush,
  setCallDebugReporter,
  startDebugConnectionAsInitiator,
  startDebugConnectionAsResponder,
} from './services/call-service.js';
import {
  authService,
  addAppLogEntry,
  authorizeSession,
  hydrateMessagesFromStore,
  isSessionInvalidError,
  refreshSessions,
  setSessionAuthorizedHandler,
  setSessionResetHandler,
  state,
  terminateCurrentSession,
  addSignedMessageToChat,
  markIncomingReadByBaseKey,
  markOutgoingReadByBaseKey,
  setContacts,
} from './state.js';

import * as startView from './pages/start-view.js?v=202606142105';
import * as entrySettingsView from './pages/entry-settings-view.js';
import * as registerView from './pages/register-view.js';
import * as registrationPaymentView from './pages/registration-payment-view.js';
import * as registrationKeysView from './pages/registration-keys-view.js';
import * as registrationDraftKeysView from './pages/registration-draft-keys-view.js';
import * as topupView from './pages/topup-view.js';
import * as devnetTopupView from './pages/devnet-topup-view.js';
import * as loginView from './pages/login-view.js?v=202606142055';
import * as loginCameraView from './pages/login-camera-view.js';
import * as loginOtherDeviceView from './pages/login-other-device-view.js?v=202606142055';
import * as loginPasswordView from './pages/login-password-view.js';
import * as keyStorageView from './pages/key-storage-view.js';

import * as profileView from './pages/profile-view.js';
import * as profileEditView from './pages/profile-edit-view.js';
import * as walletView from './pages/wallet-view.js?v=202605300007';
import * as settingsView from './pages/settings-view.js';
import * as developerSettingsView from './pages/developer-settings-view.js';
import * as serverSettingsView from './pages/server-settings-view.js';
import * as toolsSettingsView from './pages/tools-settings-view.js';
import * as deviceView from './pages/device-view.js?v=202606131435';
import * as connectDeviceView from './pages/connect-device-view.js?v=202606142055';
import * as devicePairingView from './pages/device-pairing-view.js?v=202606142055';
import * as deviceQrView from './pages/device-qr-view.js';
import * as deviceCameraView from './pages/device-camera-view.js';
import * as showKeysView from './pages/show-keys-view.js';
import * as deviceSessionView from './pages/device-session-view.js?v=202606131435';
import * as languageView from './pages/language-view.js';
import * as appLogView from './pages/app-log-view.js';
import * as pwaDiagnosticsView from './pages/pwa-diagnostics-view.js';
import * as solanaUsersInitView from './pages/solana-users-init-view.js';
import * as messagesList from './pages/messages-list.js';
import * as contactSearchView from './pages/contact-search-view.js';
import * as chatView from './pages/chat-view.js';
import * as userProfileView from './pages/user-profile-view.js';
import * as channelsList from './pages/channels-list.js';
import * as channelView from './pages/channel-view.js';
import * as channelThreadView from './pages/channel-thread-view.js';
import * as addChannelView from './pages/add-channel-view.js';
import * as addPersonalPublicChatView from './pages/add-personal-public-chat-view.js';
import * as networkView from './pages/network-view.js';
import * as notificationsView from './pages/notifications-view.js';

const routes = {
  'start-view': startView,
  'entry-settings-view': entrySettingsView,
  'register-view': registerView,
  'registration-payment-view': registrationPaymentView,
  'registration-keys-view': registrationKeysView,
  'registration-draft-keys-view': registrationDraftKeysView,
  'topup-view': topupView,
  'devnet-topup-view': devnetTopupView,
  'login-view': loginView,
  'login-camera-view': loginCameraView,
  'login-other-device-view': loginOtherDeviceView,
  'login-password-view': loginPasswordView,
  'key-storage-view': keyStorageView,
  'profile-view': profileView,
  'profile-edit-view': profileEditView,
  'wallet-view': walletView,
  'settings-view': settingsView,
  'developer-settings-view': developerSettingsView,
  'server-settings-view': serverSettingsView,
  'tools-settings-view': toolsSettingsView,
  'device-view': deviceView,
  'connect-device-view': connectDeviceView,
  'device-pairing-view': devicePairingView,
  'device-qr-view': deviceQrView,
  'device-camera-view': deviceCameraView,
  'show-keys-view': showKeysView,
  'device-session-view': deviceSessionView,
  'language-view': languageView,
  'app-log-view': appLogView,
  'pwa-diagnostics-view': pwaDiagnosticsView,
  'solana-users-init-view': solanaUsersInitView,
  'messages-list': messagesList,
  'contact-search-view': contactSearchView,
  'chat-view': chatView,
  user: userProfileView,
  'channels-list': channelsList,
  'channel-view': channelView,
  'channel-thread-view': channelThreadView,
  'add-channel-view': addChannelView,
  'add-personal-public-chat-view': addPersonalPublicChatView,
  'network-view': networkView,
  'notifications-view': notificationsView,
};

const screenEl = document.getElementById('app-screen');
const toolbarEl = document.getElementById('toolbar-slot');
const appShellEl = document.querySelector('.app-shell');

const CONNECTION_CHECK_INTERVAL_MS = 20 * 1000;
const UI_VERSION_PERIODIC_CHECK_MS = 5 * 60 * 1000;
const CURRENT_BUILD_HASH = String(window.__SHINE_BUILD_HASH__ || '').trim();
const UI_BUILD_HASH_PATTERN = /window\.__SHINE_BUILD_HASH__\s*=\s*'([^']+)'/;

let currentCleanup = null;
let pingIntervalId = null;
let reconnectIntervalId = null;
let sessionRuntimeStarted = false;
let connectionState = '';
let connectionStatusText = '';
let connectionRetryBannerEl = null;
let connectionStatusCountdownId = null;
let connectionNextRetryAtMs = 0;
let connectionCheckInFlight = false;
let wsSessionRestoreInFlight = null;
let uiUpdateReloadScheduled = false;
let pwaUpdateCheckAttempted = false;
let uiVersionCheckInFlight = false;
let uiVersionPeriodicIntervalId = null;
const CALL_PUSH_PENDING_ACTION_KEY = 'shine-ui-call-push-pending-action-v1';
const GUEST_ALLOWED_PAGES = new Set([
  'start-view',
  'entry-settings-view',
  'network-view',
  'channels-list',
  'channel-view',
  'channel-thread-view',
  'user',
  'contact-search-view',
]);

setClientErrorTransport((payload) => authService.reportClientUiError(payload));
setClientErrorSentNotifier((payload) => {
  const login = String(state.session.login || 'guest').trim();
  const isoTs = new Date(Number(payload?.clientTs || Date.now())).toISOString();
  showToast(`Ошибка отправлена на сервер · ${login} · ${isoTs}`);
});
initPwaInstallPromptHandling();
initCallUiOverlay();
setCallDebugReporter((payload) => authService.reportClientDebug(payload));

function ensureConnectionIndicatorEl() {
  return document.getElementById('toolbar-connection-indicator');
}

function ensureConnectionRetryBannerEl() {
  if (connectionRetryBannerEl) return connectionRetryBannerEl;
  if (!appShellEl) return null;
  const el = document.createElement('div');
  el.id = 'connection-retry-banner';
  el.className = 'connection-retry-banner is-connecting';
  el.textContent = 'Соединяюсь…';
  el.addEventListener('click', () => {
    void triggerImmediateConnectionRetry();
  });
  appShellEl.append(el);
  connectionRetryBannerEl = el;
  return el;
}

function stopConnectionCountdown() {
  if (connectionStatusCountdownId) {
    window.clearInterval(connectionStatusCountdownId);
    connectionStatusCountdownId = null;
  }
}

function getConnectionRetrySecondsLeft() {
  const leftMs = Math.max(0, Number(connectionNextRetryAtMs || 0) - Date.now());
  return Math.ceil(leftMs / 1000);
}

function refreshConnectionUi() {
  const state = String(connectionState || 'connecting').trim();

  const indicatorEl = ensureConnectionIndicatorEl();
  if (indicatorEl) {
    indicatorEl.classList.remove('is-connected', 'is-disconnected', 'is-connecting', 'is-updating', 'is-unknown');
    indicatorEl.classList.add(`is-${state || 'unknown'}`);
  }

  const bannerEl = ensureConnectionRetryBannerEl();
  if (!bannerEl) return;

  if (state === 'connected' || state === 'updating') {
    bannerEl.hidden = true;
    stopConnectionCountdown();
    return;
  }

  bannerEl.hidden = false;
  bannerEl.classList.remove('is-connected', 'is-disconnected', 'is-connecting', 'is-updating');
  bannerEl.classList.add(`is-${state}`);

  if (connectionStatusText) {
    bannerEl.textContent = connectionStatusText;
    return;
  }

  if (state === 'connecting') {
    bannerEl.textContent = 'Соединяюсь… Нажмите, чтобы попробовать сразу';
    return;
  }

  if (state === 'disconnected') {
    const secs = getConnectionRetrySecondsLeft();
    bannerEl.textContent = `Нет соединения. Повтор через ${secs}с. Нажмите для попытки сейчас`;
    return;
  }

  bannerEl.textContent = 'Проблема с соединением. Нажмите для повтора';
}

function startConnectionCountdown() {
  if (connectionStatusCountdownId) return;
  connectionStatusCountdownId = window.setInterval(() => {
    if (connectionState !== 'disconnected') return;
    const secs = getConnectionRetrySecondsLeft();
    if (secs <= 0 && !connectionCheckInFlight) {
      connectionStatusText = '';
      setConnectionStatus('connecting', 'Соединяюсь…');
      void checkConnectionHealth();
      return;
    }
    refreshConnectionUi();
  }, 1000);
}

function savePendingCallPushAction(action, payload = {}) {
  try {
    const item = {
      action: String(action || '').trim().toLowerCase(),
      payload: payload || {},
      savedAtMs: Date.now(),
    };
    localStorage.setItem(CALL_PUSH_PENDING_ACTION_KEY, JSON.stringify(item));
  } catch {
    // ignore localStorage errors
  }
}

function loadPendingCallPushAction() {
  try {
    const raw = localStorage.getItem(CALL_PUSH_PENDING_ACTION_KEY);
    if (!raw) return null;
    const parsed = JSON.parse(raw);
    const action = String(parsed?.action || '').trim().toLowerCase();
    if (action !== 'accept' && action !== 'decline') return null;
    return {
      action,
      payload: parsed?.payload || {},
    };
  } catch {
    return null;
  }
}

function clearPendingCallPushAction() {
  try {
    localStorage.removeItem(CALL_PUSH_PENDING_ACTION_KEY);
  } catch {
    // ignore localStorage errors
  }
}

function consumeCallPushActionFromUrlIfAny() {
  try {
    const params = new URLSearchParams(window.location.search || '');
    const action = String(params.get('callPushAction') || '').trim().toLowerCase();
    const rawPayload = String(params.get('callPushPayload') || '');
    if (action !== 'accept' && action !== 'decline') return;
    let payload = {};
    if (rawPayload) {
      try {
        payload = JSON.parse(decodeURIComponent(rawPayload));
      } catch {
        payload = {};
      }
    }
    savePendingCallPushAction(action, payload);
    params.delete('callPushAction');
    params.delete('callPushPayload');
    const nextQuery = params.toString();
    const nextUrl = `${window.location.pathname}${nextQuery ? `?${nextQuery}` : ''}`;
    window.history.replaceState({}, '', nextUrl);
  } catch {
    // ignore URL parsing errors
  }
}

async function processPendingCallPushActionIfPossible() {
  if (!state.session.isAuthorized) return;
  const pending = loadPendingCallPushAction();
  if (!pending) return;
  clearPendingCallPushAction();
  try {
    await handleCallPushAction(pending.action, pending.payload || {});
  } catch (error) {
    addAppLogEntry({
      level: 'warn',
      source: 'web-push',
      message: 'Не удалось выполнить действие звонка из push',
      details: { action: pending.action, error: error?.message || 'unknown' },
    });
  }
}

function setConnectionStatus(nextState, text = '') {
  const state = String(nextState || '').trim();
  if (!state) return;
  connectionState = state;
  connectionStatusText = String(text || '').trim();
  if (state === 'disconnected') {
    if (!connectionNextRetryAtMs || connectionNextRetryAtMs <= Date.now()) {
      connectionNextRetryAtMs = Date.now() + CONNECTION_CHECK_INTERVAL_MS;
    }
    startConnectionCountdown();
  } else {
    stopConnectionCountdown();
  }
  refreshConnectionUi();
}

async function triggerImmediateConnectionRetry() {
  if (connectionCheckInFlight) return;
  connectionNextRetryAtMs = Date.now() + CONNECTION_CHECK_INTERVAL_MS;
  setConnectionStatus('connecting', 'Соединяюсь…');
  await checkConnectionHealth();
}

function extractBuildHashFromHtml(htmlText) {
  const html = String(htmlText || '');
  if (!html) return '';
  const match = html.match(UI_BUILD_HASH_PATTERN);
  return String(match?.[1] || '').trim();
}

async function fetchCurrentHostUiBuildHash() {
  try {
    const url = `./index.html?build_probe=${Date.now()}`;
    const response = await fetch(url, { cache: 'no-store' });
    if (!response?.ok) return '';
    const html = await response.text();
    return extractBuildHashFromHtml(html);
  } catch {
    return '';
  }
}

async function checkAndReloadIfUiUpdated(remoteHashRaw) {
  const remoteHash = String(remoteHashRaw || '').trim();
  if (!remoteHash || !CURRENT_BUILD_HASH || remoteHash === CURRENT_BUILD_HASH) return;
  if (uiVersionCheckInFlight) return;

  uiVersionCheckInFlight = true;
  try {
    const latestHostHash = await fetchCurrentHostUiBuildHash();
    if (!latestHostHash || latestHostHash === CURRENT_BUILD_HASH) {
      addAppLogEntry({
        level: 'info',
        source: 'version-check',
        message: `Ping сообщил другую версию UI (${remoteHash}), но текущий хост всё ещё отдаёт ${CURRENT_BUILD_HASH}. Reload пропущен, чтобы избежать цикла.`,
      });
      return;
    }

    scheduleUiReload({
      source: 'version-check',
      message: `Обнаружена новая версия UI: ${CURRENT_BUILD_HASH} -> ${latestHostHash} (ping: ${remoteHash})`,
      delayMs: 600,
      activateWaitingWorker: true,
    });
  } finally {
    uiVersionCheckInFlight = false;
  }
}

async function refreshServiceWorkers({ activateWaitingWorker = false } = {}) {
  if (!('serviceWorker' in navigator)) return;
  try {
    const registrations = await navigator.serviceWorker.getRegistrations();
    await Promise.all(registrations.map(async (registration) => {
      try {
        await registration.update();
      } catch {}
      if (activateWaitingWorker && registration.waiting) {
        try {
          registration.waiting.postMessage({ type: 'SKIP_WAITING' });
        } catch {}
      }
    }));
  } catch {
    // ignore service worker update failures
  }
}

function scheduleUiReload({
  source = 'ui-update',
  message = 'Запрошено обновление интерфейса',
  delayMs = 700,
  activateWaitingWorker = true,
} = {}) {
  if (uiUpdateReloadScheduled) return;
  uiUpdateReloadScheduled = true;
  addAppLogEntry({
    level: 'info',
    source,
    message,
  });
  setConnectionStatus('updating');
  void refreshServiceWorkers({ activateWaitingWorker });

  const ms = Math.min(15_000, Math.max(200, Number(delayMs || 700)));
  window.setTimeout(() => {
    window.location.reload();
  }, ms);
}

async function tryUpdatePwaOnFirstConnectedPing() {
  if (pwaUpdateCheckAttempted) return;
  pwaUpdateCheckAttempted = true;
  await refreshServiceWorkers({ activateWaitingWorker: false });
}

async function runPeriodicUiVersionCheck() {
  if (uiUpdateReloadScheduled) return;
  if (uiVersionCheckInFlight) return;
  try {
    const latestHostHash = await fetchCurrentHostUiBuildHash();
    if (!latestHostHash || !CURRENT_BUILD_HASH || latestHostHash === CURRENT_BUILD_HASH) return;
    scheduleUiReload({
      source: 'ui-periodic-version-check',
      message: `Найдена новая версия UI: ${CURRENT_BUILD_HASH} -> ${latestHostHash}`,
      delayMs: 600,
      activateWaitingWorker: true,
    });
  } catch {
    // ignore periodic check errors
  }
}

function startPeriodicUiVersionCheck() {
  if (uiVersionPeriodicIntervalId) return;
  // ВРЕМЕННО: частая проверка обновления UI (каждые 5 минут) для диагностики проблем с обновлением клиента.
  // Позже интервал нужно увеличить или вернуть проверку только по ручному действию.
  uiVersionPeriodicIntervalId = window.setInterval(() => {
    void runPeriodicUiVersionCheck();
  }, UI_VERSION_PERIODIC_CHECK_MS);
}

async function checkConnectionHealth() {
  if (connectionCheckInFlight) return;
  connectionCheckInFlight = true;
  if (connectionState !== 'connected') {
    setConnectionStatus('connecting');
  }
  try {
    const wasOpen = wsIsOpen();
    if (!wasOpen) {
      await authService.ws.open();
      const restored = await ensureSessionAfterWsReconnect();
      if (!restored) {
        connectionStatusText = '';
        setConnectionStatus('disconnected');
        return;
      }
    }
    const pingResp = await authService.ws.request('Ping', { ts: Date.now() }, 7000);
    const remoteUiBuildHash = pingResp?.payload?.uiBuildHash || pingResp?.uiBuildHash || '';
    void checkAndReloadIfUiUpdated(remoteUiBuildHash);
    await tryUpdatePwaOnFirstConnectedPing();
    setConnectionStatus('connected');
  } catch {
    connectionStatusText = '';
    setConnectionStatus('disconnected');
  } finally {
    connectionCheckInFlight = false;
  }
}

function startConnectionMonitor() {
  if (pingIntervalId) {
    window.clearInterval(pingIntervalId);
    pingIntervalId = null;
  }

  connectionNextRetryAtMs = Date.now() + CONNECTION_CHECK_INTERVAL_MS;
  void checkConnectionHealth();

  pingIntervalId = window.setInterval(() => {
    connectionNextRetryAtMs = Date.now() + CONNECTION_CHECK_INTERVAL_MS;
    if (connectionState === 'disconnected') {
      refreshConnectionUi();
    }
    void checkConnectionHealth();
  }, CONNECTION_CHECK_INTERVAL_MS);
}

function isReconnectAllowedNow() {
  const pageId = getRoute().pageId || '';
  return !PRE_AUTH_PAGES.includes(pageId);
}

function wsIsOpen() {
  const ws = authService?.ws?.ws;
  return !!(ws && ws.readyState === WebSocket.OPEN);
}

async function ensureSessionAfterWsReconnect() {
  if (!state.session.isAuthorized) return true;
  if (wsSessionRestoreInFlight) return wsSessionRestoreInFlight;

  wsSessionRestoreInFlight = (async () => {
    try {
      const resumed = await authService.resumeSession(state.session.login, state.session.sessionId);
      authorizeSession({
        login: resumed.login || state.session.login,
        sessionId: resumed.sessionId || state.session.sessionId,
        storagePwd: resumed.storagePwd || state.session.storagePwdInMemory,
      });
      addAppLogEntry({
        level: 'info',
        source: 'ws-reconnect',
        message: 'Сессия восстановлена после переподключения WS',
      });
      return true;
    } catch (error) {
      if (isSessionInvalidError(error)) {
        await terminateCurrentSession({
          infoMessage: 'Ваша сессия устарела. Авторизируйтесь заново.',
        });
        return false;
      }
      throw error;
    }
  })().finally(() => {
    wsSessionRestoreInFlight = null;
  });

  return wsSessionRestoreInFlight;
}

function showGlobalErrorAlert(title, details = {}) {
  const lines = [title];
  if (details.message) lines.push(`Сообщение: ${details.message}`);
  if (details.pageId) lines.push(`Экран: ${details.pageId}`);
  if (details.sourceUrl) lines.push(`Источник: ${details.sourceUrl}`);
  if (Number.isFinite(details.lineNumber)) lines.push(`Строка: ${details.lineNumber}`);
  if (Number.isFinite(details.columnNumber)) lines.push(`Колонка: ${details.columnNumber}`);
  if (details.reasonType) lines.push(`Тип: ${details.reasonType}`);
  if (details.stack) lines.push(`Stack:\n${details.stack}`);
  window.alert(lines.join('\n'));
}

window.addEventListener('error', (event) => {
  const pageId = getRoute().pageId || '';
  addAppLogEntry({
    level: 'error',
    source: 'global_error',
    message: event.message || 'Global JS error',
    details: {
      pageId,
      sourceUrl: event.filename || '',
      line: event.lineno,
      column: event.colno,
      stack: event.error?.stack || '',
    },
  });
  captureClientError({
    kind: 'global_error',
    message: event.message || 'Global JS error',
    stack: event.error?.stack || '',
    sourceUrl: event.filename || '',
    lineNumber: event.lineno,
    columnNumber: event.colno,
    context: {
      pageId,
    },
  });

  showGlobalErrorAlert('Поймана глобальная ошибка UI', {
    message: event.message || 'Global JS error',
    stack: event.error?.stack || '',
    sourceUrl: event.filename || '',
    lineNumber: event.lineno,
    columnNumber: event.colno,
    pageId,
  });
});

window.addEventListener('unhandledrejection', (event) => {
  const reason = event.reason;
  const pageId = getRoute().pageId || '';
  addAppLogEntry({
    level: 'error',
    source: 'unhandled_rejection',
    message: reason?.message || String(reason || 'Unhandled promise rejection'),
    details: {
      pageId,
      reasonType: reason?.constructor?.name || typeof reason,
      stack: reason?.stack || '',
    },
  });
  captureClientError({
    kind: 'unhandled_rejection',
    message: reason?.message || String(reason || 'Unhandled promise rejection'),
    stack: reason?.stack || '',
    context: {
      pageId,
      reasonType: reason?.constructor?.name || typeof reason,
    },
  });

  showGlobalErrorAlert('Пойман необработанный Promise reject', {
    message: reason?.message || String(reason || 'Unhandled promise rejection'),
    stack: reason?.stack || '',
    pageId,
    reasonType: reason?.constructor?.name || typeof reason,
  });
});

function renderPageFailureFallback(pageId, error) {
  captureClientError({
    kind: 'page_render_failure',
    message: error?.message || 'Page render failed',
    stack: error?.stack || '',
    context: {
      pageId,
      routeHash: window.location.pathname || '',
    },
  });

  screenEl.innerHTML = '';
  const wrap = document.createElement('section');
  wrap.className = 'stack';

  const card = document.createElement('div');
  card.className = 'card stack channels-status';

  const title = document.createElement('strong');
  title.textContent = 'Не удалось отрисовать экран';

  const details = document.createElement('p');
  details.className = 'meta-muted';
  details.textContent = `Экран: ${pageId || 'неизвестно'}. Попробуйте повторить.`;

  const retry = document.createElement('button');
  retry.type = 'button';
  retry.className = 'primary-btn';
  retry.textContent = 'Повторить';
  retry.addEventListener('click', () => renderApp());

  card.append(title, details, retry);
  wrap.append(card);
  screenEl.append(wrap);

  screenEl.classList.toggle('no-app-chrome', false);
  toolbarEl.innerHTML = '';
  refreshConnectionUi();
}

function renderApp() {
  const route = getRoute();
  const pageId = route.pageId || (state.session.isAuthorized ? 'messages-list' : 'start-view');

  if (!state.session.isAuthorized && !PRE_AUTH_PAGES.includes(pageId) && !GUEST_ALLOWED_PAGES.has(pageId)) {
    navigate('start-view');
    return;
  }

  if (state.session.isAuthorized && PRE_AUTH_PAGES.includes(pageId)) {
    navigate('messages-list');
    return;
  }

  const page = routes[pageId] || routes['start-view'];

  if (typeof currentCleanup === 'function') {
    currentCleanup();
    currentCleanup = null;
  }

  try {
    screenEl.innerHTML = '';
    const screen = page.render({ route, navigate });
    if (!(screen instanceof Node)) {
      throw new Error('Page render returned invalid node');
    }

    screenEl.append(screen);
    currentCleanup = typeof screen.cleanup === 'function' ? screen.cleanup : null;

    const showAppChrome = page.pageMeta?.showAppChrome !== false;
    screenEl.classList.toggle('no-app-chrome', !showAppChrome);

    toolbarEl.innerHTML = '';
    if (showAppChrome) {
      toolbarEl.append(renderToolbar(page.pageMeta.id, navigate));
    }
    refreshConnectionUi();
  } catch (error) {
    console.error('[renderApp] controlled fallback', error);
    renderPageFailureFallback(pageId, error);
  }
}

async function tryAutoLogin() {
  if (!state.session.login || !state.session.sessionId) return;
  try {
    await authService.reconnect(state.entrySettings.shineServer);
    const resumed = await authService.resumeSession(state.session.login, state.session.sessionId);
    authorizeSession(resumed);
    await refreshSessions();
    try {
      const contacts = await authService.listContacts();
      setContacts(contacts.contacts || []);
    } catch {}
  } catch (error) {
    if (isSessionInvalidError(error)) {
      await terminateCurrentSession({
        infoMessage: 'Сессия на этом устройстве уже завершена. Выполните вход заново.',
      });
    }
  }
}

async function ensureSessionRuntimeStarted() {
  if (!state.session.isAuthorized || sessionRuntimeStarted) return;
  sessionRuntimeStarted = true;

  await initPwaPush({
    authService,
    onLog: (entry) => addAppLogEntry(entry),
  });

  startConnectionMonitor();

  if (reconnectIntervalId) {
    window.clearInterval(reconnectIntervalId);
    reconnectIntervalId = null;
  }
  reconnectIntervalId = window.setInterval(async () => {
    if (!state.session.isAuthorized) return;
    if (!isReconnectAllowedNow()) return;
    if (wsIsOpen()) return;

    try {
      await authService.ws.open();
      const restored = await ensureSessionAfterWsReconnect();
      if (!restored) return;
      addAppLogEntry({
        level: 'info',
        source: 'ws-reconnect',
        message: 'WS переподключен автоматически',
      });
    } catch (error) {
      addAppLogEntry({
        level: 'warn',
        source: 'ws-reconnect',
        message: 'Попытка автопереподключения не удалась',
        details: { error: error?.message || 'unknown' },
      });
    }
  }, 15_000);

  await processPendingCallPushActionIfPossible();
}

async function init() {
  consumeCallPushActionFromUrlIfAny();

  addAppLogEntry({
    level: 'info',
    source: 'app',
    message: 'Инициализация UI запущена',
  });

  setSessionResetHandler(() => {
    sessionRuntimeStarted = false;
    startConnectionMonitor();
    if (reconnectIntervalId) {
      window.clearInterval(reconnectIntervalId);
      reconnectIntervalId = null;
    }
    navigate('start-view');
  });

  setSessionAuthorizedHandler(() => {
    void ensureSessionRuntimeStarted();
    void processPendingCallPushActionIfPossible();
  });

  if ('serviceWorker' in navigator) {
    navigator.serviceWorker.addEventListener('message', (event) => {
      const data = event?.data || {};
      if (data.type === 'SHINE_CALL_PUSH_ACTION') {
        const action = String(data.action || '').trim().toLowerCase();
        const payload = data.payload || {};
        if (action === 'accept' || action === 'decline') {
          savePendingCallPushAction(action, payload);
          void processPendingCallPushActionIfPossible();
        }
        return;
      }
      if (data.type !== 'SHINE_WEB_PUSH_EVENT') return;

      const payload = data.payload || {};
      const kind = String(payload.kind || '').trim();
      const now = Date.now();
      try {
        localStorage.setItem('shine-ui-last-push-at-v1', String(now));
        localStorage.setItem('shine-ui-last-push-payload-v1', JSON.stringify(payload || {}));
        if (kind === 'test_push') {
          localStorage.setItem('shine-ui-last-test-push-status-v1', 'доставлен');
          localStorage.setItem('shine-ui-last-test-push-at-v1', String(now));
        }
      } catch {
        // ignore localStorage errors
      }
      addAppLogEntry({
        level: 'info',
        source: 'web-push',
        message: 'Получено push-событие в service worker',
        details: payload,
      });
      if (kind === 'incoming_call' && !payload.stale && state.session.isAuthorized) {
        void handleIncomingCallPush(payload);
      } else if (kind === 'stop_call' && state.session.isAuthorized) {
        void handleStopCallPush(payload);
      }
      window.dispatchEvent(new CustomEvent('shine-push-diagnostics-update', { detail: payload }));
    });
  }

  authService.onEvent('SessionRevoked', async () => {
    await terminateCurrentSession({ infoMessage: 'Сессия закрыта с другого устройства.' });
  });

  authService.onEvent('ForceUiReload', async (evt) => {
    const payload = evt?.payload || {};
    const reason = String(payload.reason || 'server_debug_api').trim() || 'server_debug_api';
    const reloadAfterMs = Number(payload.reloadAfterMs || 700);
    scheduleUiReload({
      source: 'server-ui-reload',
      message: `Сервер запросил обновление UI (${reason})`,
      delayMs: reloadAfterMs,
      activateWaitingWorker: true,
    });
  });

  authService.onEvent('SignedMessageArrived', async (evt) => {
    const payload = evt?.payload || {};
    const messageKey = String(payload.messageKey || '').trim();
    const blobB64 = String(payload.blobB64 || '').trim();
    if (!messageKey || !blobB64) return;

    let parsed;
    try {
      parsed = authService.parseSignedMessageBlob(blobB64);
    } catch (error) {
      addAppLogEntry({
        level: 'warn',
        source: 'signed-dm',
        message: 'Не удалось распарсить входящий signed message',
        details: { messageKey, error: error?.message || 'unknown' },
      });
      return;
    }

    const fromLogin = parsed.fromLogin || '';
    const toLogin = parsed.toLogin || '';
    const messageType = Number(parsed.messageType || 0);
    const chatId = messageType === 2 ? toLogin : fromLogin;
    const text = (messageType === 1 || messageType === 2)
      ? new TextDecoder().decode(parsed.payloadBytes || new Uint8Array(0))
      : '';

    let shouldRefreshToolbarUnread = false;

    if (messageType === 1 || messageType === 2) {
      const isIncomingForCurrent = messageType === 1;
      const added = addSignedMessageToChat({
        chatId,
        messageKey,
        baseKey: parsed.baseKey,
        from: isIncomingForCurrent ? 'in' : 'out',
        text,
        messageType,
        unread: isIncomingForCurrent,
        rawBlobB64: blobB64,
      });
      if (added) {
        addAppLogEntry({
          level: 'info',
          source: 'signed-dm',
          message: isIncomingForCurrent
            ? `Новое входящее сообщение от ${fromLogin}`
            : `Синхронизирована исходящая копия в чате ${chatId}`,
          details: { messageKey, baseKey: parsed.baseKey, messageType },
        });
      }
      if (added && isIncomingForCurrent) {
        shouldRefreshToolbarUnread = true;
      }
      if (added && isIncomingForCurrent && Notification.permission === 'granted' && !payload.backlog) {
        try {
          new Notification(`Сообщение от ${fromLogin}`, { body: text || '' });
        } catch {}
      }
    } else if (messageType === 3 || messageType === 4) {
      let refBaseKey = String(payload.receiptRefBaseKey || '').trim();
      if (!refBaseKey) {
        try {
          const ref = authService.parseReadReceiptPayload(parsed.payloadBytes);
          refBaseKey = `${ref.refFromLogin}|${ref.refToLogin}|${ref.refTimeMs}|${ref.refNonce}`;
        } catch {}
      }
      if (refBaseKey) {
        if (messageType === 3) {
          markOutgoingReadByBaseKey(refBaseKey);
        } else {
          markIncomingReadByBaseKey(refBaseKey);
        }
      }
      addAppLogEntry({
        level: 'info',
        source: 'signed-dm',
        message: 'Получено подтверждение прочтения',
        details: { messageKey, baseKey: parsed.baseKey, messageType },
      });
    }

    try {
      await authService.ackSessionDelivery(messageKey);
    } catch (error) {
      addAppLogEntry({
        level: 'warn',
        source: 'signed-dm',
        message: 'Не удалось отправить ACK доставки по сессии',
        details: { messageKey, error: error?.message || 'unknown' },
      });
    }

    const pageId = getRoute().pageId || '';
    if (pageId === 'chat-view' || pageId === 'messages-list' || shouldRefreshToolbarUnread) {
      renderApp();
    }
  });

  authService.onEvent('IncomingCallInvite', async (evt) => {
    try { await handleIncomingCallInvite(evt); } catch {}
  });

  authService.onEvent('IncomingCallSignal', async (evt) => {
    try { await handleIncomingCallSignal(evt); } catch {}
  });

  authService.onEvent('DebugConnectPrepareResponder', async (evt) => {
    try {
      const p = evt?.payload || {};
      await startDebugConnectionAsResponder({
        runId: p.runId,
        callId: p.callId,
        peerLogin: p.peerLogin,
        peerSessionId: p.peerSessionId,
      });
      addAppLogEntry({
        level: 'info',
        source: 'debug-connect',
        message: 'Получена команда debug responder',
        details: p,
      });
    } catch (error) {
      addAppLogEntry({
        level: 'error',
        source: 'debug-connect',
        message: 'Ошибка запуска debug responder',
        details: { error: error?.message || 'unknown' },
      });
      await authService.reportClientDebug({
        runId: evt?.payload?.runId || '',
        level: 'error',
        message: 'debug_responder_failed',
        details: error?.message || 'unknown',
      });
    }
  });

  authService.onEvent('DebugConnectStartInitiator', async (evt) => {
    try {
      const p = evt?.payload || {};
      await startDebugConnectionAsInitiator({
        runId: p.runId,
        callId: p.callId,
        peerLogin: p.peerLogin,
        peerSessionId: p.peerSessionId,
      });
      addAppLogEntry({
        level: 'info',
        source: 'debug-connect',
        message: 'Получена команда debug initiator',
        details: p,
      });
    } catch (error) {
      addAppLogEntry({
        level: 'error',
        source: 'debug-connect',
        message: 'Ошибка запуска debug initiator',
        details: { error: error?.message || 'unknown' },
      });
      await authService.reportClientDebug({
        runId: evt?.payload?.runId || '',
        level: 'error',
        message: 'debug_initiator_failed',
        details: error?.message || 'unknown',
      });
    }
  });

  // Важно: сначала всегда отрисовываем UI (чтобы не было "чёрного экрана"),
  // а сетевые/авторизационные шаги выполняем фоном.
  if (!window.location.pathname || window.location.pathname === '/' || window.location.pathname === '/index.html') {
    navigate(state.session.isAuthorized ? 'messages-list' : 'start-view');
  }
  renderApp();

  void (async () => {
    try {
      await tryAutoLogin();
      await hydrateMessagesFromStore();
      startConnectionMonitor();
      startPeriodicUiVersionCheck();
      await ensureSessionRuntimeStarted();
    } finally {
      renderApp();
    }
  })();

  window.addEventListener('popstate', renderApp);
  document.addEventListener('visibilitychange', () => {
    if (document.visibilityState !== 'visible') return;
    void checkConnectionHealth();
  });
}

init();
