import { createRequesterPairingMaterial, decryptPairingPayloadFromEnvelope, deriveEspPairingPasswordHash } from './js/lib/device-pairing.js';
import { loadPluginSettings, loadSessionMaterial, savePluginSettings, saveSessionMaterial, clearSessionMaterial } from './js/lib/session-store.js';
import { ShineApiClient } from './js/lib/shine-api.js';
import {
  DEFAULT_SHINE_SERVER_LOGIN,
  buildHttpBase,
  readWalletProfileByLogin,
  resolveShineServerByUserLogin,
} from './js/lib/shine-server-resolver.js';

const state = {
  api: null,
  settings: {
    serverLogin: DEFAULT_SHINE_SERVER_LOGIN,
    serverHttp: buildHttpBase('shineup.me'),
    serverUrl: 'wss://shineup.me/ws',
    login: '',
  },
  requesterMaterial: null,
  pairingId: '',
  expiresAtMs: 0,
  shortCode: '',
  trustedSessionOnline: false,
  pollTimer: 0,
  activeSession: null,
  connectionOnline: false,
  walletProfile: null,
  signing: {
    selectedDeviceName: '',
    devicesResolvedAtMs: 0,
  },
  currentWallet: null,
  pendingApprovals: [],
  siteApprovalChain: Promise.resolve(),
  sessionAttachInProgress: false,
  statusText: '',
  statusKind: 'info',
};

const WALLET_RPC_REQUEST_TYPE = 9100;
const WALLET_RPC_RESPONSE_TYPE = 9101;

async function configureSidePanelBehavior() {
  if (!chrome.sidePanel?.setPanelBehavior) {
    return;
  }
  try {
    await chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
  } catch (error) {
    console.warn('Failed to configure SHiNE side panel behavior:', error);
  }
}

function normalizeOrigin(value = '') {
  const raw = String(value || '').trim();
  if (!raw) return '';
  try {
    return new URL(raw).origin;
  } catch {
    return raw;
  }
}

function makeCodeError(message, code) {
  const error = new Error(String(message || 'Wallet error'));
  error.code = String(code || '').trim().toUpperCase();
  return error;
}

function setStatus(message = '', kind = 'info') {
  state.statusText = String(message || '');
  state.statusKind = kind === 'error' ? 'error' : 'info';
}

function makePendingApprovalSnapshot(payload = {}) {
  const pendingId = String(payload?.id || '').trim();
  const pendingApprovals = Array.isArray(state.pendingApprovals) ? state.pendingApprovals : [];
  const queueIndex = pendingApprovals.findIndex((item) => String(item?.id || '').trim() === pendingId);
  const queueLength = pendingApprovals.length;
  return {
    id: pendingId,
    kind: String(payload?.kind || 'sign_transaction').trim() || 'sign_transaction',
    origin: String(payload?.origin || '').trim(),
    publicKeyBase58: String(payload?.publicKeyBase58 || '').trim(),
    comment: String(payload?.comment || '').trim(),
    createdAtMs: Number(payload?.createdAtMs || Date.now()),
    status: String(payload?.status || 'queued').trim() || 'queued',
    queuePosition: queueIndex >= 0 ? queueIndex + 1 : 1,
    queueLength: queueLength || 1,
    transactionSummary: payload?.transactionSummary && typeof payload.transactionSummary === 'object'
      ? { ...payload.transactionSummary }
      : null,
  };
}

function getCurrentPendingApproval() {
  return Array.isArray(state.pendingApprovals) && state.pendingApprovals.length
    ? state.pendingApprovals[0]
    : null;
}

function removePendingApproval(pendingId, { rejectError = null } = {}) {
  const pendingApprovals = Array.isArray(state.pendingApprovals) ? state.pendingApprovals : [];
  const index = pendingApprovals.findIndex((item) => String(item?.id || '').trim() === String(pendingId || '').trim());
  if (index < 0) return;
  const [pending] = pendingApprovals.splice(index, 1);
  if (pending.timeoutId) {
    clearTimeout(pending.timeoutId);
  }
  if (rejectError && pending.abortController) {
    try {
      pending.abortController.abort(rejectError);
    } catch {}
  }
}

async function openSidePanelForSender(sender) {
  if (!chrome.sidePanel?.open || !sender?.tab?.id) return;
  try {
    await chrome.sidePanel.open({ tabId: sender.tab.id });
  } catch {}
}

function stopPoll() {
  if (state.pollTimer) {
    clearTimeout(state.pollTimer);
    state.pollTimer = 0;
  }
}

