import { renderHeader } from '../components/header.js';
import { authService, state } from '../state.js';
import { makeProfileRoute } from '../services/shine-routes.js';
import { makeProfileLinksRoute } from '../services/shine-routes.js';
import { renderNetworkLab } from './network/lab.js';
import { createForceGraph } from './network/force-graph.js';
import { engineModelFromGraphModel } from './network/adapter.js';
import { openNodeMenu, relationLabelRu } from './network/node-menu.js';

export const pageMeta = { id: 'network-view', title: 'Связи' };

const GENDER_MALE = 'male';
const GENDER_FEMALE = 'female';
const GENDER_UNKNOWN = 'unknown';

function normalizeLogin(value) {
  return String(value || '').trim();
}

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

function normKey(value) {
  return normalizeLogin(value).toLowerCase();
}

function uniqueLogins(list) {
  const out = [];
  const seen = new Set();
  (Array.isArray(list) ? list : []).forEach((item) => {
    const login = normalizeLogin(item);
    if (!login) return;
    const key = normKey(login);
    if (seen.has(key)) return;
    seen.add(key);
    out.push(login);
  });
  return out;
}

function escapeHtml(text) {
  return String(text || '')
    .replaceAll('&', '&amp;')
    .replaceAll('<', '&lt;')
    .replaceAll('>', '&gt;')
    .replaceAll('"', '&quot;')
    .replaceAll("'", '&#39;');
}

function normalizeGender(value) {
  const clean = String(value || '').trim().toLowerCase();
  if (clean === GENDER_MALE) return GENDER_MALE;
  if (clean === GENDER_FEMALE) return GENDER_FEMALE;
  return GENDER_UNKNOWN;
}

function toSet(list) {
  return new Set(uniqueLogins(list).map((value) => normKey(value)));
}

function hasLogin(setObj, login) {
  return setObj.has(normKey(login));
}

function getMarkByLogin(allUsers) {
  const map = new Map();
  (Array.isArray(allUsers) ? allUsers : []).forEach((row) => {
    const login = normalizeLogin(row?.login);
    if (!login) return;
    map.set(normKey(login), {
      login,
      official: Boolean(row?.official),
      shine: Boolean(row?.shine),
      officialLabel: String(row?.officialLabel || (row?.official ? 'официальный' : 'неофициальный')),
      shineLabel: String(row?.shineLabel || (row?.shine ? 'сияющий' : 'несияющий')),
      avatar: normalizeAvatar(row),
    });
  });
  return map;
}

function normalizeAvatar(row) {
  const txFromAvatar = String(row?.avatar?.ar || '').trim();
  if (txFromAvatar) return { ar: txFromAvatar };
  const txFallback = String(row?.avatarTxId || '').trim();
  if (txFallback) return { ar: txFallback };
  return null;
}

function applyRelativeGender(map, rows) {
  (Array.isArray(rows) ? rows : []).forEach((row) => {
    const login = normalizeLogin(row?.login);
    if (!login) return;
    const key = normKey(login);
    const gender = normalizeGender(row?.gender);
    const prev = map.get(key) || GENDER_UNKNOWN;
    if (prev === GENDER_UNKNOWN || gender !== GENDER_UNKNOWN) map.set(key, gender);
  });
}

function getRelativeGenderMap(graph) {
  const map = new Map();
  applyRelativeGender(map, graph?.parents);
  applyRelativeGender(map, graph?.children);
  applyRelativeGender(map, graph?.siblings);
  applyRelativeGender(map, graph?.spouses);
  return map;
}

