import { AuthService } from './services/auth-service.js';
import { listStoredMessages, putStoredMessage, clearStoredMessages } from './services/message-store.js';
import { SOLANA_ENDPOINT_DEFAULT } from './solana-programs.js';

const clone = (value) => JSON.parse(JSON.stringify(value));
const SESSION_STORAGE_KEY = 'shine-ui-current-session-v1';
const REACTIONS_STORAGE_KEY = 'shine-ui-message-reactions-v2';
const WEB_PUSH_SUBSCRIPTION_KEY = 'shine-ui-webpush-subscription-v1';
const ENTRY_SETTINGS_STORAGE_KEY = 'shine-ui-entry-settings-v1';
const CHANNEL_NOTIFY_KEY = 'shine-channels-notify-v1';
const CHANNELS_DEMO_KEY = 'shine-channels-demo';
const CREATE_CHANNEL_FLASH_KEY = 'shine-channels-create-success';
const MAX_APP_LOG_ENTRIES = 500;
const INVALID_SESSION_CODES = new Set([
  'NOT_AUTHENTICATED',
  'SESSION_NOT_FOUND',
  'SESSION_KEY_NOT_ACTUAL',
  'SESSION_OF_ANOTHER_USER',
]);

function readLocalWsOverrideUrl() {
  try {
    const params = new URLSearchParams(window.location.search);

    const explicitWsUrl = String(params.get('wsUrl') || '').trim();
    if (explicitWsUrl) {
      if (explicitWsUrl.startsWith('ws://') || explicitWsUrl.startsWith('wss://')) {
        try {
          const parsed = new URL(explicitWsUrl);
          if (!parsed.pathname || parsed.pathname === '/') parsed.pathname = '/ws';
          return parsed.toString();
        } catch {
          return explicitWsUrl;
        }
      }
      if (explicitWsUrl.startsWith('http://') || explicitWsUrl.startsWith('https://')) {
        try {
          const parsed = new URL(explicitWsUrl);
          parsed.protocol = parsed.protocol === 'https:' ? 'wss:' : 'ws:';
          if (!parsed.pathname || parsed.pathname === '/') parsed.pathname = '/ws';
          return parsed.toString();
        } catch {
          return `${explicitWsUrl.replace(/^http/, 'ws').replace(/\/$/, '')}/ws`;
        }
      }
      return '';
    }

    const value = params.get('localWsPort');
    const asNum = Number(value);
    if (!Number.isFinite(asNum)) return '';
    const port = Math.trunc(asNum);
    if (port <= 0 || port > 65535) return '';
    const isHttpsPage = window.location.protocol === 'https:';
    const forceInsecureLocal = params.get('allowInsecureLocalWs') === '1';
    const scheme = (isHttpsPage && !forceInsecureLocal) ? 'wss' : 'ws';
    return `${scheme}://localhost:${port}/ws`;
  } catch {
    return '';
  }
}

function inferTunnelWsUrl() {
  try {
    const host = String(window.location.host || '').toLowerCase();
    const isTunnelHost = (
      host.endsWith('.ngrok-free.dev') ||
      host.endsWith('.ngrok.io') ||
      host.endsWith('.trycloudflare.com')
    );
    if (!isTunnelHost) return '';
    const scheme = window.location.protocol === 'https:' ? 'wss' : 'ws';
    return `${scheme}://${window.location.host}/ws`;
  } catch {
    return '';
  }
}

const LOCAL_WS_OVERRIDE_URL = readLocalWsOverrideUrl() || inferTunnelWsUrl();
const DEFAULT_SOLANA_SERVER = SOLANA_ENDPOINT_DEFAULT;
const DEFAULT_SHINE_SERVER = 'wss://shineup.me/ws';
const DEFAULT_ARWEAVE_SERVER = 'https://arweave.net';
const DEFAULT_CALL_PREFLIGHT_TIMEOUT_MS = 6000;
const DEFAULT_OPENAI_BASE_URL = 'https://api.openai.com/v1';