function clearPairingState() {
  stopPoll();
  state.requesterMaterial = null;
  state.pairingId = '';
  state.expiresAtMs = 0;
  state.shortCode = '';
  state.trustedSessionOnline = false;
}

function ensureApi(serverUrl = state.settings.serverUrl) {
  const normalized = String(serverUrl || '').trim() || 'wss://shineup.me/ws';
  if (!state.api || state.api.serverUrl !== normalized) {
    state.api?.close();
    state.api = new ShineApiClient(normalized);
  }
  return state.api;
}

async function loadStateFromStorage() {
  const settings = await loadPluginSettings();
  state.settings = {
    serverLogin: String(settings?.serverLogin || state.settings.serverLogin || DEFAULT_SHINE_SERVER_LOGIN).trim(),
    serverHttp: String(settings?.serverHttp || state.settings.serverHttp || buildHttpBase('shineup.me')).trim() || buildHttpBase('shineup.me'),
    serverUrl: String(settings?.serverUrl || state.settings.serverUrl || 'wss://shineup.me/ws').trim() || 'wss://shineup.me/ws',
    login: String(settings?.login || '').trim(),
    connectedOrigins: Array.isArray(settings?.connectedOrigins) ? settings.connectedOrigins.map((item) => normalizeOrigin(item)).filter(Boolean) : [],
  };
  const storedSession = await loadSessionMaterial();
  if (storedSession || !state.sessionAttachInProgress) {
    state.activeSession = storedSession;
  }
  state.walletProfile = state.activeSession?.walletProfile || null;
  state.signing = {
    selectedDeviceName: String(state.activeSession?.selectedDeviceName || ''),
    devicesResolvedAtMs: Number(state.activeSession?.devicesResolvedAtMs || 0),
  };
  state.currentWallet = state.activeSession?.currentWallet || null;
}

async function persistSettings(nextSettings = {}) {
  state.settings = {
    ...state.settings,
    ...nextSettings,
  };
  if (!Array.isArray(state.settings.connectedOrigins)) {
    state.settings.connectedOrigins = [];
  }
  await savePluginSettings(state.settings);
  return state.settings;
}

function isOriginApproved(origin) {
  const normalized = normalizeOrigin(origin);
  return !!normalized && Array.isArray(state.settings.connectedOrigins) && state.settings.connectedOrigins.includes(normalized);
}

async function setOriginApproved(origin, approved) {
  const normalized = normalizeOrigin(origin);
  const current = new Set(Array.isArray(state.settings.connectedOrigins) ? state.settings.connectedOrigins : []);
  if (approved) {
    if (normalized) current.add(normalized);
  } else if (normalized) {
    current.delete(normalized);
  }
  await persistSettings({ connectedOrigins: [...current] });
}

async function resolveServerForLogin(login) {
  const cleanLogin = String(login || state.settings.login || '').trim();
  if (!cleanLogin) {
    state.settings = {
      ...state.settings,
      login: '',
      serverLogin: '',
    };
    await savePluginSettings(state.settings);
    return { ok: true, resolved: false };
  }

  const resolved = await resolveShineServerByUserLogin(cleanLogin);
  state.settings = {
    ...state.settings,
    login: cleanLogin,
    serverLogin: resolved.serverLogin,
    serverHttp: resolved.serverHttp,
    serverUrl: resolved.serverUrl,
  };
  await savePluginSettings(state.settings);
  return { ok: true, resolved: true, ...resolved };
}

async function saveActiveSessionRecord() {
  if (!state.activeSession) return;
  const nextRecord = {
    ...state.activeSession,
    walletProfile: state.walletProfile,
    selectedDeviceName: state.signing.selectedDeviceName,
    devicesResolvedAtMs: state.signing.devicesResolvedAtMs,
    currentWallet: state.currentWallet,
  };
  state.activeSession = nextRecord;
  await saveSessionMaterial(nextRecord);
}

function shortKey(value = '', size = 10) {
  const raw = String(value || '').trim();
  return raw ? raw.slice(0, size) : '';
}

function extractErrorCode(message = '') {
  const match = String(message || '').match(/\(([A-Z0-9_]+)\)\s*$/i);
  return match ? String(match[1]).toUpperCase() : '';
}