function buildGraphModel(graph, centerLogin) {
  const login = normalizeLogin(graph?.login || centerLogin || state.session.login);
  const outFriends = toSet(graph?.outFriends);
  const inFriends = toSet(graph?.inFriends);
  const outParents = toSet(graph?.outParents);
  const inParents = toSet(graph?.inParents);
  const outChildren = toSet(graph?.outChildren);
  const inChildren = toSet(graph?.inChildren);
  const outSiblings = toSet(graph?.outSiblings);
  const inSiblings = toSet(graph?.inSiblings);
  const outSpouses = toSet(graph?.outSpouses);
  const inSpouses = toSet(graph?.inSpouses);
  // контакты/подписки/знакомые — для слоя «Все контакты» (Фаза 3)
  const outContacts = toSet(graph?.outContacts);
  const inContacts = toSet(graph?.inContacts);
  const outFollows = toSet(graph?.outFollows);
  const inFollows = toSet(graph?.inFollows);
  const outKnown = toSet(graph?.outKnownPersons);
  const inKnown = toSet(graph?.inKnownPersons);

  const relativesGender = getRelativeGenderMap(graph);
  const allMarks = getMarkByLogin(graph?.allUsers);

  const allLogins = uniqueLogins([
    ...(graph?.outFriends || []),
    ...(graph?.inFriends || []),
    ...(graph?.outParents || []),
    ...(graph?.inParents || []),
    ...(graph?.outChildren || []),
    ...(graph?.inChildren || []),
    ...(graph?.outSiblings || []),
    ...(graph?.inSiblings || []),
    ...(graph?.outSpouses || []),
    ...(graph?.inSpouses || []),
    ...(graph?.outContacts || []),
    ...(graph?.inContacts || []),
    ...(graph?.outFollows || []),
    ...(graph?.inFollows || []),
    ...(graph?.outKnownPersons || []),
    ...(graph?.inKnownPersons || []),
  ]).filter((entry) => normKey(entry) !== normKey(login));

  const relations = allLogins.map((targetLogin) => {
    const parentOut = hasLogin(outParents, targetLogin);
    const parentIn = hasLogin(inChildren, targetLogin);
    const childOut = hasLogin(outChildren, targetLogin);
    const childIn = hasLogin(inParents, targetLogin);
    const siblingOut = hasLogin(outSiblings, targetLogin);
    const siblingIn = hasLogin(inSiblings, targetLogin);
    const spouseOut = hasLogin(outSpouses, targetLogin);
    const spouseIn = hasLogin(inSpouses, targetLogin);
    const friendOut = hasLogin(outFriends, targetLogin);
    const friendIn = hasLogin(inFriends, targetLogin);
    const contactOut = hasLogin(outContacts, targetLogin) || hasLogin(outFollows, targetLogin) || hasLogin(outKnown, targetLogin);
    const contactIn = hasLogin(inContacts, targetLogin) || hasLogin(inFollows, targetLogin) || hasLogin(inKnown, targetLogin);

    let role = 'contact';
    if (parentOut || parentIn) role = 'parent';
    else if (childOut || childIn) role = 'child';
    else if (spouseOut || spouseIn) role = 'spouse';
    else if (siblingOut || siblingIn) role = 'sibling';
    else if (friendOut || friendIn) role = 'friend';

    let forward = friendOut;
    let backward = friendIn;
    if (role === 'parent') {
      forward = parentOut;
      backward = parentIn;
    } else if (role === 'child') {
      forward = childOut;
      backward = childIn;
    } else if (role === 'spouse') {
      forward = spouseOut;
      backward = spouseIn;
    } else if (role === 'sibling') {
      forward = siblingOut;
      backward = siblingIn;
    } else if (role === 'contact') {
      forward = contactOut;
      backward = contactIn;
    }

    return {
      login: targetLogin,
      key: normKey(targetLogin),
      role,
      isRelative: role === 'parent' || role === 'child' || role === 'spouse' || role === 'sibling',
      gender: normalizeGender(relativesGender.get(normKey(targetLogin))),
      forward: Boolean(forward),
      backward: Boolean(backward),
      mark: allMarks.get(normKey(targetLogin)) || null,
    };
  });

  return {
    centerLogin: login,
    centerMark: allMarks.get(normKey(login)) || null,
    relations,
  };
}

let persistedCenterLogin = '';
let persistedCenterHistory = [];