function normalizeToolsSettings(rawTools) {
  const source = rawTools && typeof rawTools === 'object' ? rawTools : {};
  const stt = source.speechToText && typeof source.speechToText === 'object' ? source.speechToText : {};
  const tts = source.textToSpeech && typeof source.textToSpeech === 'object' ? source.textToSpeech : {};
  return {
    speechToText: {
      provider: String(stt.provider || 'openai'),
      baseUrl: String(stt.baseUrl || DEFAULT_OPENAI_BASE_URL),
      apiKey: String(stt.apiKey || ''),
      quality: String(stt.quality || 'medium'),
      model: String(stt.model || ''),
    },
    textToSpeech: {
      provider: String(tts.provider || 'openai'),
      quality: String(tts.quality || 'medium'),
      voice: String(tts.voice || ''),
      piperBaseUrl: String(tts.piperBaseUrl || 'http://127.0.0.1:5000'),
      externalBaseUrl: String(tts.externalBaseUrl || ''),
      apiKey: String(tts.apiKey || ''),
      model: String(tts.model || ''),
    },
  };
}

function loadStoredSession() {
  try {
    const raw = localStorage.getItem(SESSION_STORAGE_KEY);
    if (!raw) return null;
    return JSON.parse(raw);
  } catch {
    return null;
  }
}

function loadStoredReactions() {
  try {
    const raw = localStorage.getItem(REACTIONS_STORAGE_KEY);
    if (!raw) return {};
    const parsed = JSON.parse(raw);
    if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return {};
    return parsed;
  } catch {
    return {};
  }
}

function persistStoredReactions(reactions) {
  try {
    localStorage.setItem(REACTIONS_STORAGE_KEY, JSON.stringify(reactions || {}));
  } catch {
    // ignore storage errors
  }
}

function persistSession(session) {
  try {
    localStorage.setItem(SESSION_STORAGE_KEY, JSON.stringify(session));
  } catch {
    // ignore quota/storage errors for prototype
  }
}

function clearStoredSession() {
  try {
    localStorage.removeItem(SESSION_STORAGE_KEY);
  } catch {
    // ignore
  }
}

function loadStoredEntrySettings() {
  try {
    const raw = localStorage.getItem(ENTRY_SETTINGS_STORAGE_KEY);
    if (!raw) return null;
    const parsed = JSON.parse(raw);
    if (!parsed || typeof parsed !== 'object') return null;
    return parsed;
  } catch {
    return null;
  }
}

function persistEntrySettings(settings) {
  try {
    const payload = {
      language: String(settings?.language || 'ru'),
      solanaServer: String(settings?.solanaServer || DEFAULT_SOLANA_SERVER),
      shineServer: String(settings?.shineServer || DEFAULT_SHINE_SERVER),
      arweaveServer: String(settings?.arweaveServer || DEFAULT_ARWEAVE_SERVER),
      callPreflightTimeoutMs: Math.max(1000, Math.min(20000, Number(settings?.callPreflightTimeoutMs || DEFAULT_CALL_PREFLIGHT_TIMEOUT_MS) || DEFAULT_CALL_PREFLIGHT_TIMEOUT_MS)),
      statuses: {
        solanaServer: String(settings?.statuses?.solanaServer || 'idle'),
        shineServer: String(settings?.statuses?.shineServer || 'idle'),
        arweaveServer: String(settings?.statuses?.arweaveServer || 'idle'),
      },
      tools: normalizeToolsSettings(settings?.tools),
    };
    localStorage.setItem(ENTRY_SETTINGS_STORAGE_KEY, JSON.stringify(payload));
  } catch {
    // ignore storage errors
  }
}

export function clearBrowserClientData() {
  const localKeys = [
    SESSION_STORAGE_KEY,
    REACTIONS_STORAGE_KEY,
    WEB_PUSH_SUBSCRIPTION_KEY,
    CHANNEL_NOTIFY_KEY,
    CHANNELS_DEMO_KEY,
  ];
  localKeys.forEach((key) => {
    try {
      localStorage.removeItem(key);
    } catch {
      // ignore
    }
  });

  try {
    sessionStorage.removeItem(CREATE_CHANNEL_FLASH_KEY);
  } catch {
    // ignore
  }
}