function toWalletErrorMessage(error, fallback = 'Не удалось выполнить операцию кошелька.') {
  const raw = String(error?.message || '').trim();
  const code = String(error?.code || extractErrorCode(raw) || '').toUpperCase();
  if (code === 'PAIRING_NOT_AVAILABLE') {
    return 'Для этого логина ещё не включено подключение по коду. На доверенном устройстве откройте «Подключить по коду» и нажмите «Включить подключение по коду» или задайте дополнительный пароль.';
  }
  if (code === 'PAIRING_NO_TRUSTED_SESSION_ONLINE') {
    return 'Сейчас нет ни одной онлайн доверенной сессии этого пользователя. Откройте SHiNE на другом уже подключённом устройстве и держите его в сети.';
  }
  if (code === 'PAIRING_PASSWORD_INVALID') {
    return 'Дополнительный пароль подключения не подходит.';
  }
  return raw || fallback;
}

function homeserverSessionNameFromClientInfo(value = '') {
  const raw = String(value || '').trim();
  const match = raw.match(/^ESP32 homeserver:(.+)$/i);
  return match ? String(match[1] || '').trim() : '';
}

function mergeHomeserverStatuses(publishedHomeservers = [], serverSessions = []) {
  const published = Array.isArray(publishedHomeservers) ? publishedHomeservers : [];
  const homeserverSessions = Array.isArray(serverSessions)
    ? serverSessions.filter((item) => Number(item?.sessionType || 0) === 100)
    : [];
  const onlineHomeservers = homeserverSessions.filter((item) => !!item?.onlineOnThisServer);
  const byName = new Map();
  onlineHomeservers.forEach((item) => {
    const sessionName = homeserverSessionNameFromClientInfo(item?.clientInfoFromClient);
    if (sessionName) {
      byName.set(sessionName, item);
    }
  });

  return published.map((item) => {
    const matched = byName.get(String(item?.sessionName || '').trim()) || null;
    let onlineState = matched ? 'online' : 'offline';
    let activeSessionId = matched?.sessionId ? String(matched.sessionId) : '';
    if (!matched && published.length === 1 && onlineHomeservers.length === 1) {
      onlineState = 'online';
      activeSessionId = String(onlineHomeservers[0]?.sessionId || '');
    }
    return {
      ...item,
      activeSessionId,
      onlineState,
      onlineLabel: onlineState === 'online' ? 'online' : onlineState === 'offline' ? 'offline' : 'unknown',
    };
  });
}

async function hydrateWalletProfile(login) {
  const cleanLogin = String(login || state.activeSession?.login || state.settings.login || '').trim();
  if (!cleanLogin) throw new Error('Нет логина для чтения PDA кошелька.');
  const profile = await readWalletProfileByLogin(cleanLogin);
  const selectedDeviceName = state.signing.selectedDeviceName
    || String(profile?.homeserverSessions?.[0]?.sessionName || '');

  state.walletProfile = {
    ...profile,
    homeserverSessions: Array.isArray(profile.homeserverSessions) ? profile.homeserverSessions.map((item) => ({
      ...item,
      onlineState: 'unknown',
      onlineLabel: 'unknown',
      activeSessionId: '',
    })) : [],
  };
  state.signing = {
    ...state.signing,
    selectedDeviceName,
  };
  await saveActiveSessionRecord();
  return state.walletProfile;
}

async function resumeActiveSession({ keepConnected = false } = {}) {
  const sessionRecord = await loadSessionMaterial();
  state.activeSession = sessionRecord;
  if (!sessionRecord) {
    state.connectionOnline = false;
    setStatus('Wallet-session ещё не подключена.', 'info');
    return { ok: true, connected: false };
  }

  try {
    await persistSettings({
      serverLogin: String(sessionRecord?.serverLogin || state.settings.serverLogin || DEFAULT_SHINE_SERVER_LOGIN).trim(),
      serverHttp: String(sessionRecord?.serverHttp || state.settings.serverHttp || buildHttpBase('shineup.me')).trim(),
      serverUrl: String(sessionRecord?.serverUrl || state.settings.serverUrl || 'wss://shineup.me/ws').trim(),
      login: String(sessionRecord?.login || state.settings.login || '').trim(),
    });
    const resumed = await ensureApi().resumeSession(sessionRecord);
    state.connectionOnline = !!keepConnected;
    if (!keepConnected) {
      ensureApi().close();
      state.api = null;
      setStatus(`Wallet-session сохранена для @${resumed.login}. Подключение будет открываться только по действию.`, 'info');
    } else {
      setStatus(`Wallet-session активна для @${resumed.login}.`, 'info');
    }
    return { ok: true, connected: true, login: resumed.login, sessionId: resumed.sessionId };
  } catch (error) {
    state.connectionOnline = false;
    setStatus(error.message || 'Не удалось восстановить wallet-session.', 'error');
    return { ok: false, connected: false, error: state.statusText };
  }
}