export function render({ navigate, route }) {
  // Лабораторный режим force-графа (Фаза 1): рендерится из мока, не трогая реальный путь ниже.
  if (String(route?.params?.mode || '').trim().toLowerCase() === 'lab') {
    return renderNetworkLab({ navigate });
  }
  const keepHistory = String(route?.params?.mode || '').trim().toLowerCase() === 'keep-history';
  const routeLogin = normalizeLogin(route?.params?.login || '');
  if (!keepHistory) {
    persistedCenterLogin = '';
    persistedCenterHistory = [];
  }

  const screen = document.createElement('section');
  screen.className = 'network-screen';
  const appScreenEl = document.getElementById('app-screen');
  appScreenEl?.classList.add('network-scroll-lock');

  const stage = document.createElement('div');
  stage.className = 'network-stage';

  const board = document.createElement('div');
  board.className = 'network-board network-board--full fg-stage';

  let centerLogin = normalizeLogin(persistedCenterLogin || state.session.login || '');
  let centerHistory = Array.isArray(persistedCenterHistory) ? [...persistedCenterHistory] : [];
  let engine = null;
  let sheetEl = null;
  let loadSeq = 0;

  // Фильтры слоёв (Фаза 3). Фокус всегда виден; предикат применяется к периферийным узлам.
  const FILTERS = {
    all: { label: 'Все', pred: () => true },
    family: { label: 'Семья', pred: (n) => n.relationType === 'family' },
    friends: { label: 'Друзья', pred: (n) => n.relationType === 'friend' },
    shining: { label: 'Сияющие', pred: (n) => Boolean(n.shining) },
  };
  const FILTER_ORDER = ['all', 'family', 'friends', 'shining'];
  let activeFilter = 'all';
  const filterChips = {};

  function applyFilter(key) {
    if (!FILTERS[key]) return;
    activeFilter = key;
    FILTER_ORDER.forEach((k) => {
      const el = filterChips[k];
      if (el) el.classList.toggle('is-active', k === activeFilter);
    });
    if (engine) engine.setFilter(FILTERS[key].pred);
  }

  function profileInfoRoute(login) {
    const cleanLogin = normalizeLogin(login);
    if (!cleanLogin) return '';
    if (normKey(cleanLogin) === normKey(state.session.login)) return 'profile-view';
    return makeProfileRoute(cleanLogin);
  }

  function helpText() {
    return [
      'Обозначения на экране связей:',
      '• Синие линии — близкие друзья.',
      '• Оранжевые линии — родственники.',
      '• Стрелка на линии — односторонняя связь.',
      '• Если стрелки нет, связь взаимная.',
    ].join('\n');
  }

  function persistHistory() {
    persistedCenterLogin = centerLogin;
    persistedCenterHistory = [...centerHistory];
  }

  function syncLinksUrl(login, { push = false } = {}) {
    const clean = normalizeLogin(login);
    if (!clean) return;
    const nextPath = `/${makeProfileLinksRoute(clean)}`;
    if (window.location.pathname === nextPath) return;
    if (push) window.history.pushState({}, '', nextPath);
    else window.history.replaceState({}, '', nextPath);
  }

  function setBackButtonState(backBtn) {
    if (!(backBtn instanceof HTMLButtonElement)) return;
    backBtn.disabled = centerHistory.length === 0;
  }

  function openSearchModal() {
    const root = document.getElementById('modal-root');
    if (!(root instanceof HTMLElement)) return;
    root.innerHTML = `
      <div class="modal" id="network-search-modal">
        <div class="modal-card stack">
          <button class="icon-btn" type="button" id="network-search-close" style="justify-self:end;">✕</button>
          <h3 class="modal-title">Найти человека</h3>
          <div class="row" style="gap:8px;">
            <input class="input" id="network-search-input" type="text" maxlength="30" placeholder="Введите логин" />
            <button class="primary-btn" type="button" id="network-search-run">Искать</button>
          </div>
          <div class="meta-muted" id="network-search-meta">Введите логин. Поиск начнётся автоматически через 2 секунды.</div>
          <div class="stack" id="network-search-results"></div>
        </div>
      </div>
    `;

    const modal = root.querySelector('#network-search-modal');
    const closeBtn = root.querySelector('#network-search-close');
    const inputEl = root.querySelector('#network-search-input');
    const runBtn = root.querySelector('#network-search-run');
    const metaEl = root.querySelector('#network-search-meta');
    const resultsEl = root.querySelector('#network-search-results');
    if (!(modal instanceof HTMLElement) || !(inputEl instanceof HTMLInputElement) || !(resultsEl instanceof HTMLElement)) {
      root.innerHTML = '';
      return;
    }

    let selectedLogin = '';
    let searchSeq = 0;

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

    const applySelection = (login) => {
      selectedLogin = normalizeLogin(login);
      const rows = resultsEl.querySelectorAll('[data-candidate]');
      rows.forEach((row) => {
        if (!(row instanceof HTMLElement)) return;
        row.classList.toggle('is-selected', String(row.dataset.candidate || '') === selectedLogin);
      });
    };

    const renderCandidates = (logins) => {
      const items = (Array.isArray(logins) ? logins : [])
        .map((item) => normalizeLogin(item))
        .filter(Boolean)
        .slice(0, 5);
      if (!items.length) {
        resultsEl.innerHTML = '<div class="meta-muted">Кандидаты не найдены.</div>';
        applySelection('');
        return;
      }
      resultsEl.innerHTML = items.map((login) => (
        `<button type="button" class="ghost-btn network-search-candidate" data-candidate="${escapeHtml(login)}">${escapeHtml(login)}</button>`
      )).join('');
      applySelection('');
    };

    const runSearch = async () => {
      const query = normalizeLogin(inputEl.value);
      if (!query) {
        metaEl.textContent = 'Введите логин.';
        renderCandidates([]);
        return;
      }
      const reqId = ++searchSeq;
      metaEl.textContent = `Поиск по «${query}»...`;
      if (runBtn instanceof HTMLButtonElement) runBtn.disabled = true;
      try {
        const found = await authService.searchUsers(query);
        if (reqId !== searchSeq) return;
        renderCandidates(found);
        const foundCount = Math.min(5, Array.isArray(found) ? found.length : 0);
        metaEl.textContent = foundCount > 0
          ? `Найдено кандидатов: ${foundCount}. Выберите одного.`
          : 'Кандидаты не найдены.';
      } catch (error) {
        if (reqId !== searchSeq) return;
        renderCandidates([]);
        metaEl.textContent = `Ошибка поиска: ${error?.message || 'unknown'}`;
      } finally {
        if (runBtn instanceof HTMLButtonElement) runBtn.disabled = false;
      }
    };

    modal.addEventListener('click', (event) => {
      if (event.target === modal) close();
    });
    closeBtn?.addEventListener('click', close);
    runBtn?.addEventListener('click', () => { void runSearch(); });
    const debouncedSearch = createDebounced(() => { void runSearch(); }, 2000);
    inputEl.addEventListener('input', debouncedSearch);
    inputEl.addEventListener('keydown', (event) => {
      if (event.key === 'Enter') {
        event.preventDefault();
        void runSearch();
      }
    });
    resultsEl.addEventListener('click', (event) => {
      const target = event.target;
      if (!(target instanceof HTMLElement)) return;
      const button = target.closest('[data-candidate]');
      if (!(button instanceof HTMLElement)) return;
      const nextLogin = String(button.dataset.candidate || '');
      applySelection(nextLogin);
      if (!nextLogin) return;
      close();
      void load(nextLogin, { pushHistory: true });
    });

    window.setTimeout(() => inputEl.focus(), 0);
  }

  // Нижний сниппет (bottom sheet) с краткой сводкой об узле; не блокирует карту.
  function showNodeSheet(node) {
    if (!sheetEl) {
      sheetEl = document.createElement('div');
      sheetEl.className = 'fg-sheet';
      stage.append(sheetEl);
    }
    const login = normalizeLogin(node.login);
    const shineBadge = node.shining ? '<span class="fg-sheet-badge">сияющий</span>' : '';
    sheetEl.innerHTML = `
      <button class="fg-sheet-close" type="button" data-act="close" aria-label="Закрыть">✕</button>
      <div class="fg-sheet-body">
        <div class="fg-sheet-title">${escapeHtml(login)} ${shineBadge}</div>
        <div class="fg-sheet-rel">${escapeHtml(relationLabelRu(node.relationType))}</div>
      </div>
      <div class="fg-sheet-actions">
        <button class="ghost-btn" type="button" data-act="profile">Профиль</button>
        <button class="primary-btn" type="button" data-act="write">Написать</button>
      </div>
    `;
    sheetEl.classList.add('is-open');
    sheetEl.onclick = (e) => {
      const btn = e.target instanceof HTMLElement ? e.target.closest('[data-act]') : null;
      if (!(btn instanceof HTMLElement)) return;
      const act = btn.dataset.act;
      if (act === 'close') hideNodeSheet();
      else if (act === 'profile') { const r = profileInfoRoute(login); if (r) navigate(r); }
      else if (act === 'write') navigate(`chat-view/${encodeURIComponent(login)}`);
    };
  }

  function hideNodeSheet() {
    if (sheetEl) sheetEl.classList.remove('is-open');
  }

  function ensureEngine(model) {
    if (engine) {
      engine.setModel(model);
      return;
    }
    engine = createForceGraph({
      stage: board,
      model,
      // тап по периферийному узлу — центрируем (грузим его граф) и показываем нижний сниппет
      onNodeTap: (node) => { showNodeSheet(node); void load(node.login, { pushHistory: true }); },
      // тап по центру — полноценный профиль
      onCenterTap: (node) => {
        const routeTo = profileInfoRoute(node.login);
        if (routeTo) navigate(routeTo);
      },
      // долгое нажатие — контекстное меню (вне масштабируемого холста)
      onNodeLongPress: (node, point) => {
        const login = normalizeLogin(node.login);
        openNodeMenu({
          login,
          relationType: node.relationType,
          point,
          actions: [
            { label: 'Профиль', onClick: () => { const r = profileInfoRoute(login); if (r) navigate(r); } },
            { label: 'Написать', onClick: () => navigate(`chat-view/${encodeURIComponent(login)}`) },
          ],
        });
      },
    });
  }

  async function load(nextCenterLogin = '', { pushHistory = false } = {}) {
    const requestId = ++loadSeq;
    const prevCenter = centerLogin;
    const targetCenter = normalizeLogin(nextCenterLogin || prevCenter || state.session.login);

    try {
      const graph = await authService.getUserConnectionsGraph(targetCenter);
      if (requestId !== loadSeq) return;
      centerLogin = targetCenter;
      if (pushHistory && prevCenter && normKey(prevCenter) !== normKey(targetCenter)) {
        centerHistory.push(prevCenter);
      }
      syncLinksUrl(targetCenter, { push: pushHistory });

      const graphModel = buildGraphModel(graph, targetCenter);
      const engineModel = engineModelFromGraphModel(graphModel);
      ensureEngine(engineModel);
      // сохраняем выбранный фильтр при перестроении графа (центрирование/переход)
      if (engine && activeFilter !== 'all') engine.setFilter(FILTERS[activeFilter].pred);

      persistHistory();
      setBackButtonState(backBtnEl);
    } catch (error) {
      if (requestId !== loadSeq) return;
      window.alert(`Ошибка загрузки связей: ${error?.message || 'unknown'}`);
    }
  }

  const header = renderHeader({
    title: 'Связи',
    leftAction: {
      label: '←',
      onClick: () => {
        if (!centerHistory.length) return;
        const prev = centerHistory.pop();
        if (!prev) {
          setBackButtonState(backBtnEl);
          return;
        }
        void load(prev, { pushHistory: false });
      },
    },
    rightActions: [
      { label: 'Найти', onClick: openSearchModal },
      { label: '?', onClick: () => window.alert(helpText()) },
    ],
  });
  const backBtnEl = header.querySelector('.header-left .icon-btn');
  setBackButtonState(backBtnEl);

  // Ресайз и перерисовку рёбер движок обрабатывает сам (window resize + ResizeObserver внутри).
  screen.cleanup = () => {
    if (engine) engine.destroy();
    engine = null;
    appScreenEl?.classList.remove('network-scroll-lock');
  };

  if (routeLogin) {
    centerLogin = routeLogin;
    centerHistory = [];
    persistHistory();
    void load(centerLogin, { pushHistory: false });
  } else if (keepHistory && centerLogin) {
    void load(centerLogin, { pushHistory: false });
  } else {
    centerLogin = normalizeLogin(state.session.login || '');
    centerHistory = [];
    persistHistory();
    if (centerLogin) {
      void load(centerLogin, { pushHistory: false });
    } else {
      window.setTimeout(() => openSearchModal(), 0);
    }
  }
  setBackButtonState(backBtnEl);

  // Панель фильтров слоёв (оверлей под шапкой)
  const filterBar = document.createElement('div');
  filterBar.className = 'fg-filter-bar';
  // Не даём нажатию на чип «провалиться» в сцену: иначе движок делает setPointerCapture на stage,
  // а захват указателя перенаправляет нативный click со сцены — и кнопка фильтра не срабатывает.
  filterBar.addEventListener('pointerdown', (e) => e.stopPropagation());
  FILTER_ORDER.forEach((key) => {
    const chip = document.createElement('button');
    chip.type = 'button';
    chip.className = `fg-filter-chip${key === activeFilter ? ' is-active' : ''}`;
    chip.textContent = FILTERS[key].label;
    chip.addEventListener('click', () => applyFilter(key));
    filterChips[key] = chip;
    filterBar.append(chip);
  });

  header.classList.add('network-header-overlay');
  stage.append(board, header, filterBar);
  screen.append(stage);
  return screen;
}