function createInitialState({ withStoredSession = true } = {}) {
  const storedSession = withStoredSession ? loadStoredSession() : null;
  const storedReactions = loadStoredReactions();
  const storedEntrySettings = loadStoredEntrySettings();
  const initialShineServer = LOCAL_WS_OVERRIDE_URL || DEFAULT_SHINE_SERVER;

  return {
    chats: {},
    contacts: [],
    appLog: [],
    incomingDedup: {},
    knownMessageKeys: {},
    outgoingTempSeq: 1,
    notificationsTab: 'replies',
    pageLabelCollapsed: false,
    session: {
      isAuthorized: false,
      login: storedSession?.login || '',
      sessionId: storedSession?.sessionId || '',
      storagePwdInMemory: '',
    },
    startHint: '',
    entrySettings: {
      language: String(storedEntrySettings?.language || 'ru'),
      solanaServer: String(storedEntrySettings?.solanaServer || DEFAULT_SOLANA_SERVER),
      shineServer: String(LOCAL_WS_OVERRIDE_URL || storedEntrySettings?.shineServer || initialShineServer),
      arweaveServer: String(storedEntrySettings?.arweaveServer || DEFAULT_ARWEAVE_SERVER),
      callPreflightTimeoutMs: Math.max(1000, Math.min(20000, Number(storedEntrySettings?.callPreflightTimeoutMs || DEFAULT_CALL_PREFLIGHT_TIMEOUT_MS) || DEFAULT_CALL_PREFLIGHT_TIMEOUT_MS)),
      statuses: {
        solanaServer: String(storedEntrySettings?.statuses?.solanaServer || 'idle'),
        shineServer: String(storedEntrySettings?.statuses?.shineServer || 'idle'),
        arweaveServer: String(storedEntrySettings?.statuses?.arweaveServer || 'idle'),
      },
      tools: normalizeToolsSettings(storedEntrySettings?.tools),
    },
    registrationDraft: {
      flowType: '',
      login: '',
      password: '',
      sessionId: '',
      storagePwd: '',
      pendingKeyBundle: null,
      pendingSessionMaterial: null,
      preGeneratedKeyBundle: null,
    },
    loginDraft: {
      login: storedSession?.login || '',
      password: '',
    },
    registrationPayment: {
      walletAddress: '',
      balanceSOL: '0.0000',
    },
    keyStorage: {
      rootKey: 'Ключ root хранится в зашифрованном виде',
      blockchainKey: 'Ключ blockchain хранится в зашифрованном виде',
      deviceKey: 'Ключ device хранится в зашифрованном виде',
      saveRoot: false,
      saveBlockchain: true,
      saveDevice: true,
    },
    deviceConnect: {
      root: true,
      blockchain: true,
      device: true,
    },
    authUi: {
      busy: false,
      error: '',
      info: '',
    },
    authReturnHash: '',
    sessions: [],
    channelsFeed: null,
    channelsIndex: {},
    localChannelPosts: {},
    messageReactions: storedReactions,
  };
}

export const state = createInitialState();

export const authService = new AuthService(state.entrySettings.shineServer);
let onSessionReset = null;
let onSessionAuthorized = null;

function parseMessageTimeFromKey(rawKey) {
  const value = String(rawKey || '').trim();
  if (!value) return 0;
  const parts = value.split('|');
  if (parts.length < 4) return 0;
  const timeMs = Number(parts[2] || 0);
  if (!Number.isFinite(timeMs) || timeMs <= 0) return 0;
  return Math.trunc(timeMs);
}

function resolveChatMessageTimeMs(row) {
  const fromBaseKey = parseMessageTimeFromKey(row?.baseKey);
  if (fromBaseKey > 0) return fromBaseKey;

  const fromMessageKey = parseMessageTimeFromKey(row?.messageKey);
  if (fromMessageKey > 0) return fromMessageKey;

  const fromCreatedAt = Number(row?.createdAtMs || row?.ts || 0);
  if (Number.isFinite(fromCreatedAt) && fromCreatedAt > 0) return Math.trunc(fromCreatedAt);

  const tempId = String(row?.tempId || '').trim();
  if (tempId.startsWith('tmp-')) {
    const parts = tempId.split('-');
    const ts = Number(parts[1] || 0);
    if (Number.isFinite(ts) && ts > 0) return Math.trunc(ts);
  }

  return 0;
}