async function attachApprovedSession(payload) {
  if (String(payload?.type || '') !== 'shine-esp-session-attach') {
    throw new Error('Доверенное устройство вернуло неподдерживаемый payload. Для plugin нужен session-only approve.');
  }

  const login = String(payload?.login || state.settings.login || '').trim();
  const approvedSession = payload?.session || {};
  const sessionRecord = {
    login,
    sessionId: String(approvedSession?.sessionId || '').trim(),
    sessionKey: state.requesterMaterial?.sessionKey || '',
    sessionPrivPkcs8: state.requesterMaterial?.sessionPrivPkcs8 || '',
    sessionType: Number(approvedSession?.sessionType || 50) || 50,
    serverLogin: state.settings.serverLogin,
    serverHttp: state.settings.serverHttp,
    serverUrl: state.settings.serverUrl,
  };
  if (!sessionRecord.login || !sessionRecord.sessionId || !sessionRecord.sessionKey || !sessionRecord.sessionPrivPkcs8) {
    throw new Error('Получен неполный session-only payload');
  }

  state.sessionAttachInProgress = true;
  try {
    state.activeSession = sessionRecord;
    state.walletProfile = null;
    state.currentWallet = null;
    state.signing = {
      ...state.signing,
      selectedDeviceName: '',
      devicesResolvedAtMs: 0,
    };
    await saveActiveSessionRecord();
    await hydrateWalletProfile(login);
    await saveActiveSessionRecord();
    await persistSettings({
      login: sessionRecord.login,
      serverLogin: sessionRecord.serverLogin,
      serverHttp: sessionRecord.serverHttp,
      serverUrl: sessionRecord.serverUrl,
    });
    state.connectionOnline = false;
    state.currentWallet = null;
  } finally {
    state.sessionAttachInProgress = false;
  }
}

async function pollPairingStatus() {
  if (!state.pairingId || !state.requesterMaterial) return;
  try {
      const payload = await ensureApi().getTrustedDeviceLoginStatus(state.pairingId);
    const stateValue = String(payload?.state || '');
    if (stateValue === 'created') {
      state.pollTimer = setTimeout(() => {
        void pollPairingStatus();
      }, 2200);
      return;
    }
    if (stateValue === 'approved') {
      const decoded = await decryptPairingPayloadFromEnvelope(payload?.encryptedPayload, state.requesterMaterial);
      await attachApprovedSession(decoded);
      clearPairingState();
      setStatus('Wallet-session создана и сохранена. Кошелёк остаётся офлайн до запроса подписи.', 'info');
      return;
    }
    if (stateValue === 'rejected') {
      clearPairingState();
      setStatus('Заявка отклонена на доверенном устройстве.', 'error');
      return;
    }
    if (stateValue === 'expired' || stateValue === 'canceled') {
      clearPairingState();
      setStatus('Ожидание подключения завершено.', 'error');
      return;
    }
    state.pollTimer = setTimeout(() => {
      void pollPairingStatus();
    }, 2200);
  } catch (error) {
    clearPairingState();
    setStatus(error.message || 'Не удалось проверить pairing-статус.', 'error');
  }
}

async function startPairing({ login, usePassword, password }) {
  const cleanLogin = String(login || '').trim();
  if (!cleanLogin) {
    throw new Error('Введите логин.');
  }

  await persistSettings({ login: cleanLogin });
  await resolveServerForLogin(cleanLogin);
  clearPairingState();
  setStatus('Проверяем пользователя и создаём wallet-session заявку...', 'info');

  const api = ensureApi();
  const user = await api.getUser(cleanLogin);
  if (user?.exists !== true) {
    throw new Error('Пользователь не найден.');
  }

  state.requesterMaterial = await createRequesterPairingMaterial();
  const passwordHash = usePassword
    ? await deriveEspPairingPasswordHash(cleanLogin, String(password || ''))
    : '';
  const payload = await api.startTrustedDeviceLogin({
    login: cleanLogin,
    passwordHash,
    requesterSessionKey: state.requesterMaterial.sessionKey,
    payloadType: 1,
  });

  state.pairingId = String(payload?.pairingId || '').trim();
  state.expiresAtMs = Number(payload?.expiresAtMs || 0);
  state.shortCode = String(payload?.shortCode || '');
  state.trustedSessionOnline = !!payload?.trustedSessionOnline;
  if (!state.pairingId) {
    throw new Error('Сервер не вернул pairingId.');
  }

  state.pollTimer = setTimeout(() => {
    void pollPairingStatus();
  }, 1800);

  setStatus('Wallet-session заявка создана. Ожидаем подтверждение на доверенном устройстве.', 'info');
  return {
    pairingId: state.pairingId,
    shortCode: String(payload?.shortCode || ''),
    expiresAtMs: state.expiresAtMs,
    trustedSessionOnline: !!payload?.trustedSessionOnline,
  };
}

