import { channels as mockChannels } from '../mock-data.js';
import { authService, setChannelsFeed, state } from '../state.js';
import { toUserMessage } from '../services/ui-error-texts.js';
import {
  animatePress,
  createSkeletonCard,
  formatRelativeTime,
  readChannelNotificationsState,
  showToast,
  softHaptic,
  writeChannelNotificationsState,
} from '../services/channels-ux.js';
import { makeShineChannelRoute } from '../services/shine-routes.js';
import { navigateBack } from '../router.js';

export const pageMeta = { id: 'channels-list', title: 'Каналы' };

const CREATE_CHANNEL_FLASH_KEY = 'shine-channels-create-success';
const MENU_OVERLAY_ID = 'channels-context-menu-overlay';
const CHANNEL_TYPE_STORIES = 0;
const CHANNEL_TYPE_PERSONAL = 100;
const TAB_ORDER = ['feed', 'my'];

function isChannelsDemoMode() {
  try {
    const qs = new URLSearchParams(window.location.search);
    if (qs.get('channelsDemo') === '1') return true;
    return localStorage.getItem('shine-channels-demo') === '1';
  } catch {
    return false;
  }
}

function normalizeHash(hash) {
  const normalized = String(hash || '').trim().toLowerCase();
  return normalized || '0';
}

function encodeRoutePart(value = '') {
  return encodeURIComponent(String(value));
}

function normalizeLoginInput(value) {
  return String(value || '').trim().replace(/^@+/, '');
}

function buildChannelRouteFromSummary(summary, fallbackId) {
  const ownerBch = String(summary?.channel?.ownerBlockchainName || '').trim();
  const ownerLogin = String(summary?.channel?.ownerLogin || '').trim();
  const channelName = String(summary?.channel?.channelName || '').trim();
  return makeShineChannelRoute({
    ownerLogin,
    ownerBlockchainName: ownerBch,
    channelName: channelName || fallbackId,
  });
}

function avatarLetterFromName(name = '') {
  const first = Array.from(String(name || '').trim())[0] || '#';
  return first.toUpperCase();
}

function allFeedSummaries() {
  const feed = state.channelsFeed || {};
  return [
    ...(feed.ownedChannels || []),
    ...(feed.followedUsersChannels || []),
    ...(feed.followedChannels || []),
  ];
}

function uniqueBy(items, keySelector) {
  const seen = new Set();
  const out = [];
  for (const item of items) {
    const key = keySelector(item);
    if (!key || seen.has(key)) continue;
    seen.add(key);
    out.push(item);
  }
  return out;
}

function createDebounced(fn, delayMs = 250) {
  let timer = null;
  return (...args) => {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => {
      timer = null;
      fn(...args);
    }, delayMs);
  };
}