function stableMessageOrderKey(row) {
  const key = String(row?.messageKey || '').trim();
  if (key) return `mk:${key}`;
  const tmp = String(row?.tempId || '').trim();
  if (tmp) return `tmp:${tmp}`;
  const base = String(row?.baseKey || '').trim();
  if (base) return `bk:${base}`;
  const text = String(row?.text || '').trim();
  return `txt:${text}`;
}

function sortChatMessagesInPlace(chatId) {
  const list = getChatMessages(chatId);
  list.sort((a, b) => {
    const ta = resolveChatMessageTimeMs(a);
    const tb = resolveChatMessageTimeMs(b);
    if (ta !== tb) return ta - tb;

    const ka = stableMessageOrderKey(a);
    const kb = stableMessageOrderKey(b);
    const byKey = ka.localeCompare(kb, 'ru');
    if (byKey !== 0) return byKey;

    if ((a?.from || '') !== (b?.from || '')) {
      return (a?.from === 'in') ? -1 : 1;
    }
    return 0;
  });
}

function persistMessageRecord(chatId, row) {
  if (!chatId || !row?.messageKey) return;
  const resolvedTs = resolveChatMessageTimeMs(row);
  void putStoredMessage({
    messageKey: row.messageKey,
    chatId,
    from: row.from || 'in',
    text: String(row.text || ''),
    baseKey: String(row.baseKey || ''),
    messageType: Number(row.messageType || 0),
    rawBlobB64: String(row.rawBlobB64 || ''),
    unread: Boolean(row.unread),
    firstTick: Boolean(row.firstTick),
    secondTick: Boolean(row.secondTick),
    readReceiptSent: Boolean(row.readReceiptSent),
    refBaseKey: String(row.refBaseKey || ''),
    ts: resolvedTs > 0 ? resolvedTs : Date.now(),
  }).catch(() => {});
}

export async function hydrateMessagesFromStore() {
  try {
    const rows = await listStoredMessages();
    const touchedChats = new Set();
    rows
      .sort((a, b) => Number(a?.ts || 0) - Number(b?.ts || 0))
      .forEach((row) => {
        const chatId = String(row?.chatId || '').trim();
        const messageKey = String(row?.messageKey || '').trim();
        if (!chatId || !messageKey) return;
        if (state.knownMessageKeys[messageKey]) return;
        state.knownMessageKeys[messageKey] = true;
        touchedChats.add(chatId);
        getChatMessages(chatId).push({
          from: row.from === 'out' ? 'out' : 'in',
          text: String(row.text || ''),
          messageKey,
          baseKey: String(row.baseKey || ''),
          messageType: Number(row.messageType || 0),
          rawBlobB64: String(row.rawBlobB64 || ''),
          unread: Boolean(row.unread),
          firstTick: Boolean(row.firstTick),
          secondTick: Boolean(row.secondTick),
          readReceiptSent: Boolean(row.readReceiptSent),
          refBaseKey: String(row.refBaseKey || ''),
          createdAtMs: Number(row.ts || 0),
        });
      });
    touchedChats.forEach((chatId) => sortChatMessagesInPlace(chatId));
  } catch {
    // ignore broken storage
  }
}

export function getChatMessages(chatId) {
  if (!state.chats[chatId]) {
    state.chats[chatId] = [];
  }
  return state.chats[chatId];
}

export function addChatMessage(chatId, text) {
  const message = text.trim();
  if (!message) return;
  getChatMessages(chatId).push({
    from: 'out',
    text: message,
    firstTick: false,
    secondTick: false,
    unread: false,
    createdAtMs: Date.now(),
  });
  sortChatMessagesInPlace(chatId);
}