async function cancelPairing() {
  if (!state.pairingId || !state.requesterMaterial?.sessionKey) {
    clearPairingState();
    return { ok: true };
  }
  await ensureApi().cancelTrustedDeviceLogin(state.pairingId, state.requesterMaterial.sessionKey);
  clearPairingState();
  setStatus('Ожидание подключения отменено.', 'info');
  return { ok: true };
}

async function disconnectSession() {
  ensureApi().close();
  state.api = null;
  await clearSessionMaterial();
  state.activeSession = null;
  state.connectionOnline = false;
  state.walletProfile = null;
  state.signing = {
    selectedDeviceName: '',
    devicesResolvedAtMs: 0,
  };
  state.currentWallet = null;
  setStatus('Сохранённая wallet-session удалена из plugin.', 'info');
  return { ok: true };
}

async function refreshWalletDevices() {
  if (!state.activeSession?.login) {
    throw new Error('Сначала подключите wallet-session.');
  }
  await hydrateWalletProfile(state.activeSession.login);
  const resumed = await resumeActiveSession({ keepConnected: true });
  if (!resumed.ok) {
    throw new Error(resumed.error || 'Не удалось открыть wallet-session.');
  }
  try {
    const sessions = await ensureApi().listSessions();
    state.walletProfile = {
      ...state.walletProfile,
      homeserverSessions: mergeHomeserverStatuses(state.walletProfile?.homeserverSessions, sessions),
    };
    state.signing.devicesResolvedAtMs = Date.now();
    if (!state.signing.selectedDeviceName && state.walletProfile.homeserverSessions[0]?.sessionName) {
      state.signing.selectedDeviceName = state.walletProfile.homeserverSessions[0].sessionName;
    }
    await saveActiveSessionRecord();
    setStatus('Список доверенных homeserver-устройств обновлён.', 'info');
    return {
      ok: true,
      devices: state.walletProfile.homeserverSessions,
    };
  } finally {
    ensureApi().close();
    state.api = null;
    state.connectionOnline = false;
  }
}

async function updateSigningSelection({ selectedDeviceName } = {}) {
  state.signing = {
    ...state.signing,
    selectedDeviceName: String(selectedDeviceName || state.signing.selectedDeviceName || ''),
  };
  await saveActiveSessionRecord();
  return { ok: true };
}

async function resolveSelectedHomeserverSession() {
  if (!state.activeSession?.login) {
    throw new Error('Сначала подключите wallet-session.');
  }
  if (!state.signing.selectedDeviceName) {
    throw new Error('Не выбрано устройство homeserver.');
  }
  let selectedDevice = (state.walletProfile?.homeserverSessions || []).find((item) => item.sessionName === state.signing.selectedDeviceName);
  if (!selectedDevice) {
    throw new Error('Выбранное устройство не найдено в PDA аккаунта.');
  }
  if (!selectedDevice.activeSessionId) {
    await refreshWalletDevices();
    selectedDevice = (state.walletProfile?.homeserverSessions || []).find((item) => item.sessionName === state.signing.selectedDeviceName);
  }
  if (!selectedDevice?.activeSessionId) {
    throw new Error('Выбранный homeserver сейчас не найден онлайн на сервере SHiNE.');
  }
  return selectedDevice;
}