async function resolveChannelTargetFromInput(rawInput) {
  const input = String(rawInput || '').trim();
  if (!input) throw new Error('Введите канал.');

  const byOwnerAndName = input.match(/^@?([^/#\s]+)\s*\/\s*#?(.+)$/);
  if (byOwnerAndName) {
    const ownerLogin = normalizeLoginInput(byOwnerAndName[1]);
    const channelName = String(byOwnerAndName[2] || '').trim().toLowerCase();
    if (!ownerLogin || !channelName) {
      throw new Error('Укажите канал в формате user/channel.');
    }

    const user = await authService.getUser(ownerLogin);
    if (!user?.exists || !user?.blockchainName) {
      throw new Error('Пользователь не найден.');
    }

    const ownerFeed = await authService.listSubscriptionsFeed(ownerLogin, 500);
    const ownChannels = Array.isArray(ownerFeed?.ownedChannels) ? ownerFeed.ownedChannels : [];
    const matches = ownChannels.filter((item) => (
      String(item?.channel?.channelName || '').trim().toLowerCase() === channelName
    ));

    if (!matches.length) {
      throw new Error('Канал не найден у указанного автора.');
    }

    const primaryMatches = matches.filter((item) => (
      String(item?.channel?.ownerBlockchainName || '') === String(user.blockchainName || '')
    ));
    const pool = primaryMatches.length ? primaryMatches : matches;
    const match = [...pool].sort((a, b) => (
      Number(b?.channel?.channelRoot?.blockNumber || -1) - Number(a?.channel?.channelRoot?.blockNumber || -1)
    ))[0];

    return {
      ownerBlockchainName: String(match?.channel?.ownerBlockchainName || user.blockchainName),
      rootBlockNumber: Number(match?.channel?.channelRoot?.blockNumber),
      rootBlockHash: normalizeHash(match?.channel?.channelRoot?.blockHash),
    };
  }

  const byNameOnly = input.replace(/^#/, '').trim().toLowerCase();
  const summaries = allFeedSummaries();
  const matches = summaries.filter((summary) => (
    String(summary?.channel?.channelName || '').trim().toLowerCase() === byNameOnly
  ));

  if (!matches.length) {
    throw new Error('Канал не найден. Укажите user/channel или выберите из списка.');
  }
  if (matches.length > 1) {
    throw new Error('Найдено несколько каналов с таким именем. Уточните в формате user/channel.');
  }

  const one = matches[0];
  return {
    ownerBlockchainName: String(one?.channel?.ownerBlockchainName || ''),
    rootBlockNumber: Number(one?.channel?.channelRoot?.blockNumber),
    rootBlockHash: normalizeHash(one?.channel?.channelRoot?.blockHash),
  };
}

function channelSuggestionsByInput(rawInput) {
  const q = String(rawInput || '').trim().toLowerCase();
  if (q.length < 1) return [];

  const rows = allFeedSummaries().map((summary) => {
    const owner = String(summary?.channel?.ownerLogin || '').trim();
    const channel = String(summary?.channel?.channelName || '').trim();
    if (!owner || !channel) return null;
    return {
      key: `${owner.toLowerCase()}/${channel.toLowerCase()}`,
      label: `@${owner}/${channel}`,
      owner,
      channel,
    };
  }).filter(Boolean);

  return uniqueBy(rows, (it) => it.key)
    .filter((it) => (
      it.channel.toLowerCase().includes(q) ||
      `${it.owner.toLowerCase()}/${it.channel.toLowerCase()}`.includes(q) ||
      `@${it.owner.toLowerCase()}/${it.channel.toLowerCase()}`.includes(q)
    ))
    .slice(0, 7)
    .map((it) => it.label);
}

function renderSuggestions(container, values, onPick) {
  container.innerHTML = '';
  if (!values.length) {
    container.style.display = 'none';
    return;
  }

  container.style.display = '';
  values.forEach((value) => {
    const btn = document.createElement('button');
    btn.type = 'button';
    btn.className = 'channel-search-item';
    btn.textContent = value;
    btn.addEventListener('click', () => onPick(value));
    container.append(btn);
  });
}

function normalizeComparableLogin(value) {
  return normalizeLoginInput(value).toLowerCase();
}

function isFollowedUserVisible(targetLogin) {
  const expected = normalizeComparableLogin(targetLogin);
  if (!expected) return false;
  const rows = Array.isArray(state.channelsFeed?.followedUsersChannels)
    ? state.channelsFeed.followedUsersChannels
    : [];
  return rows.some((row) => normalizeComparableLogin(row?.channel?.ownerLogin) === expected);
}

function isFollowedChannelVisible(target) {
  const rows = Array.isArray(state.channelsFeed?.followedChannels)
    ? state.channelsFeed.followedChannels
    : [];
  const expectedBch = String(target?.ownerBlockchainName || '');
  const expectedNo = Number(target?.rootBlockNumber);
  const expectedHash = normalizeHash(target?.rootBlockHash);
  if (!expectedBch || !Number.isFinite(expectedNo)) return false;

  return rows.some((row) => {
    const rowBch = String(row?.channel?.ownerBlockchainName || '');
    const rowNo = Number(row?.channel?.channelRoot?.blockNumber);
    const rowHash = normalizeHash(row?.channel?.channelRoot?.blockHash);
    return rowBch === expectedBch && rowNo === expectedNo && rowHash === expectedHash;
  });
}

function openSimpleSubscribeModal({ kind, kindLabel, submitLabel, unfollow = false, onSuccess }) {
  const targetHint = kind === 'channel'
    ? '<p class="meta-muted">Канал: user/channel или имя канала.</p>'
    : '<p class="meta-muted">Автор: @login или login.</p>';
  const submitText = submitLabel || (unfollow ? 'Отписаться' : 'Подписаться');
  const placeholder = kind === 'channel' ? '@owner/channel' : '@login';

  const root = document.getElementById('modal-root');
  root.innerHTML = `
    <div class="modal" id="channels-subscribe-modal">
      <div class="modal-card stack">
        <h3 class="modal-title">${kindLabel}</h3>
        ${targetHint}
        <label class="meta-muted" for="subscribe-input">Поиск</label>
        <input id="subscribe-input" class="input" placeholder="${placeholder}" autocomplete="off" />
        <div id="subscribe-suggest" class="channels-search-suggest" style="display:none"></div>
        <div id="subscribe-error" class="meta-muted inline-error"></div>
        <div class="form-actions-grid">
          <button class="secondary-btn" id="sub-cancel" type="button">Отмена</button>
          <button class="primary-btn" id="sub-submit" type="button">${submitText}</button>
        </div>
      </div>
    </div>
  `;

  const inputEl = root.querySelector('#subscribe-input');
  const suggestEl = root.querySelector('#subscribe-suggest');
  const errorEl = root.querySelector('#subscribe-error');
  const submitEl = root.querySelector('#sub-submit');

  let inFlight = false;

  const setBusy = (busy) => {
    inFlight = !!busy;
    submitEl.disabled = inFlight;
    if (inputEl) inputEl.disabled = inFlight;
    submitEl.textContent = inFlight ? 'Выполняем...' : submitText;
  };

  const close = () => {
    root.innerHTML = '';
  };

  const applySuggestion = (value) => {
    if (!inputEl) return;
    inputEl.value = value;
    if (suggestEl) suggestEl.style.display = 'none';
    inputEl.focus();
  };

  const refreshSuggestions = createDebounced(async () => {
    if (!inputEl || !suggestEl || inFlight) return;

    const raw = String(inputEl.value || '').trim();
    if (!raw) {
      suggestEl.style.display = 'none';
      suggestEl.innerHTML = '';
      return;
    }

    try {
      if (kind === 'user') {
        const prefix = normalizeLoginInput(raw);
        if (prefix.length < 2) {
          suggestEl.style.display = 'none';
          suggestEl.innerHTML = '';
          return;
        }
        const logins = await authService.searchUsers(prefix);
        const suggestions = (Array.isArray(logins) ? logins : []).slice(0, 8).map((login) => `@${login}`);
        renderSuggestions(suggestEl, suggestions, applySuggestion);
        return;
      }

      const suggestions = channelSuggestionsByInput(raw);
      renderSuggestions(suggestEl, suggestions, applySuggestion);
    } catch {
      suggestEl.style.display = 'none';
      suggestEl.innerHTML = '';
    }
  }, 240);

  root.querySelector('#sub-cancel')?.addEventListener('click', close);

  inputEl?.addEventListener('input', () => {
    if (!errorEl) return;
    errorEl.textContent = '';
    refreshSuggestions();
  });

  submitEl?.addEventListener('click', async () => {
    if (inFlight) return;

    const login = state.session.login;
    const storagePwd = state.session.storagePwdInMemory;
    const value = String(inputEl?.value || '').trim();

    if (!login || !storagePwd) {
      errorEl.textContent = 'Сессия недействительна. Выполните вход заново.';
      return;
    }

    if (!value) {
      errorEl.textContent = 'Введите идентификатор.';
      return;
    }

    setBusy(true);
    errorEl.textContent = '';

    try {
      let channelTarget = null;
      let userTargetLogin = '';

      if (kind === 'user') {
        userTargetLogin = normalizeLoginInput(value);
        await authService.addBlockFollowUser({
          login,
          targetLogin: userTargetLogin,
          storagePwd,
          unfollow,
        });
      } else if (kind === 'channel') {
        channelTarget = await resolveChannelTargetFromInput(value);
        if (!channelTarget?.ownerBlockchainName || !Number.isFinite(channelTarget.rootBlockNumber)) {
          throw new Error('Канал не найден.');
        }

        await authService.addBlockFollowChannel({
          login,
          storagePwd,
          targetBlockchainName: channelTarget.ownerBlockchainName,
          targetBlockNumber: channelTarget.rootBlockNumber,
          targetBlockHashHex: channelTarget.rootBlockHash,
          unfollow,
        });
      } else {
        throw new Error('Неподдерживаемый тип подписки');
      }

      if (typeof onSuccess === 'function') {
        await onSuccess();
      }

      if (kind === 'user') {
        const visible = isFollowedUserVisible(userTargetLogin);
        if (!unfollow && !visible) {
          throw new Error('Подписка не подтвердилась после обновления списка.');
        }
        if (unfollow && visible) {
          throw new Error('Отписка не подтвердилась после обновления списка.');
        }
      }

      if (kind === 'channel') {
        const visible = isFollowedChannelVisible(channelTarget);
        if (!unfollow && !visible) {
          throw new Error('Подписка на канал не подтвердилась после обновления списка.');
        }
        if (unfollow && visible) {
          throw new Error('Отписка от канала не подтвердилась после обновления списка.');
        }
      }

      softHaptic(15);
      showToast(unfollow ? 'Отписка выполнена' : 'Подписка выполнена');
      close();
    } catch (error) {
      errorEl.textContent = toUserMessage(error, `${submitText} не удалось.`);
      setBusy(false);
    }
  });

  if (inputEl) inputEl.focus();
}

function openChannelFinderModal({ navigate }) {
  const root = document.getElementById('modal-root');
  root.innerHTML = `
    <div class="modal" id="channels-find-modal">
      <div class="modal-card stack" style="max-width: min(96vw, 760px); width: min(96vw, 760px);">
        <h3 class="modal-title">Поиск каналов</h3>
        <p class="meta-muted">Введите логин (или начало логина), затем выберите пользователя и канал.</p>
        <input id="channels-find-input" class="input" placeholder="Например: aidar" autocomplete="off" />
        <div id="channels-find-suggest" class="channels-search-suggest" style="display:none"></div>
        <div id="channels-find-list" class="channels-search-suggest" style="display:none"></div>
        <div id="channels-find-error" class="meta-muted inline-error"></div>
        <div class="form-actions-grid">
          <button class="primary-btn" id="channels-find-run" type="button">Найти</button>
          <button class="secondary-btn" id="channels-find-close" type="button">Закрыть</button>
        </div>
      </div>
    </div>
  `;

  const inputEl = root.querySelector('#channels-find-input');
  const suggestEl = root.querySelector('#channels-find-suggest');
  const channelsEl = root.querySelector('#channels-find-list');
  const errorEl = root.querySelector('#channels-find-error');
  const close = () => { root.innerHTML = ''; };

  const renderButtons = (container, values, onPick) => {
    container.innerHTML = '';
    if (!values.length) {
      container.style.display = 'none';
      return;
    }
    container.style.display = '';
    values.forEach((value) => {
      const btn = document.createElement('button');
      btn.type = 'button';
      btn.className = 'channel-search-item';
      btn.textContent = value.label;
      btn.addEventListener('click', () => onPick(value));
      container.append(btn);
    });
  };

  const renderChannelRows = (values) => {
    channelsEl.innerHTML = '';
    if (!values.length) {
      channelsEl.style.display = 'none';
      return;
    }
    channelsEl.style.display = '';
    values.forEach((item) => {
      const row = document.createElement('div');
      row.className = 'channel-search-item';

      const label = document.createElement('span');
      label.textContent = item.label;

      const openBtn = document.createElement('button');
      openBtn.type = 'button';
      openBtn.className = 'secondary-btn small-btn';
      openBtn.textContent = 'Просмотреть';
      openBtn.addEventListener('click', () => {
        close();
        const route = makeShineChannelRoute({
          ownerLogin: String(item.ownerLogin || '').trim(),
          ownerBlockchainName: String(item.ownerBlockchainName || '').trim(),
          channelName: String(item.channelName || '').trim(),
        });
        if (route) navigate(route);
      });

      row.style.display = 'flex';
      row.style.alignItems = 'center';
      row.style.justifyContent = 'space-between';
      row.style.gap = '8px';
      row.append(label, openBtn);
      channelsEl.append(row);
    });
  };

  const loadChannelsForLogin = async (login, filterChannel = '') => {
    const ownerLogin = normalizeLoginInput(login);
    if (!ownerLogin) return;
    const feed = await authService.listSubscriptionsFeed(ownerLogin, 1000);
    const rows = Array.isArray(feed?.ownedChannels) ? feed.ownedChannels : [];
    const needle = String(filterChannel || '').trim().toLowerCase();
    const channels = rows
      .map((item) => ({
        ownerBlockchainName: String(item?.channel?.ownerBlockchainName || '').trim(),
        channelName: String(item?.channel?.channelName || '').trim(),
      }))
      .filter((item) => !!item.channelName)
      .filter((item) => !needle || item.channelName.toLowerCase().includes(needle))
      .slice(0, 200)
      .map((item) => ({
        label: `${ownerLogin}/${item.channelName}`,
        ownerLogin,
        ownerBlockchainName: item.ownerBlockchainName,
        channelName: item.channelName,
      }));
    renderChannelRows(channels);
    if (!channels.length) {
      errorEl.textContent = filterChannel
        ? 'Каналы с таким фильтром не найдены.'
        : `У пользователя "${ownerLogin}" пока нет доступных каналов.`;
    } else {
      errorEl.textContent = '';
    }
  };

  const runSearch = async () => {
    const raw = String(inputEl?.value || '').trim();
    errorEl.textContent = '';
    if (!raw) {
      suggestEl.style.display = 'none';
      suggestEl.innerHTML = '';
      channelsEl.style.display = 'none';
      channelsEl.innerHTML = '';
      errorEl.textContent = 'Введите логин или начало логина.';
      return;
    }

    const parts = raw.split('/');
    const loginPrefix = normalizeLoginInput(parts[0] || '');
    const channelFilter = String(parts[1] || '').trim();

    try {
      if (raw.includes('/')) {
        suggestEl.style.display = 'none';
        suggestEl.innerHTML = '';
        await loadChannelsForLogin(loginPrefix, channelFilter);
        return;
      }

      if (loginPrefix.length < 1) {
        suggestEl.style.display = 'none';
        suggestEl.innerHTML = '';
        channelsEl.style.display = 'none';
        channelsEl.innerHTML = '';
        return;
      }

      const logins = await authService.searchUsers(loginPrefix);
      const rows = Array.isArray(logins) ? logins : [];
      if (!rows.length) {
        suggestEl.style.display = 'none';
        suggestEl.innerHTML = '';
        channelsEl.style.display = 'none';
        channelsEl.innerHTML = '';
        errorEl.textContent = `Логины, начинающиеся на "${loginPrefix}", не найдены.`;
        return;
      }
      const items = (Array.isArray(logins) ? logins : []).slice(0, 15).map((login) => ({
        label: login,
        login,
      }));
      errorEl.textContent = '';
      renderButtons(suggestEl, items, async (item) => {
        inputEl.value = `${item.login}/`;
        suggestEl.style.display = 'none';
        suggestEl.innerHTML = '';
        await loadChannelsForLogin(item.login, '');
      });
    } catch (error) {
      errorEl.textContent = toUserMessage(error, 'Не удалось выполнить поиск.');
    }
  };

  const refresh = createDebounced(runSearch, 220);

  root.querySelector('#channels-find-close')?.addEventListener('click', close);
  root.querySelector('#channels-find-run')?.addEventListener('click', () => {
    void runSearch();
  });
  inputEl?.addEventListener('keydown', (event) => {
    if (event.key !== 'Enter') return;
    event.preventDefault();
    void runSearch();
  });
  inputEl?.addEventListener('input', refresh);
  if (inputEl) inputEl.focus();
}

function mapMockGroups() {
  const mapRow = (channel) => ({
    ...channel,
    route: makeShineChannelRoute({
      ownerLogin: String(channel.ownerName || 'channel'),
      ownerBlockchainName: String(channel.ownerName || ''),
      channelName: String(channel.channelName || channel.title || channel.id),
    }),
    tabCategory: channel.kind === 'own' || channel.kind === 'own-personal'
      ? 'my'
      : 'feed',
    messagePreview: channel.lastMessage || 'Ждем ваших начинаний',
    isSubscribed: channel.kind !== 'own' && channel.kind !== 'own-personal',
    isOwnChannel: channel.kind === 'own' || channel.kind === 'own-personal',
    notificationsEnabled: false,
    messagesCount: Number(channel.messagesCount || 0),
    unreadCount: 0,
    lastMessageAt: 0,
    ownerName: String(channel.ownerName || 'неизвестно'),
    channelName: String(channel.channelName || channel.title || ''),
    title: String(channel.title || channel.channelName || ''),
  });

  const ownChannels = mockChannels
    .filter((channel) => channel.kind === 'own-personal' || channel.kind === 'own')
    .map((item) => ({ ...mapRow(item), tabCategory: 'my' }));

  const followedUserChannels = mockChannels
    .filter((channel) => channel.kind === 'followed-user-channel')
    .map((item) => ({ ...mapRow(item), tabCategory: 'feed' }));

  const subscribedChannels = mockChannels
    .filter((channel) => channel.kind === 'subscribed')
    .map((item) => ({ ...mapRow(item), tabCategory: 'feed' }));

  return { ownChannels, followedUserChannels, subscribedChannels, index: {} };
}

function mapApiChannelRow(summary, bucketKey, idx, index, notificationsState) {
  const rowId = `${bucketKey}-${idx}`;
  index[rowId] = summary;

  const ownerLogin = summary?.channel?.ownerLogin || 'неизвестно';
  const channelName = summary?.channel?.channelName || '(без названия)';
  const channelDescription = String(summary?.channel?.channelDescription || '').trim();
  const channelTypeCode = Number(summary?.channel?.channelTypeCode ?? 1);
  const channelTypeVersion = Number(summary?.channel?.channelTypeVersion ?? 1);
  const isOwn = bucketKey === 'own';
  const tabCategory = isOwn ? 'my' : 'feed';

  const title = isOwn ? channelName : `${ownerLogin}/${channelName}`;

  return {
    id: rowId,
    route: buildChannelRouteFromSummary(summary, rowId),
    ownerName: ownerLogin,
    ownerBlockchainName: summary?.channel?.ownerBlockchainName || '',
    channelRootBlockNumber: Number(summary?.channel?.channelRoot?.blockNumber),
    channelRootBlockHash: normalizeHash(summary?.channel?.channelRoot?.blockHash),
    avatar: avatarLetterFromName(channelName),
    title,
    channelName,
    channelDescription,
    channelTypeCode,
    channelTypeVersion,
    messagePreview: summary?.lastMessage?.text || 'Ждем ваших начинаний',
    messagesCount: Number(summary?.messagesCount || 0),
    unreadCount: Number(summary?.unreadCount || 0),
    lastMessageAt: Number(summary?.lastMessage?.createdAtMs || 0),
    tabCategory,
    isOwnChannel: isOwn,
    isSubscribed: !isOwn,
    notificationsEnabled: notificationsState[rowId] === true,
    pending: false,
  };
}

function pullCreateSuccessFlash() {
  try {
    const value = String(sessionStorage.getItem(CREATE_CHANNEL_FLASH_KEY) || '').trim();
    if (value) sessionStorage.removeItem(CREATE_CHANNEL_FLASH_KEY);
    return value;
  } catch {
    return '';
  }
}

function mapApiFeed(feed, notificationsState) {
  const index = {};
  const ownChannels = (feed?.ownedChannels || [])
    .map((it, idx) => mapApiChannelRow(it, 'own', idx, index, notificationsState));
  const followedUserChannels = (feed?.followedUsersChannels || []).map((it, idx) => mapApiChannelRow(it, 'followedUsers', idx, index, notificationsState));
  const subscribedChannels = (feed?.followedChannels || []).map((it, idx) => mapApiChannelRow(it, 'followedChannels', idx, index, notificationsState));

  return { ownChannels, followedUserChannels, subscribedChannels, index };
}

function toListModel(groups) {
  return [
    ...(groups.ownChannels || []),
    ...(groups.followedUserChannels || []),
    ...(groups.subscribedChannels || []),
  ];
}

function renderEmptyState(activeTab, navigate) {
  const wrap = document.createElement('div');
  wrap.className = 'channels-empty-state channels-empty-state--compact channels-empty-state--silent';
  if (!state.session.isAuthorized) {
    return wrap;
  }
  const text = document.createElement('p');
  text.className = 'meta-muted';
  if (activeTab === 'feed') {
    text.textContent = 'Нет подписок и найденных каналов.';
  } else if (activeTab === 'my') {
    text.textContent = 'У вас пока нет каналов.';
  } else {
    text.textContent = 'Пусто.';
  }
  wrap.append(text);

  return wrap;
}

function renderSkeletonList(container, count = 4) {
  container.innerHTML = '';
  const list = document.createElement('div');
  list.className = 'stack';
  for (let i = 0; i < count; i += 1) {
    list.append(createSkeletonCard());
  }
  container.append(list);
}

function renderErrorState(container, error, onRetry) {
  const errCard = document.createElement('div');
  errCard.className = 'card stack channels-status';

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

  const details = document.createElement('p');
  details.className = 'meta-muted';
  details.textContent = toUserMessage(error, 'Проверьте подключение к серверу и повторите попытку.');

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

  errCard.append(title, details, retry);
  container.append(errCard);
}

function renderDemoFallback(container, navigate, error, onRetry) {
  const info = document.createElement('div');
  info.className = 'card stack';
  info.innerHTML = `
    <strong>Включен демо-режим</strong>
    <p class="meta-muted">Данные сервера недоступны. Показаны мок-каналы, потому что включен channelsDemo.</p>
    <p class="meta-muted">${toUserMessage(error, 'Ошибка API/WS')}</p>
  `;

  const retry = document.createElement('button');
  retry.className = 'secondary-btn';
  retry.type = 'button';
  retry.textContent = 'Повторить запрос к серверу';
  retry.addEventListener('click', onRetry);

  info.append(retry);
  container.append(info);

  const groups = mapMockGroups();
  const list = document.createElement('div');
  list.className = 'stack';

  toListModel(groups).forEach((channel) => {
    const row = document.createElement('article');
    row.className = 'channel-row';
    row.innerHTML = `
      <div class="avatar">${channel.avatar || channel.initials || '#'}</div>
      <div class="channel-row-main">
        <strong class="channel-row-title">${channel.title || channel.displayName || channel.name}</strong>
        <p class="channel-row-message">${channel.messagePreview || 'Ждем ваших начинаний'}</p>
      </div>
      <div class="channel-row-controls">
        <span class="channel-row-time">—</span>
      </div>
    `;
    row.addEventListener('click', () => {
      const route = channel.route || makeShineChannelRoute({
      ownerLogin: String(channel.ownerName || 'channel'),
      ownerBlockchainName: String(channel.ownerName || ''),
      channelName: String(channel.channelName || channel.id),
      });
      if (route) navigate(route);
    });
    list.append(row);
  });

  container.append(list);
}

function closeChannelMenu(listState, clearOpenMenuId = true) {
  if (typeof listState.menuCleanup === 'function') {
    listState.menuCleanup();
  }
  listState.menuCleanup = null;
  const root = document.getElementById('modal-root');
  if (root) {
    const overlay = root.querySelector(`#${MENU_OVERLAY_ID}`);
    if (overlay) overlay.remove();
  }
  if (clearOpenMenuId) {
    listState.openMenuId = null;
  }
}

function openChannelMenu({ listState, channel, anchorEl, refreshFeed, rerenderList }) {
  closeChannelMenu(listState, false);

  const root = document.getElementById('modal-root');
  if (!root || !anchorEl) return;

  const rect = anchorEl.getBoundingClientRect();
  const menuWidth = Math.min(250, Math.max(220, window.innerWidth - 28));
  let left = rect.right - menuWidth;
  left = Math.max(12, Math.min(left, window.innerWidth - menuWidth - 12));

  const estimatedHeight = 210;
  let top = rect.bottom + 8;
  if (top + estimatedHeight > window.innerHeight - 10) {
    top = Math.max(12, rect.top - estimatedHeight - 8);
  }

  const overlay = document.createElement('div');
  overlay.id = MENU_OVERLAY_ID;
  overlay.className = 'channels-menu-overlay';

  const menu = document.createElement('div');
  menu.className = 'channel-menu-wrap channel-menu-wrap--portal';
  menu.style.left = `${Math.round(left)}px`;
  menu.style.top = `${Math.round(top)}px`;
  menu.style.width = `${Math.round(menuWidth)}px`;

  const canToggleSubscription = !channel.isOwnChannel;
  const actionBtn = document.createElement('button');
  actionBtn.type = 'button';
  actionBtn.className = `channel-menu-item ${channel.isSubscribed ? 'destructive' : ''}`.trim();

  if (canToggleSubscription) {
    actionBtn.textContent = channel.pending
      ? 'Выполняется...'
      : channel.isSubscribed
        ? 'Отписаться'
        : 'Подписаться';
    actionBtn.disabled = !!channel.pending;

    actionBtn.addEventListener('click', async (event) => {
      event.stopPropagation();
      if (channel.pending) return;

      const login = state.session.login;
      const storagePwd = state.session.storagePwdInMemory;
      if (!login || !storagePwd) {
        showToast('Сессия недействительна. Выполните вход заново.', { kind: 'error' });
        return;
      }

      channel.pending = true;
      actionBtn.disabled = true;
      actionBtn.textContent = 'Выполняется...';

      const nextSubscribed = !channel.isSubscribed;
      try {
        await authService.addBlockFollowChannel({
          login,
          storagePwd,
          targetBlockchainName: channel.ownerBlockchainName,
          targetBlockNumber: channel.channelRootBlockNumber,
          targetBlockHashHex: channel.channelRootBlockHash,
          unfollow: !nextSubscribed,
        });

        channel.isSubscribed = nextSubscribed;
        channel.pending = false;
        softHaptic(15);
        showToast(nextSubscribed ? 'Подписка на канал включена' : 'Подписка на канал отключена');
        closeChannelMenu(listState);
        await refreshFeed();
      } catch (error) {
        channel.pending = false;
        actionBtn.disabled = false;
        actionBtn.textContent = channel.isSubscribed ? 'Отписаться' : 'Подписаться';
        showToast(toUserMessage(error, 'Не удалось изменить подписку.'), { kind: 'error' });
        rerenderList();
      }
    });
  } else {
    actionBtn.textContent = 'Собственный канал';
    actionBtn.disabled = true;
  }

  const toggleWrap = document.createElement('div');
  toggleWrap.className = 'channel-menu-toggle';

  const toggleLabel = document.createElement('span');
  toggleLabel.textContent = 'Уведомления';

  const toggleBtn = document.createElement('button');
  toggleBtn.type = 'button';
  toggleBtn.className = `channel-toggle-btn ${channel.notificationsEnabled ? 'is-on' : ''}`.trim();
  toggleBtn.addEventListener('click', (event) => {
    event.stopPropagation();
    animatePress(toggleBtn);

    channel.notificationsEnabled = !channel.notificationsEnabled;
    const next = { ...listState.notificationsState, [channel.id]: channel.notificationsEnabled };
    listState.notificationsState = next;
    writeChannelNotificationsState(next);

    toggleBtn.classList.toggle('is-on', channel.notificationsEnabled);
    softHaptic(10);
  });

  toggleWrap.append(toggleLabel, toggleBtn);
  menu.append(actionBtn, toggleWrap);
  overlay.append(menu);
  root.append(overlay);

  const onOverlayClick = (event) => {
    if (event.target === overlay) {
      closeChannelMenu(listState);
      rerenderList();
    }
  };

  const onWindowResize = () => {
    closeChannelMenu(listState);
    rerenderList();
  };

  overlay.addEventListener('click', onOverlayClick);
  window.addEventListener('resize', onWindowResize);

  listState.menuCleanup = () => {
    overlay.removeEventListener('click', onOverlayClick);
    window.removeEventListener('resize', onWindowResize);
  };
}

function renderChannelMain(channel, activeTab) {
  const main = document.createElement('div');
  main.className = 'channel-row-main';

  if (activeTab === 'feed') {
    const author = document.createElement('p');
    author.className = 'channel-row-author';
    author.textContent = `@${channel.ownerName}`;

    const title = document.createElement('strong');
    title.className = 'channel-row-title';
    title.textContent = channel.channelName ? `#${channel.channelName}` : channel.title;

    const preview = document.createElement('p');
    preview.className = 'channel-row-message';
    preview.textContent = channel.messagePreview || 'Ждем ваших начинаний';

    const meta = document.createElement('p');
    meta.className = 'channel-row-owner channel-counter-meta';
    meta.textContent = `Сообщений: ${channel.messagesCount || 0}`;

    main.append(author, title, preview, meta);
    return main;
  }

  const title = document.createElement('strong');
  title.className = 'channel-row-title';
  title.textContent = activeTab === 'my' ? channel.channelName : channel.title;

  if (activeTab === 'my' && channel.channelDescription) {
    const desc = document.createElement('p');
    desc.className = 'channel-row-description';
    desc.textContent = channel.channelDescription;
    main.append(desc);
  }

  const preview = document.createElement('p');
  preview.className = 'channel-row-message';
  preview.textContent = channel.messagePreview || 'Ждем ваших начинаний';

  const meta = document.createElement('p');
  meta.className = 'channel-row-owner channel-counter-meta';
  meta.textContent = `Сообщений: ${channel.messagesCount || 0}`;

  main.prepend(title);
  main.append(preview, meta);
  return main;
}

function renderListContent({ screen, container, listState, navigate, refreshFeed }) {
  container.innerHTML = '';

  const allChannels = listState.channels || [];
  const activeTab = listState.activeTab;
  const filtered = allChannels.filter((channel) => channel.tabCategory === activeTab);

  if (!filtered.length) {
    container.append(renderEmptyState(activeTab, navigate));
    return;
  }

  const list = document.createElement('div');
  list.className = 'stack channels-groups channels-list-body-fade';

  const rerenderList = () => renderListContent({ screen, container, listState, navigate, refreshFeed });

  filtered.forEach((channel) => {
    const row = document.createElement('article');
    row.className = 'channel-row';
    const countersVisible = listState.revealedCounters.has(channel.id);
    row.classList.toggle('is-counters-visible', countersVisible);

    const avatar = document.createElement('div');
    avatar.className = 'avatar';
    avatar.textContent = channel.avatar;

    const main = renderChannelMain(channel, activeTab);

    const isGuest = !state.session.isAuthorized;
    const controls = document.createElement('div');
    controls.className = 'channel-row-controls';

    const time = document.createElement('span');
    time.className = 'channel-row-time';
    time.textContent = channel.lastMessageAt ? formatRelativeTime(channel.lastMessageAt) : '—';

    const count = document.createElement('span');
    count.className = 'unread channel-row-count';
    const unreadCount = Number(channel.unreadCount || 0);
    count.textContent = unreadCount > 0 ? String(unreadCount) : '';
    count.classList.toggle('is-empty', unreadCount <= 0);

    if (!isGuest) {
      const menuButton = document.createElement('button');
      menuButton.type = 'button';
      menuButton.className = 'channel-menu-trigger';
      menuButton.textContent = '…';
      menuButton.addEventListener('click', (event) => {
        event.stopPropagation();
        animatePress(menuButton);
        listState.revealedCounters.add(channel.id);

        if (listState.openMenuId === channel.id) {
          closeChannelMenu(listState);
          rerenderList();
          return;
        }

        listState.openMenuId = channel.id;
        openChannelMenu({
          listState,
          channel,
          anchorEl: menuButton,
          refreshFeed: async () => loadFeedAndRender({ screen, listState, contentEl: container, navigate }),
          rerenderList,
        });
        rerenderList();
      });
      controls.append(menuButton);
    }
    controls.append(time, count);

    row.append(avatar, main, controls);
    row.addEventListener('click', () => {
      const route = channel.route || makeShineChannelRoute({
      ownerLogin: String(channel.ownerName || 'channel'),
      ownerBlockchainName: String(channel.ownerName || ''),
      channelName: String(channel.channelName || channel.id),
      });
      if (route) navigate(route);
    });
    list.append(row);
  });

  container.append(list);
}

function updateBottomCta({ button, listState, navigate, isTabEmpty = false }) {
  const tab = listState.activeTab;
  const baseClass = `primary-btn channels-bottom-action${isTabEmpty && tab !== 'my' ? ' is-empty-lift' : ''}`;

  if (tab === 'feed') {
    button.textContent = 'Найти канал';
    button.className = baseClass;
    button.onclick = () => openChannelFinderModal({ navigate });
    return;
  }

  if (tab === 'my') {
    button.textContent = 'Найти канал';
    button.className = baseClass;
    button.onclick = () => openChannelFinderModal({ navigate });
    return;
  }

  button.textContent = 'Поиск каналов';
  button.className = baseClass;
  button.onclick = () => openChannelFinderModal({ navigate });
}

async function loadFeedAndRender({ screen, listState, contentEl, navigate }) {
  closeChannelMenu(listState);
  renderSkeletonList(contentEl, 5);

  if (!state.session.isAuthorized) {
    setChannelsFeed(null, {});
    listState.channels = [];
    renderListContent({
      screen,
      container: contentEl,
      listState,
      navigate,
      refreshFeed: async () => loadFeedAndRender({ screen, listState, contentEl, navigate }),
    });
    return;
  }

  try {
    const feed = await authService.listSubscriptionsFeed(state.session.login, 200);
    const groups = mapApiFeed(feed, listState.notificationsState);

    listState.channels = toListModel(groups);
    setChannelsFeed(feed, groups.index);

    renderListContent({
      screen,
      container: contentEl,
      listState,
      navigate,
      refreshFeed: async () => loadFeedAndRender({ screen, listState, contentEl, navigate }),
    });
  } catch (error) {
    setChannelsFeed(null, {});
    contentEl.innerHTML = '';

    if (isChannelsDemoMode()) {
      renderDemoFallback(contentEl, navigate, error, () => loadFeedAndRender({ screen, listState, contentEl, navigate }));
      return;
    }

    renderErrorState(contentEl, error, () => loadFeedAndRender({ screen, listState, contentEl, navigate }));
  }
}

export function render({ navigate, route }) {
  const screen = document.createElement('section');
  screen.className = 'stack channels-screen channels-screen--list';
  const appScreen = document.getElementById('app-screen');
  appScreen?.classList.add('channels-scroll-clean');

  const createSuccessFlash = pullCreateSuccessFlash();
  const notificationsState = readChannelNotificationsState();

  const isGuest = !state.session.isAuthorized;
  const listState = {
    activeTab: TAB_ORDER.includes(String(route?.params?.mode || '').trim())
      ? String(route?.params?.mode).trim()
      : 'feed',
    openMenuId: null,
    notificationsState,
    revealedCounters: new Set(),
    channels: [],
    menuCleanup: null,
  };
  if (isGuest && listState.activeTab === 'my') {
    listState.activeTab = 'feed';
  }

  const contentEl = document.createElement('div');
  contentEl.className = 'channels-list-content';

  const topBarEl = document.createElement('div');
  topBarEl.className = 'channels-top-bar';

  const topBarLeft = document.createElement('div');
  topBarLeft.className = 'channels-top-left';

  const backBtn = document.createElement('button');
  backBtn.type = 'button';
  backBtn.className = 'icon-btn channels-top-back-btn';
  backBtn.textContent = '←';
  backBtn.setAttribute('aria-label', 'Назад');
  backBtn.addEventListener('click', () => navigateBack());

  const topTitle = document.createElement('strong');
  topTitle.className = 'channels-top-title';

  const myChannelsBtn = document.createElement('button');
  myChannelsBtn.type = 'button';
  myChannelsBtn.className = 'secondary-btn channels-top-switch-btn';
  myChannelsBtn.textContent = 'Мои каналы';
  myChannelsBtn.addEventListener('click', () => {
    if (listState.activeTab === 'my') return;
    listState.activeTab = 'my';
    rerenderList();
  });

  const topBarRight = document.createElement('div');
  topBarRight.className = 'channels-top-right';

  const findChannelBtn = document.createElement('button');
  findChannelBtn.type = 'button';
  findChannelBtn.className = 'icon-btn channels-top-search-btn';
  findChannelBtn.textContent = '🔎';
  findChannelBtn.setAttribute('aria-label', 'Найти канал');
  findChannelBtn.addEventListener('click', () => openChannelFinderModal({ navigate }));

  const createInMyBtn = document.createElement('button');
  createInMyBtn.type = 'button';
  createInMyBtn.className = 'icon-btn channels-top-add-btn';
  createInMyBtn.textContent = '+';
  createInMyBtn.setAttribute('aria-label', 'Создать канал');
  createInMyBtn.addEventListener('click', () => navigate('add-channel-view'));

  const switchToAllBtn = document.createElement('button');
  switchToAllBtn.type = 'button';
  switchToAllBtn.className = 'secondary-btn channels-top-switch-btn';
  switchToAllBtn.textContent = 'Все каналы';
  switchToAllBtn.addEventListener('click', () => {
    if (listState.activeTab === 'feed') return;
    listState.activeTab = 'feed';
    rerenderList();
  });

  topBarLeft.append(backBtn, topTitle);
  topBarRight.append(myChannelsBtn, findChannelBtn, createInMyBtn);
  topBarEl.append(topBarLeft, topBarRight);

  const bottomCta = document.createElement('button');
  bottomCta.type = 'button';

  const reloadFeed = async () => loadFeedAndRender({ screen, listState, contentEl, navigate });

  const rerenderList = () => {
    try {
      const expectedPath = `/channels-list/${listState.activeTab}`;
      if (window.location.pathname !== expectedPath) {
        window.history.replaceState({}, '', expectedPath);
      }
    } catch {
      // ignore history errors
    }

    const isTabEmpty = !(listState.channels || []).some((channel) => channel.tabCategory === listState.activeTab);

    closeChannelMenu(listState);

    renderListContent({
      screen,
      container: contentEl,
      listState,
      navigate,
      refreshFeed: reloadFeed,
    });

    if (listState.activeTab === 'my' && !isGuest) {
      myChannelsBtn.style.display = 'none';
      topTitle.textContent = 'Мои каналы';
      findChannelBtn.style.display = 'none';
      switchToAllBtn.style.display = '';
      createInMyBtn.style.display = '';
      if (!switchToAllBtn.isConnected) topBarLeft.append(switchToAllBtn);
      if (topTitle.parentElement !== topBarRight) topBarRight.prepend(topTitle);
    } else {
      myChannelsBtn.style.display = isGuest ? 'none' : '';
      topTitle.textContent = 'Все каналы';
      findChannelBtn.style.display = '';
      switchToAllBtn.style.display = 'none';
      createInMyBtn.style.display = 'none';
      if (switchToAllBtn.isConnected) switchToAllBtn.remove();
      if (topTitle.parentElement !== topBarLeft) topBarLeft.append(topTitle);
    }

    updateBottomCta({
      button: bottomCta,
      listState,
      navigate,
      isTabEmpty,
    });
  };

  let touchStartX = 0;
  let touchStartY = 0;
  contentEl.addEventListener('touchstart', (event) => {
    const p = event.changedTouches?.[0];
    if (!p) return;
    touchStartX = p.clientX;
    touchStartY = p.clientY;
  }, { passive: true });
  contentEl.addEventListener('touchend', (event) => {
    const p = event.changedTouches?.[0];
    if (!p) return;
    const dx = p.clientX - touchStartX;
    const dy = p.clientY - touchStartY;
    if (Math.abs(dx) < 45 || Math.abs(dx) < Math.abs(dy)) return;
    const index = TAB_ORDER.indexOf(listState.activeTab);
    if (index < 0) return;
    if (dx < 0 && index < TAB_ORDER.length - 1) listState.activeTab = TAB_ORDER[index + 1];
    if (dx > 0 && index > 0) listState.activeTab = TAB_ORDER[index - 1];
    if (isGuest && listState.activeTab === 'my') listState.activeTab = 'feed';
    rerenderList();
  }, { passive: true });

  screen.append(topBarEl, contentEl, bottomCta);

  if (createSuccessFlash) {
    showToast(createSuccessFlash);
  }

  updateBottomCta({
    button: bottomCta,
    listState,
    navigate,
    isTabEmpty: true,
  });

  // Применяем корректное состояние хедера сразу на первом рендере,
  // чтобы не показывать лишние кнопки до первой перерисовки.
  rerenderList();
  loadFeedAndRender({ screen, listState, contentEl, navigate });

  screen.cleanup = () => {
    closeChannelMenu(listState);
    appScreen?.classList.remove('channels-scroll-clean');
  };

  return screen;
}