export function addSystemChatMessage(chatId, text, { from = 'out', kind = 'system' } = {}) {
  const message = String(text || '').trim();
  if (!message) return;
  getChatMessages(chatId).push({
    from: from === 'in' ? 'in' : 'out',
    text: message,
    kind: String(kind || 'system'),
    unread: from === 'in',
    firstTick: from !== 'in',
    secondTick: false,
    createdAtMs: Date.now(),
  });
  sortChatMessagesInPlace(chatId);
}


export function addIncomingMessage(chatId, text, messageId = '') {
  const msg = text?.trim();
  if (!msg) return false;
  if (messageId && state.incomingDedup[messageId]) return false;
  if (messageId) state.incomingDedup[messageId] = true;
  getChatMessages(chatId).push({
    from: 'in',
    text: msg,
    messageId,
    unread: true,
    createdAtMs: Date.now(),
  });
  sortChatMessagesInPlace(chatId);
  return true;
}

export function addOutgoingPendingMessage(chatId, text) {
  const msg = String(text || '').trim();
  if (!msg) return null;
  const tempId = `tmp-${Date.now()}-${state.outgoingTempSeq++}`;
  getChatMessages(chatId).push({
    from: 'out',
    text: msg,
    tempId,
    firstTick: false,
    secondTick: false,
    unread: false,
    createdAtMs: Date.now(),
  });
  sortChatMessagesInPlace(chatId);
  return tempId;
}

export function markOutgoingSent(tempId, { messageKey = '', baseKey = '' } = {}) {
  if (!tempId) return;
  const keys = Object.keys(state.chats || {});
  keys.forEach((chatId) => {
    const list = getChatMessages(chatId);
    const row = list.find((item) => item?.tempId === tempId);
    if (!row) return;
    row.firstTick = true;
    row.messageKey = messageKey || row.messageKey || '';
    row.baseKey = baseKey || row.baseKey || '';
    if (messageKey) {
      state.knownMessageKeys[messageKey] = true;
      persistMessageRecord(chatId, row);
    }
    sortChatMessagesInPlace(chatId);
  });
}

export function markOutgoingReadByBaseKey(baseKey) {
  if (!baseKey) return;
  const keys = Object.keys(state.chats || {});
  keys.forEach((chatId) => {
    const list = getChatMessages(chatId);
    list.forEach((row) => {
      if (row?.from !== 'out') return;
      if (row.baseKey === baseKey) {
        row.secondTick = true;
        persistMessageRecord(chatId, row);
      }
    });
  });
}

export function markIncomingReadByBaseKey(baseKey) {
  if (!baseKey) return;
  const keys = Object.keys(state.chats || {});
  keys.forEach((chatId) => {
    const list = getChatMessages(chatId);
    list.forEach((row) => {
      if (row?.from !== 'in') return;
      if (row.baseKey === baseKey) {
        row.unread = false;
        row.readReceiptSent = true;
        persistMessageRecord(chatId, row);
      }
    });
  });
}

export function markReadReceiptSentByBaseKey(baseKey) {
  if (!baseKey) return;
  const keys = Object.keys(state.chats || {});
  keys.forEach((chatId) => {
    const list = getChatMessages(chatId);
    list.forEach((row) => {
      if (row?.from !== 'in') return;
      if (row.baseKey === baseKey) {
        row.readReceiptSent = true;
        persistMessageRecord(chatId, row);
      }
    });
  });
}

export function addSignedMessageToChat({
  chatId,
  messageKey,
  baseKey = '',
  from = 'in',
  text = '',
  messageType = 1,
  unread = false,
  rawBlobB64 = '',
  refBaseKey = '',
} = {}) {
  const id = String(messageKey || '').trim();
  if (!chatId || !id) return false;
  if (state.knownMessageKeys[id]) return false;
  state.knownMessageKeys[id] = true;

  const row = {
    from: from === 'out' ? 'out' : 'in',
    text: String(text || ''),
    messageKey: id,
    baseKey: String(baseKey || ''),
    messageType: Number(messageType || 0),
    rawBlobB64: String(rawBlobB64 || ''),
    unread: Boolean(unread),
    refBaseKey: String(refBaseKey || ''),
    firstTick: from === 'out',
    secondTick: false,
  };
  getChatMessages(chatId).push(row);
  sortChatMessagesInPlace(chatId);
  persistMessageRecord(chatId, row);
  return true;
}