async function callWalletRpc(requestData, timeoutMs = 8000, abortSignal = null) {
  const selectedDevice = await resolveSelectedHomeserverSession();
  const resumed = await resumeActiveSession({ keepConnected: true });
  if (!resumed.ok) {
    throw new Error(resumed.error || 'Не удалось открыть wallet-session.');
  }

  const requestId = String(requestData?.requestId || `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`);
  const callId = `wallet-rpc-${requestId}`;
  const payload = {
    ...requestData,
    requestId,
    timeMs: Number(requestData?.timeMs || Date.now()),
  };

  try {
    const response = await new Promise((resolve, reject) => {
      let settled = false;
      let timeoutId = 0;
      let off = () => {};
      let removeAbortListener = () => {};
      const cleanup = () => {
        if (settled) return;
        settled = true;
        if (timeoutId) clearTimeout(timeoutId);
        off();
        removeAbortListener();
      };
      timeoutId = setTimeout(() => {
        cleanup();
        reject(new Error('Таймаут ответа от ESP32.'));
      }, timeoutMs);
      off = ensureApi().onEvent('IncomingCallSignal', (evt) => {
        const eventPayload = evt?.payload || {};
        if (String(eventPayload?.callId || '') !== callId) return;
        if (Number(eventPayload?.type || 0) !== WALLET_RPC_RESPONSE_TYPE) return;
        cleanup();
        try {
          resolve(JSON.parse(String(eventPayload?.data || '{}')));
        } catch {
          reject(new Error('ESP32 вернул некорректный JSON.'));
        }
      });
      if (abortSignal) {
        const onAbort = () => {
          cleanup();
          reject(abortSignal.reason instanceof Error ? abortSignal.reason : new Error('Ожидание подписи отменено.'));
        };
        if (abortSignal.aborted) {
          onAbort();
          return;
        }
        abortSignal.addEventListener('abort', onAbort, { once: true });
        removeAbortListener = () => {
          abortSignal.removeEventListener('abort', onAbort);
        };
      }
      ensureApi().callSignalToSession({
        toLogin: state.activeSession.login,
        targetSessionId: selectedDevice.activeSessionId,
        callId,
        type: WALLET_RPC_REQUEST_TYPE,
        data: JSON.stringify(payload),
      }).catch((error) => {
        cleanup();
        reject(error);
      });
    });
    return { response, selectedDevice, requestId };
  } finally {
    ensureApi().close();
    state.api = null;
    state.connectionOnline = false;
  }
}

function verifyWalletAgainstPda(wallet) {
  const type = String(wallet?.type || '').trim();
  const pub = String(wallet?.publicKeyBase58 || '').trim();
  const rootKey = String(state.walletProfile?.publicKeys?.rootKeyBase58 || '').trim();
  const clientKey = String(
    state.walletProfile?.publicKeys?.clientKeyBase58 || '',
  ).trim();
  if (type === 'client.key') {
    return {
      verified: !!clientKey && clientKey === pub,
      verificationText: clientKey === pub ? 'Совпадает с clientKey из PDA.' : 'Не совпадает с clientKey из PDA.',
    };
  }
  if (type === 'root.key') {
    return {
      verified: !!rootKey && rootKey === pub,
      verificationText: rootKey === pub ? 'Совпадает с rootKey из PDA.' : 'Не совпадает с rootKey из PDA.',
    };
  }
  return {
    verified: null,
    verificationText: 'Для custom-кошелька проверка через PDA пока не выполняется.',
  };
}

async function requestCurrentWallet() {
  const { response, selectedDevice, requestId } = await callWalletRpc({
    v: 1,
    operation: 'get_wallet_public_key',
    requestId: `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`,
  });
  if (!response?.ok) {
    throw new Error(`ESP32 отклонил запрос: ${String(response?.error || 'unknown_error')}`);
  }
  state.currentWallet = {
    type: String(response?.wallet?.type || '').trim(),
    publicKeyBase58: String(response?.wallet?.publicKeyBase58 || '').trim(),
    homeserverName: selectedDevice.sessionName,
    requestId: String(response?.requestId || requestId),
    timeMs: Number(response?.timeMs || 0),
    ...verifyWalletAgainstPda(response?.wallet || {}),
  };
  await saveActiveSessionRecord();
  setStatus(`Кошелёк получен с ${selectedDevice.sessionName}.`, 'info');
  return { ok: true, wallet: state.currentWallet };
}

async function cancelPendingSiteApproval() {
  const pending = getCurrentPendingApproval();
  if (!pending) {
    setStatus('Сейчас нет активного ожидания подписи.', 'info');
    return { ok: true };
  }
  removePendingApproval(pending.id, {
    rejectError: makeCodeError('User canceled request in extension.', 'USER_REJECTED'),
  });
  setStatus('Ожидание подписи отменено в расширении.', 'info');
  return { ok: true };
}

async function markPendingSiteApprovalResolved(pendingId) {
  removePendingApproval(pendingId);
}

function enqueueSiteApproval(work) {
  const run = state.siteApprovalChain.then(work, work);
  state.siteApprovalChain = run.catch(() => {});
  return run;
}

async function activatePendingApproval(pending, sender = null) {
  const abortController = new AbortController();
  pending.status = 'active';
  pending.abortController = abortController;
  pending.timeoutId = setTimeout(() => {
    removePendingApproval(pending.id, {
      rejectError: makeCodeError('Signing request timed out in extension.', 'USER_REJECTED'),
    });
    setStatus('Ожидание подписи истекло в расширении.', 'error');
  }, 120000);
  setStatus(`Сайт ${pending.origin} запросил подпись. Подтвердите или отмените на доверенном устройстве.`, 'info');
  await openSidePanelForSender(sender);
  return pending;
}

function beginSiteTransactionFlow(payload = {}) {
  const pending = makePendingApprovalSnapshot({
    ...payload,
    kind: 'sign_transaction',
    id: `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`,
    createdAtMs: Date.now(),
    status: 'queued',
  });
  state.pendingApprovals.push({
    ...pending,
    timeoutId: 0,
    abortController: null,
  });
  return pending;
}

async function siteConnect({ origin, onlyIfTrusted = false } = {}) {
  const normalizedOrigin = normalizeOrigin(origin);
  if (!normalizedOrigin) {
    throw makeCodeError('Site origin is missing.', 'BAD_ORIGIN');
  }
  if (onlyIfTrusted && !isOriginApproved(normalizedOrigin)) {
    throw makeCodeError('Site is not trusted yet.', 'NOT_TRUSTED');
  }
  const result = await requestCurrentWallet();
  const publicKeyBase58 = String(result?.wallet?.publicKeyBase58 || '').trim();
  if (!publicKeyBase58) {
    throw makeCodeError('Wallet public key is not available.', 'WALLET_UNAVAILABLE');
  }
  if (!isOriginApproved(normalizedOrigin)) {
    await setOriginApproved(normalizedOrigin, true);
  }
  setStatus(`Site ${normalizedOrigin} connected to ${shortKey(publicKeyBase58, 8)}.`, 'info');
  return {
    ok: true,
    publicKeyBase58,
    walletType: String(result?.wallet?.type || '').trim(),
  };
}

async function siteDisconnect({ origin } = {}) {
  const normalizedOrigin = normalizeOrigin(origin);
  setStatus(normalizedOrigin ? `Site ${normalizedOrigin} disconnected.` : 'Site disconnected.', 'info');
  return { ok: true };
}