export function markChatRead(chatId) {
  const list = getChatMessages(chatId);
  list.forEach((row) => {
    if (row?.from === 'in') {
      row.unread = false;
      persistMessageRecord(chatId, row);
    }
  });
}

export function setContacts(list) {
  state.contacts = Array.isArray(list) ? [...list] : [];
}

function toText(value) {
  if (typeof value === 'string') return value;
  if (value == null) return '';
  try {
    return JSON.stringify(value);
  } catch {
    return String(value);
  }
}

export function addAppLogEntry({
  level = 'info',
  source = 'ui',
  message = '',
  details = '',
} = {}) {
  const cleanMessage = String(message || '').trim();
  if (!cleanMessage) return;
  const cleanLevel = String(level || 'info').trim().toLowerCase();
  const normalizedLevel = (cleanLevel === 'error' || cleanLevel === 'warn') ? cleanLevel : 'info';

  state.appLog.push({
    id: `${Date.now()}-${Math.random().toString(16).slice(2, 10)}`,
    ts: Date.now(),
    level: normalizedLevel,
    source: String(source || 'ui').trim() || 'ui',
    message: cleanMessage,
    details: toText(details),
  });

  if (state.appLog.length > MAX_APP_LOG_ENTRIES) {
    state.appLog.splice(0, state.appLog.length - MAX_APP_LOG_ENTRIES);
  }
}

export function getAppLogEntries() {
  return [...state.appLog];
}

export function clearAppLogEntries() {
  state.appLog = [];
}

export function togglePageLabel() {
  state.pageLabelCollapsed = !state.pageLabelCollapsed;
}

export function ensureChat(chatId) {
  return getChatMessages(chatId);
}

export function checkServerAvailability(address) {
  const normalized = String(address || '').trim().toLowerCase();
  if (!normalized) return 'unavailable';
  return /^(https?:\/\/|wss?:\/\/)/i.test(normalized) ? 'available' : 'unavailable';
}

export async function saveEntrySettings(nextSettings) {
  const forcedShineServer = LOCAL_WS_OVERRIDE_URL || nextSettings.shineServer;
  state.entrySettings = {
    ...state.entrySettings,
    ...nextSettings,
    shineServer: forcedShineServer,
    statuses: {
      ...state.entrySettings.statuses,
      ...(nextSettings.statuses || {}),
    },
    tools: normalizeToolsSettings(nextSettings.tools || state.entrySettings.tools),
  };
  persistEntrySettings(state.entrySettings);
  await authService.reconnect(state.entrySettings.shineServer);
  state.startHint = 'Настройки входа сохранены, адреса серверов обновлены.';
}

export function clearStartHint() {
  state.startHint = '';
}

export function setAuthBusy(flag) {
  state.authUi.busy = flag;
}

export function setAuthError(message) {
  state.authUi.error = message || '';
}

export function setAuthInfo(message) {
  state.authUi.info = message || '';
}

export function clearAuthMessages() {
  state.authUi.error = '';
  state.authUi.info = '';
}

export function authorizeSession({
  login = state.session.login,
  sessionId = state.session.sessionId,
  storagePwd = state.session.storagePwdInMemory,
} = {}) {
  state.session.isAuthorized = true;
  state.session.login = login;
  state.session.sessionId = sessionId;
  state.session.storagePwdInMemory = storagePwd;
  persistSession({
    isAuthorized: true,
    login,
    sessionId,
  });
  state.startHint = '';
  if (onSessionAuthorized) {
    onSessionAuthorized();
  }
}

export function setSessionResetHandler(handler) {
  onSessionReset = typeof handler === 'function' ? handler : null;
}

export function setSessionAuthorizedHandler(handler) {
  onSessionAuthorized = typeof handler === 'function' ? handler : null;
}

export function isSessionInvalidError(error) {
  return INVALID_SESSION_CODES.has(error?.code);
}

export async function refreshSessions() {
  state.sessions = await authService.listSessions();
  return state.sessions;
}