async function siteSignTransaction({ origin, publicKeyBase58, transactionBase64, comment, transactionSummary } = {}, sender = null) {
  const normalizedOrigin = normalizeOrigin(origin);
  if (!normalizedOrigin) {
    throw makeCodeError('Site origin is missing.', 'BAD_ORIGIN');
  }
  if (!isOriginApproved(normalizedOrigin)) {
    throw makeCodeError('Site is not trusted yet.', 'NOT_TRUSTED');
  }
  const cleanPub = String(publicKeyBase58 || '').trim();
  const cleanTx = String(transactionBase64 || '').trim();
  if (!cleanPub || !cleanTx) {
    throw makeCodeError('Transaction payload is incomplete.', 'BAD_REQUEST');
  }
  const pending = beginSiteTransactionFlow({
    origin: normalizedOrigin,
    publicKeyBase58: cleanPub,
    comment: String(comment || '').trim(),
    transactionSummary: transactionSummary || null,
  });
  return enqueueSiteApproval(async () => {
    await activatePendingApproval(getCurrentPendingApproval() || pending, sender);
    const activePending = getCurrentPendingApproval() || pending;
    const requestId = `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
    const signComment = String(comment || '').trim() || `Site ${normalizedOrigin} requested transaction signature`;
    try {
      const { response } = await callWalletRpc({
        v: 1,
        operation: 'sign_transaction',
        requestId,
        publicKeyBase58: cleanPub,
        transactionBase64: cleanTx,
        comment: signComment,
      }, 120000, activePending.abortController?.signal || null);
      if (!response?.ok) {
        const errorCode = String(response?.error || 'unknown_error').trim().toUpperCase();
        if (errorCode === 'REJECTED_BY_USER') {
          throw makeCodeError('User rejected transaction signature on ESP32.', 'USER_REJECTED');
        }
        throw makeCodeError(`ESP32 rejected transaction signature: ${String(response?.error || 'unknown_error')}`, errorCode || 'RPC_REJECTED');
      }
      setStatus(`Подпись для ${normalizedOrigin} завершена.`, 'info');
      return {
        ok: true,
        publicKeyBase58: String(response?.publicKeyBase58 || cleanPub).trim(),
        signedTransactionBase64: String(response?.signedTransactionBase64 || '').trim(),
        signatureBase58: String(response?.signatureBase58 || '').trim(),
      };
    } finally {
      await markPendingSiteApprovalResolved(activePending.id);
    }
  });
}

function snapshot() {
  return {
    settings: { ...state.settings },
    pairing: {
      active: !!state.pairingId,
      pairingId: state.pairingId,
      expiresAtMs: state.expiresAtMs,
      shortCode: state.shortCode,
      trustedSessionOnline: state.trustedSessionOnline,
    },
    session: state.activeSession ? { ...state.activeSession } : null,
    connectionOnline: !!state.activeSession,
    walletProfile: state.walletProfile ? { ...state.walletProfile } : null,
    currentWallet: state.currentWallet ? { ...state.currentWallet } : null,
    pendingApproval: getCurrentPendingApproval() ? makePendingApprovalSnapshot(getCurrentPendingApproval()) : null,
    signing: { ...state.signing },
    status: {
      text: state.statusText,
      kind: state.statusKind,
    },
  };
}

chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
  (async () => {
    const type = String(message?.type || '');
    if (type === 'wallet:getState') {
      await loadStateFromStorage();
      sendResponse({ ok: true, state: snapshot() });
      return;
    }
    if (type === 'wallet:saveSettings') {
      await persistSettings(message?.payload || {});
      sendResponse({ ok: true, state: snapshot() });
      return;
    }
    if (type === 'wallet:resolveServerInfo') {
      const result = await resolveServerForLogin(String(message?.payload?.login || '').trim());
      sendResponse({ ok: true, result, state: snapshot() });
      return;
    }
    if (type === 'wallet:startPairing') {
      const result = await startPairing(message?.payload || {});
      sendResponse({ ok: true, result, state: snapshot() });
      return;
    }
    if (type === 'wallet:cancelPairing') {
      const result = await cancelPairing();
      sendResponse({ ok: true, result, state: snapshot() });
      return;
    }
    if (type === 'wallet:resumeSession') {
      const result = await resumeActiveSession();
      sendResponse({ ok: true, result, state: snapshot() });
      return;
    }
    if (type === 'wallet:refreshWalletDevices') {
      const result = await refreshWalletDevices();
      sendResponse({ ok: true, result, state: snapshot() });
      return;
    }
    if (type === 'wallet:updateSigningSelection') {
      const result = await updateSigningSelection(message?.payload || {});
      sendResponse({ ok: true, result, state: snapshot() });
      return;
    }
    if (type === 'wallet:requestCurrentWallet') {
      const result = await requestCurrentWallet();
      sendResponse({ ok: true, result, state: snapshot() });
      return;
    }
    if (type === 'wallet:disconnectSession') {
      const result = await disconnectSession();
      sendResponse({ ok: true, result, state: snapshot() });
      return;
    }
    if (type === 'wallet:cancelPendingSiteApproval') {
      const result = await cancelPendingSiteApproval();
      sendResponse({ ok: true, result, state: snapshot() });
      return;
    }
    if (type === 'wallet:siteConnect') {
      const result = await siteConnect(message?.payload || {});
      sendResponse({ ok: true, result, state: snapshot() });
      return;
    }
    if (type === 'wallet:siteDisconnect') {
      const result = await siteDisconnect(message?.payload || {});
      sendResponse({ ok: true, result, state: snapshot() });
      return;
    }
    if (type === 'wallet:siteSignTransaction') {
      const result = await siteSignTransaction(message?.payload || {}, _sender);
      sendResponse({ ok: true, result, state: snapshot() });
      return;
    }
    sendResponse({ ok: false, error: 'UNKNOWN_MESSAGE' });
  })().catch((error) => {
    const message = toWalletErrorMessage(error, 'Unknown error');
    setStatus(message, 'error');
    sendResponse({ ok: false, error: message, code: String(error?.code || ''), state: snapshot() });
  });
  return true;
});

chrome.runtime.onInstalled.addListener(() => {
  void configureSidePanelBehavior();
});

chrome.runtime.onStartup.addListener(() => {
  void configureSidePanelBehavior();
});

void configureSidePanelBehavior();

void loadStateFromStorage().then(async () => {
  if (state.activeSession?.login) {
    await hydrateWalletProfile(state.activeSession.login).catch(() => {});
    setStatus(`Wallet-session сохранена для @${state.activeSession.login}. Подключение будет открываться только по действию.`, 'info');
  }
}).catch((error) => {
  setStatus(error?.message || 'Не удалось инициализировать wallet plugin.', 'error');
});