function resetStateForSignedOut() {
  const next = createInitialState({ withStoredSession: false });
  state.chats = next.chats;
  state.contacts = next.contacts;
  state.appLog = next.appLog;
  state.incomingDedup = next.incomingDedup;
  state.knownMessageKeys = next.knownMessageKeys;
  state.outgoingTempSeq = next.outgoingTempSeq;
  state.notificationsTab = next.notificationsTab;
  state.pageLabelCollapsed = next.pageLabelCollapsed;
  state.session = next.session;
  state.startHint = next.startHint;
  state.entrySettings = next.entrySettings;
  state.registrationDraft = next.registrationDraft;
  state.loginDraft = next.loginDraft;
  state.registrationPayment = next.registrationPayment;
  state.keyStorage = next.keyStorage;
  state.deviceConnect = next.deviceConnect;
  state.authUi = next.authUi;
  state.sessions = next.sessions;
  state.channelsFeed = next.channelsFeed;
  state.channelsIndex = next.channelsIndex;
  state.localChannelPosts = next.localChannelPosts;
  state.messageReactions = next.messageReactions;
}

export async function terminateCurrentSession({ infoMessage = '' } = {}) {
  clearStoredSession();
  resetStateForSignedOut();
  await clearStoredMessages().catch(() => {});
  authService.close();
  if (infoMessage) {
    state.startHint = infoMessage;
  }
  try {
    await authService.reconnect(state.entrySettings.shineServer);
  } catch {
    // ignore reconnect errors on sign out
  }
  if (onSessionReset) {
    onSessionReset();
  }
}

export async function closeCurrentSessionAndSignOut({ infoMessage = '' } = {}) {
  const currentSessionId = String(state.session.sessionId || '').trim();
  try {
    if (state.session.isAuthorized && currentSessionId) {
      await authService.closeSession(currentSessionId);
    }
  } catch (error) {
    addAppLogEntry({
      level: 'warn',
      source: 'session',
      message: 'Не удалось завершить текущую сессию на сервере',
      details: { sessionId: currentSessionId, error: error?.message || 'unknown' },
    });
  }

  await terminateCurrentSession({ infoMessage });
}

export function refreshRegistrationBalance() {
  const next = (0.005 + Math.random() * 0.03).toFixed(4);
  state.registrationPayment.balanceSOL = next;
  return next;
}

export function setChannelsFeed(feed, index) {
  state.channelsFeed = feed || null;
  state.channelsIndex = index || {};
}

export function getLocalChannelPosts(channelId) {
  if (!channelId) return [];
  if (!state.localChannelPosts[channelId]) {
    state.localChannelPosts[channelId] = [];
  }
  return state.localChannelPosts[channelId];
}

export function addLocalChannelPost(channelId, post) {
  if (!channelId) return;
  const text = post?.body?.trim();
  if (!text) return;

  getLocalChannelPosts(channelId).push({
    title: post.title || `${state.session.login || 'Вы'} • сейчас`,
    body: text,
  });
}

function makeMessageReactionKey(messageRef, login = state.session.login) {
  const bch = String(messageRef?.blockchainName || '').trim();
  const blockNumber = Number(messageRef?.blockNumber);
  const blockHash = String(messageRef?.blockHash || '').trim().toLowerCase();
  const cleanLogin = String(login || '').trim().toLowerCase();
  if (!cleanLogin || !bch || !Number.isFinite(blockNumber) || blockNumber < 0 || !blockHash) return '';
  return `${cleanLogin}|${bch}|${blockNumber}|${blockHash}`;
}

export function getMessageReactionState(messageRef) {
  const key = makeMessageReactionKey(messageRef);
  if (!key) return '';
  return state.messageReactions[key] || '';
}

export function setMessageReactionState(messageRef, nextState) {
  const key = makeMessageReactionKey(messageRef);
  if (!key) return;
  const normalized = String(nextState || '').trim().toLowerCase();
  if (normalized === 'liked' || normalized === 'unliked') {
    state.messageReactions[key] = normalized;
    persistStoredReactions(state.messageReactions);
    return;
  }
  delete state.messageReactions[key];
  persistStoredReactions(state.messageReactions);
}
