// kb-structure.jsx — база знаний: дерево + галерея + панель раздела

const FOLDER_TONES = ['folder-blue', 'folder-amber', 'folder-violet', 'folder-green'];

const STATUS_MAP = {
  published: { label: 'Опубликовано', cls: 'kb-status--pub' },
  draft: { label: 'Черновик', cls: 'kb-status--draft' },
  review: { label: 'На ревью', cls: 'kb-status--review' },
};

const SPECIAL = {
  drafts: { label: 'Черновики', icon: 'edit', filter: (a) => a.status === 'draft' && !a.deletedAt },
  favorites: {
    label: 'Избранное',
    icon: 'star',
    filter: (a) => window.KB.isBookmarked(a.id) && !a.deletedAt && window.KB.canViewArticle(a),
  },
  trash: { label: 'Корзина', icon: 'trash', filter: (a) => a.deletedAt },
};

const applyFilters = (list, { statusFilter, tagFilter, localSearch }) => {
  let out = list;
  if (statusFilter) out = out.filter((a) => a.status === statusFilter);
  if (tagFilter) out = out.filter((a) => (a.tags || []).includes(tagFilter));
  if (localSearch) {
    const q = localSearch.toLowerCase();
    out = out.filter((a) => a.title.toLowerCase().includes(q) || (a.desc || '').toLowerCase().includes(q));
  }
  return out;
};

const sortArticleList = (list, sortKey) => {
  const cmp = sortKey === 'title'
    ? (a, b) => a.title.localeCompare(b.title)
    : sortKey === 'views'
      ? (a, b) => b.views - a.views
      : (a, b) => new Date(b.updatedAt || 0) - new Date(a.updatedAt || 0);
  const pinned = list.filter((a) => a.isPinned).sort(cmp);
  const rest = list.filter((a) => !a.isPinned).sort(cmp);
  return { pinned, rest, all: [...pinned, ...rest] };
};

const countInSection = (sectionId, filters, { descendants = true } = {}) => {
  let list = descendants
    ? window.getSectionArticles(sectionId, { descendants: true })
    : window.getSectionArticles(sectionId);
  return applyFilters(list, filters).length;
};

const isInsideSubtree = (rootId, nodeId) => {
  if (!nodeId) return false;
  if (rootId === nodeId) return true;
  return window.getChildSections(rootId).some((c) => isInsideSubtree(c.id, nodeId));
};

const getNavSnapshot = (state) => ({
  selectedId: state.selectedId,
  specialView: state.specialView,
  openIds: state.openIds,
  localSearch: state.localSearch,
  statusFilter: state.statusFilter,
  tagFilter: state.tagFilter,
  sort: state.sort,
  viewMode: state.viewMode,
});

const TagChip = ({ tag }) => (
  <span className={`tag ${tag.color} kb-grid-card-tag`}>{tag.name}</span>
);

const ArticleCardStats = ({ article }) => {
  const stats = window.KB.getVoteStats(article.id);
  return (
    <span className="kb-grid-card-stats">
      <span className="kb-stat-count kb-stat-count--views" title="Просмотры">
        <Icon name="eye" size={11} /> {(article.views || 0).toLocaleString('ru')}
      </span>
      <span className="kb-stat-count kb-stat-count--up" title="Полезно">
        <Icon name="arrowUp" size={11} /> {stats.up}
      </span>
      <span className="kb-stat-count kb-stat-count--down" title="Не полезно">
        <Icon name="arrowDown" size={11} /> {stats.down}
      </span>
    </span>
  );
};

const ArticleMarkers = ({ article, size = 16 }) => (
  <span className="kb-article-markers">
    {window.KB.isBookmarked(article.id) && (
      <span className="kb-marker kb-marker--star" title="В избранном">
        <Icon name="star" size={size} />
      </span>
    )}
    {article.isMandatory && (
      <span className="kb-marker kb-marker--mandatory" title="Обязательно к прочтению">
        <span className="kb-mandatory-dot" aria-hidden="true" />
      </span>
    )}
  </span>
);

const ArticleCardMenu = ({ article, top, left, onClose, onEdit, onAction, inTrash }) => {
  const ref = React.useRef(null);

  React.useEffect(() => {
    const h = (e) => { if (ref.current && !ref.current.contains(e.target)) onClose(); };
    document.addEventListener('mousedown', h);
    return () => document.removeEventListener('mousedown', h);
  }, [onClose]);

  const items = inTrash ? [
    { id: 'restore', label: 'Восстановить', icon: 'arrowLeft', action: () => onAction('restore', article) },
    { id: 'purge', label: 'Удалить навсегда', icon: 'trash', danger: true, action: () => onAction('purge', article) },
  ] : [
    { id: 'edit', label: 'Редактировать', icon: 'edit', action: () => onEdit(article) },
    {
      id: 'favorite',
      label: window.KB.isBookmarked(article.id) ? 'Убрать из избранного' : 'В избранное',
      icon: 'star',
      action: () => onAction('favorite', article),
    },
    {
      id: 'pin',
      label: article.isPinned ? 'Открепить' : 'Закрепить',
      icon: 'bookmark',
      action: () => onAction('pin', article),
    },
    {
      id: 'mandatory',
      label: article.isMandatory ? 'Снять обязательность' : 'Обязательно к прочтению',
      icon: 'check',
      action: () => onAction('mandatory', article),
    },
    { id: 'delete', label: 'Удалить', icon: 'trash', danger: true, action: () => onAction('delete', article) },
  ];

  return (
    <div className="kb-card-menu" ref={ref} style={{ top, left }}>
      {items.map((item) => (
        <button
          key={item.id}
          type="button"
          className={`kb-card-menu-item${item.danger ? ' kb-card-menu-item--danger' : ''}`}
          onClick={(e) => { e.stopPropagation(); item.action(); onClose(); }}
        >
          <Icon name={item.icon} size={14} />
          {item.label}
        </button>
      ))}
    </div>
  );
};

const KbGridCard = ({ article, onOpen, onMenu, inTrash }) => {
  const tags = (article.tags || []).map((t) => window.findTag(t)).filter(Boolean);
  const author = article.authorId
    ? window.findPerson(article.authorId)
    : window.findPerson(article.responsibles?.[0]);
  const st = inTrash
    ? { label: 'В корзине', cls: 'kb-status--trash' }
    : (STATUS_MAP[article.status] || STATUS_MAP.draft);
  const sectionLabel = window.getSectionLabel(article.section);
  const trashEta = inTrash ? window.KB.formatTrashEta(article) : '';

  return (
    <article className={`kb-grid-card${article.isMandatory ? ' kb-grid-card--mandatory' : ''}`} onClick={() => onOpen(article)}>
      <div className="kb-grid-card-top">
        <span className="kb-grid-card-file"><Icon name="file" size={15} /></span>
        <span className={`kb-grid-card-status ${st.cls}`}>{st.label}</span>
        <ArticleMarkers article={article} />
        {article.isPinned && <span className="kb-grid-card-pin" title="Закреплено"><Icon name="bookmark" size={12} /></span>}
        <button type="button" className="kb-grid-card-more" onClick={(e) => onMenu(e, article)}>
          <Icon name="more" size={14} />
        </button>
      </div>
      <h3 className="kb-grid-card-title">{article.title}</h3>
      {sectionLabel !== '—' && (
        <div className="kb-grid-card-section" title={sectionLabel}>
          <Icon name="folder" size={12} />
          <span>{sectionLabel}</span>
        </div>
      )}
      {tags.length > 0 && (
        <div className="kb-grid-card-tags">
          {tags.slice(0, 3).map((t) => <TagChip key={t.id} tag={t} />)}
        </div>
      )}
      <div className="kb-grid-card-foot">
        {author && (
          <span className="kb-grid-card-author">
            <span className={`avatar ${author.av}`} style={{ width: 22, height: 22, fontSize: 9 }}>
              {author.name.split(' ').map((n) => n[0]).join('').slice(0, 2)}
            </span>
            {author.name.split(' ')[0]}
          </span>
        )}
        <span className="kb-grid-card-meta">
          {!inTrash && <ArticleCardStats article={article} />}
          {inTrash ? trashEta : `${article.updated || '—'} · ${article.readMin || 5} мин`}
        </span>
      </div>
    </article>
  );
};

const AccessModal = ({ open, section, onClose, onSaved }) => {
  const [access, setAccess] = React.useState({ people: [], departments: [] });
  const [saving, setSaving] = React.useState(false);

  React.useEffect(() => {
    if (open && section) {
      setAccess(window.KB.getSectionAccess(section.id));
    }
  }, [open, section]);

  if (!open || !section) return null;

  const save = async () => {
    setSaving(true);
    try {
      await window.KB.saveSectionAccess(section.id, access);
      onSaved?.();
      onClose();
    } catch (err) {
      await window.UI.alert({ title: 'Ошибка', message: err.message });
    } finally {
      setSaving(false);
    }
  };

  return (
    <div className="modal-overlay" onClick={onClose}>
      <div className="modal kb-access-modal" onClick={(e) => e.stopPropagation()}>
        <div className="modal-header">
          <div>
            <h2 className="modal-title">Доступ к разделу</h2>
            <p className="modal-sub">{section.name} — выберите сотрудников и отделы</p>
          </div>
          <button type="button" className="btn btn-ghost btn-icon" onClick={onClose}>×</button>
        </div>
        <AccessPicker value={access} onChange={setAccess} />
        <div className="kb-access-foot">
          <button type="button" className="btn btn-ghost" onClick={onClose}>Отмена</button>
          <button type="button" className="btn btn-primary" onClick={save} disabled={saving}>
            {saving ? 'Сохранение…' : 'Сохранить'}
          </button>
        </div>
      </div>
    </div>
  );
};

const TreeDropZone = ({ depth, active, onDragOver, onDrop, onDragLeave }) => (
  <div
    className={`kb-tree-drop-line${active ? ' is-active' : ''}`}
    style={{ marginLeft: `${8 + depth * 18}px` }}
    onDragOver={(e) => { e.preventDefault(); e.stopPropagation(); onDragOver(e); }}
    onDrop={(e) => { e.preventDefault(); e.stopPropagation(); onDrop(e); }}
    onDragLeave={onDragLeave}
  />
);

const TreeFolder = ({
  section,
  depth,
  selectedId,
  openIds,
  onSelect,
  onToggle,
  addingFor,
  editingId,
  form,
  setForm,
  onSaveFolder,
  onCancelForm,
  onDeleteFolder,
  filters,
  dragId,
  dropTarget,
  onDragStart,
  onDragOver,
  onDragOverBefore,
  onDragOverAppend,
  onDragLeave,
  onDrop,
  onDropBefore,
  onDropAppend,
}) => {
  const children = window.getChildSections(section.id);
  const isOpen = !!openIds[section.id];
  const isSelected = selectedId === section.id;
  const tone = FOLDER_TONES[depth % FOLDER_TONES.length];
  const count = countInSection(section.id, filters);
  const isDropTarget = dropTarget?.type === 'inside' && dropTarget.id === section.id;
  const isDragging = dragId === section.id;

  if (editingId === section.id) {
    return (
      <div className="kb-tree-form" style={{ paddingLeft: `${8 + depth * 18}px` }}>
        <input className="kb-tree-input" value={form.name} onChange={(e) => setForm({ name: e.target.value })} autoFocus />
        <button type="button" className="btn btn-primary btn-sm" onClick={() => onSaveFolder()}>OK</button>
        <button type="button" className="btn btn-ghost btn-sm" onClick={onCancelForm}>×</button>
      </div>
    );
  }

  return (
    <div className="kb-tree-node">
      <div
        className={`kb-tree-item${isSelected ? ' is-selected' : ''}${isDropTarget ? ' is-drop-target' : ''}${isDragging ? ' is-dragging' : ''}`}
        style={{ paddingLeft: `${8 + depth * 18}px` }}
        onDragOver={(e) => onDragOver(e, section.id)}
        onDragLeave={onDragLeave}
        onDrop={(e) => onDrop(e, section.id)}
      >
        <button
          type="button"
          className="kb-tree-chevron"
          onClick={(e) => { e.stopPropagation(); if (children.length) onToggle(section.id); }}
        >
          {children.length > 0 && (
            <Icon name="chevronRight" size={13} style={{ transform: isOpen ? 'rotate(90deg)' : 'none', transition: 'transform 0.15s' }} />
          )}
        </button>
        <button type="button" className="kb-tree-item-btn" onClick={() => onSelect(section.id)}>
          <span className={`kb-tree-folder-icon ${tone}`}><Icon name="folder" size={15} /></span>
          <span className="kb-tree-item-label">{section.name}</span>
        </button>
        <span className="kb-tree-count">{count}</span>
        <button type="button" className="kb-tree-delete" title="Удалить папку" onClick={(e) => { e.stopPropagation(); onDeleteFolder(section); }}>
          <Icon name="trash" size={12} />
        </button>
        <span
          className="kb-tree-drag"
          draggable
          onDragStart={(e) => onDragStart(e, section.id)}
          onDragEnd={() => { /* reset handled in parent via dragend on document */ }}
          title="Переместить"
        >
          <Icon name="drag" size={13} />
        </span>
      </div>
      {isOpen && children.map((child) => (
        <React.Fragment key={child.id}>
          <TreeDropZone
            depth={depth + 1}
            active={dropTarget?.type === 'before' && dropTarget.id === child.id && dropTarget.parentId === section.id}
            onDragOver={(e) => onDragOverBefore(e, child.id, section.id)}
            onDrop={(e) => onDropBefore(e, child.id, section.id)}
            onDragLeave={onDragLeave}
          />
          <TreeFolder
            section={child}
            depth={depth + 1}
            selectedId={selectedId}
            openIds={openIds}
            onSelect={onSelect}
            onToggle={onToggle}
            addingFor={addingFor}
            editingId={editingId}
            form={form}
            setForm={setForm}
            onSaveFolder={onSaveFolder}
            onCancelForm={onCancelForm}
            onDeleteFolder={onDeleteFolder}
            filters={filters}
            dragId={dragId}
            dropTarget={dropTarget}
            onDragStart={onDragStart}
            onDragOver={onDragOver}
            onDragOverBefore={onDragOverBefore}
            onDragOverAppend={onDragOverAppend}
            onDragLeave={onDragLeave}
            onDrop={onDrop}
            onDropBefore={onDropBefore}
            onDropAppend={onDropAppend}
          />
        </React.Fragment>
      ))}
      {isOpen && children.length > 0 && (
        <TreeDropZone
          depth={depth + 1}
          active={dropTarget?.type === 'append' && dropTarget.parentId === section.id}
          onDragOver={(e) => onDragOverAppend(e, section.id)}
          onDrop={(e) => onDropAppend(e, section.id)}
          onDragLeave={onDragLeave}
        />
      )}
      {isOpen && addingFor === section.id && (
        <div className="kb-tree-folder-create" style={{ paddingLeft: `${8 + (depth + 1) * 18}px` }}>
          <input
            className="kb-tree-input"
            placeholder="Название папки"
            value={form.name}
            onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))}
            autoFocus
          />
          <AccessPicker
            compact
            value={form.access}
            onChange={(access) => setForm((f) => ({ ...f, access }))}
          />
          <div className="kb-tree-form-actions">
            <button type="button" className="btn btn-primary btn-sm" onClick={() => onSaveFolder(section.id)}>Создать</button>
            <button type="button" className="btn btn-ghost btn-sm" onClick={onCancelForm}>Отмена</button>
          </div>
        </div>
      )}
    </div>
  );
};

const KbStructureView = ({ onOpenArticle, onCreate, onEdit, onRefresh, restoredNav, onNavChange, refreshKey = 0 }) => {
  const [revision, bumpRevision] = React.useReducer((n) => n + 1, 0);
  const roots = window.getRootSections();

  const [selectedId, setSelectedId] = React.useState(() => restoredNav?.selectedId ?? null);
  const [specialView, setSpecialView] = React.useState(() => restoredNav?.specialView ?? null);
  const [openIds, setOpenIds] = React.useState(() => {
    if (restoredNav?.openIds) return restoredNav.openIds;
    const initial = { __root: true };
    roots.forEach((s) => { initial[s.id] = true; });
    return initial;
  });
  const [addingFor, setAddingFor] = React.useState(null);
  const [editingId, setEditingId] = React.useState(null);
  const [form, setForm] = React.useState({ name: '', access: { people: [], departments: [] } });
  const [busyId, setBusyId] = React.useState(null);
  const [localSearch, setLocalSearch] = React.useState(() => restoredNav?.localSearch ?? '');
  const [statusFilter, setStatusFilter] = React.useState(() => restoredNav?.statusFilter ?? '');
  const [tagFilter, setTagFilter] = React.useState(() => restoredNav?.tagFilter ?? '');
  const [sort, setSort] = React.useState(() => restoredNav?.sort ?? 'updated');
  const [viewMode, setViewMode] = React.useState(() => restoredNav?.viewMode ?? 'grid');
  const [tagsModalOpen, setTagsModalOpen] = React.useState(false);
  const [accessModalOpen, setAccessModalOpen] = React.useState(false);
  const [addMenuOpen, setAddMenuOpen] = React.useState(false);
  const [cardMenu, setCardMenu] = React.useState(null);
  const [toast, setToast] = React.useState(null);
  const toastTimer = React.useRef(null);
  const [dragId, setDragId] = React.useState(null);
  const [dropTarget, setDropTarget] = React.useState(null);
  const addRef = React.useRef(null);

  const filters = React.useMemo(() => ({ statusFilter, tagFilter, localSearch }), [statusFilter, tagFilter, localSearch]);

  const selectedSection = selectedId ? window.findSection(selectedId) : null;
  const sectionPath = selectedId ? window.getSectionPath(selectedId) : [];

  const navState = React.useMemo(() => ({
    selectedId, specialView, openIds, localSearch, statusFilter, tagFilter, sort, viewMode,
  }), [selectedId, specialView, openIds, localSearch, statusFilter, tagFilter, sort, viewMode]);

  React.useEffect(() => {
    onNavChange?.(navState);
  }, [navState, onNavChange]);

  const restoredNavSigRef = React.useRef(
    restoredNav ? `${restoredNav.specialView ?? ''}|${restoredNav.selectedId ?? ''}` : '',
  );
  React.useEffect(() => {
    if (!restoredNav) return;
    const sig = `${restoredNav.specialView ?? ''}|${restoredNav.selectedId ?? ''}`;
    if (sig === restoredNavSigRef.current) return;
    restoredNavSigRef.current = sig;
    setSpecialView(restoredNav.specialView ?? null);
    setSelectedId(restoredNav.selectedId ?? null);
    if (restoredNav.openIds) setOpenIds(restoredNav.openIds);
    if (restoredNav.localSearch !== undefined) setLocalSearch(restoredNav.localSearch);
    if (restoredNav.statusFilter !== undefined) setStatusFilter(restoredNav.statusFilter);
    if (restoredNav.tagFilter !== undefined) setTagFilter(restoredNav.tagFilter);
    if (restoredNav.sort !== undefined) setSort(restoredNav.sort);
    if (restoredNav.viewMode !== undefined) setViewMode(restoredNav.viewMode);
    bumpRevision();
  }, [restoredNav]);

  React.useEffect(() => {
    if (refreshKey > 0) bumpRevision();
  }, [refreshKey]);

  React.useEffect(() => {
    const end = () => { setDragId(null); setDropTarget(null); };
    document.addEventListener('dragend', end);
    return () => document.removeEventListener('dragend', end);
  }, []);

  React.useEffect(() => {
    const h = (e) => {
      if (addMenuOpen && addRef.current && !addRef.current.contains(e.target)) setAddMenuOpen(false);
    };
    document.addEventListener('mousedown', h);
    return () => document.removeEventListener('mousedown', h);
  }, [addMenuOpen]);

  React.useEffect(() => () => { if (toastTimer.current) clearTimeout(toastTimer.current); }, []);

  const showToast = (message) => {
    if (toastTimer.current) clearTimeout(toastTimer.current);
    setToast(message);
    toastTimer.current = setTimeout(() => setToast(null), 3200);
  };

  const refreshArticles = () => {
    bumpRevision();
    onRefresh?.();
  };

  const selectRoot = () => {
    setSpecialView(null);
    setSelectedId(null);
  };

  const selectFolder = (id) => {
    setSpecialView(null);
    setSelectedId(id);
    const path = window.getSectionPath(id);
    setOpenIds((p) => {
      const next = { ...p, __root: true };
      path.forEach((s) => { next[s.id] = true; });
      return next;
    });
  };

  const toggleFolder = (id) => setOpenIds((p) => ({ ...p, [id]: !p[id] }));

  const resetForm = () => {
    setAddingFor(null);
    setEditingId(null);
    setForm({ name: '', access: { people: [], departments: [] } });
  };

  const handleSaveFolder = async (parentId = null) => {
    if (!form.name.trim()) return;
    setBusyId(editingId || 'new');
    try {
      const saved = await window.KB.saveSection({
        id: editingId || undefined,
        name: form.name.trim(),
        icon: 'folder',
        ...(!editingId ? { parentId, access: form.access } : {}),
      });
      if (!editingId) selectFolder(saved.id);
      resetForm();
      bumpRevision();
      onRefresh?.();
    } catch (err) {
      await window.UI.alert({ title: 'Ошибка', message: err.message });
    } finally {
      setBusyId(null);
    }
  };

  const handleDeleteFolder = async (section) => {
    const children = window.getChildSections(section.id);
    if (children.length) {
      await window.UI.alert({ title: 'Нельзя удалить', message: 'Сначала удалите подпапки' });
      return;
    }
    const count = window.getSectionArticles(section.id).length;
    if (count > 0) {
      await window.UI.alert({ title: 'Нельзя удалить', message: 'В папке есть статьи — перенесите или удалите их' });
      return;
    }
    if (!await window.UI.confirm({
      title: `Удалить «${section.name}»?`,
      message: 'Папка будет удалена без возможности восстановления.',
      confirmLabel: 'Удалить',
      cancelLabel: 'Отмена',
      danger: true,
    })) return;
    setBusyId(section.id);
    try {
      await window.KB.deleteSection(section.id);
      if (selectedId === section.id) selectRoot();
      bumpRevision();
      onRefresh?.();
    } catch (err) {
      await window.UI.alert({ title: 'Ошибка', message: err.message });
    } finally {
      setBusyId(null);
    }
  };

  const handleDragStart = (e, sectionId) => {
    e.stopPropagation();
    setDragId(sectionId);
    e.dataTransfer.effectAllowed = 'move';
    e.dataTransfer.setData('text/plain', sectionId);
  };

  const handleDragOver = (e, targetId) => {
    e.preventDefault();
    e.stopPropagation();
    if (dragId && dragId !== targetId && !isInsideSubtree(dragId, targetId)) {
      setDropTarget({ type: 'inside', id: targetId });
    }
  };

  const handleDragOverBefore = (e, beforeId, parentId) => {
    e.preventDefault();
    e.stopPropagation();
    if (!dragId || dragId === beforeId || isInsideSubtree(dragId, beforeId)) return;
    setDropTarget({ type: 'before', id: beforeId, parentId });
  };

  const handleDragOverAppend = (e, parentId) => {
    e.preventDefault();
    e.stopPropagation();
    if (!dragId || dragId === parentId || (parentId && isInsideSubtree(dragId, parentId))) return;
    setDropTarget({ type: 'append', parentId });
  };

  const handleDragLeave = (e) => {
    if (e.currentTarget.contains(e.relatedTarget)) return;
    setDropTarget(null);
  };

  const resetDrag = () => {
    setDragId(null);
    setDropTarget(null);
  };

  const handleDrop = async (e, targetId) => {
    e.preventDefault();
    e.stopPropagation();
    if (!dragId || dragId === targetId || isInsideSubtree(dragId, targetId)) {
      resetDrag();
      return;
    }
    try {
      await window.KB.reorderSection(dragId, { parentId: targetId, beforeId: null });
      bumpRevision();
      onRefresh?.();
    } catch (err) {
      await window.UI.alert({ title: 'Ошибка', message: err.message });
    } finally {
      resetDrag();
    }
  };

  const handleDropBefore = async (e, beforeId, parentId) => {
    e.preventDefault();
    e.stopPropagation();
    if (!dragId || dragId === beforeId || isInsideSubtree(dragId, beforeId)) {
      resetDrag();
      return;
    }
    try {
      await window.KB.reorderSection(dragId, { parentId, beforeId });
      bumpRevision();
      onRefresh?.();
    } catch (err) {
      await window.UI.alert({ title: 'Ошибка', message: err.message });
    } finally {
      resetDrag();
    }
  };

  const handleDropAppend = async (e, parentId) => {
    e.preventDefault();
    e.stopPropagation();
    if (!dragId || dragId === parentId || (parentId && isInsideSubtree(dragId, parentId))) {
      resetDrag();
      return;
    }
    try {
      await window.KB.reorderSection(dragId, { parentId, beforeId: null });
      bumpRevision();
      onRefresh?.();
    } catch (err) {
      await window.UI.alert({ title: 'Ошибка', message: err.message });
    } finally {
      resetDrag();
    }
  };

  const handleDropRoot = async (e) => {
    e.preventDefault();
    if (!dragId) return;
    try {
      await window.KB.reorderSection(dragId, { parentId: null, beforeId: null });
      bumpRevision();
      onRefresh?.();
    } catch (err) {
      await window.UI.alert({ title: 'Ошибка', message: err.message });
    } finally {
      resetDrag();
    }
  };

  const articleGroups = React.useMemo(() => {
    let list;
    if (specialView) {
      const cfg = SPECIAL[specialView];
      list = cfg ? window.ARTICLES.filter(cfg.filter) : [];
    } else if (selectedId) {
      list = window.getSectionArticles(selectedId);
    } else {
      list = window.getVisibleArticles();
    }
    return sortArticleList(applyFilters(list, filters), sort);
  }, [selectedId, specialView, filters, sort, revision, refreshKey]);

  const { pinned: pinnedArticles, rest: restArticles, all: articles } = articleGroups;

  const rootCount = React.useMemo(() => {
    if (specialView) return applyFilters(window.ARTICLES.filter(SPECIAL[specialView].filter), filters).length;
    return applyFilters(window.getVisibleArticles(), filters).length;
  }, [specialView, filters, revision, refreshKey]);

  const openArticle = (article) => {
    onOpenArticle(article, 'structure', getNavSnapshot(navState));
  };

  const openCardMenu = (e, article) => {
    e.stopPropagation();
    const rect = e.currentTarget.getBoundingClientRect();
    setCardMenu({ article, top: rect.bottom + 4, left: Math.max(8, rect.right - 200) });
  };

  const handleArticleAction = async (action, article) => {
    try {
      if (action === 'restore') {
        await window.KB.restoreArticle(article.id);
        showToast(`«${article.title}» восстановлена`);
      } else if (action === 'purge') {
        if (!await window.UI.confirm({
          title: `Удалить «${article.title}» навсегда?`,
          message: 'Статью нельзя будет восстановить.',
          confirmLabel: 'Удалить навсегда',
          cancelLabel: 'Отмена',
          danger: true,
        })) return;
        await window.KB.hardDeleteArticle(article.id);
        showToast(`«${article.title}» удалена навсегда`);
      } else if (action === 'favorite') {
        await window.KB.toggleBookmark(article.id);
        showToast(window.KB.isBookmarked(article.id) ? 'Добавлено в избранное' : 'Убрано из избранного');
      }
      else if (action === 'pin') {
        const nextPinned = !article.isPinned;
        await window.KB.patchArticle(article.id, { isPinned: nextPinned });
        showToast(nextPinned ? 'Статья закреплена' : 'Статья откреплена');
      }
      else if (action === 'mandatory') {
        const nextMandatory = !article.isMandatory;
        await window.KB.patchArticle(article.id, { isMandatory: nextMandatory });
        showToast(nextMandatory ? 'Статья обязательна к прочтению' : 'Обязательность снята');
      }
      else if (action === 'delete') {
        if (!await window.UI.confirm({
          title: `Удалить «${article.title}»?`,
          message: 'Статья попадёт в корзину.',
          confirmLabel: 'Удалить',
          cancelLabel: 'Отмена',
          danger: true,
        })) return;
        await window.KB.deleteArticle(article.id);
        showToast(`«${article.title}» перемещена в корзину`);
      }
      setCardMenu(null);
      refreshArticles();
    } catch (err) {
      await window.UI.alert({ title: 'Ошибка', message: err.message });
    }
  };

  const panelTitle = specialView ? SPECIAL[specialView]?.label : (selectedSection?.name || 'База знаний');
  const panelDesc = specialView === 'trash'
    ? `Статьи хранятся ${window.KB.TRASH_RETENTION_DAYS} дней, затем удаляются автоматически.`
    : specialView === 'favorites'
      ? 'Статьи, которые вы добавили в избранное.'
    : specialView
      ? 'Служебная подборка статей'
      : selectedId
        ? 'Материалы и инструкции этого раздела базы знаний.'
        : 'Все статьи базы знаний.';

  const sectionAccess = selectedSection ? window.KB.getSectionAccess(selectedSection.id) : null;

  return (
    <div className="kb-layout-page">
      <aside className="kb-layout-tree">
        <div className="kb-layout-tree-head">
          <span className="kb-layout-tree-title">Структура базы знаний</span>
          <div className="kb-layout-tree-actions">
            <button type="button" className="kb-icon-btn" title="Добавить папку" onClick={() => { resetForm(); setAddingFor(selectedId || '__root'); }}>
              <Icon name="plus" size={15} />
            </button>
            <button type="button" className="kb-tags-btn" onClick={() => setTagsModalOpen(true)}>Теги</button>
          </div>
        </div>

        <div className="kb-layout-tree-body">
          <div
            className={`kb-tree-item${!selectedId && !specialView ? ' is-selected' : ''}${dropTarget?.type === 'inside' && dropTarget.id === '__root' ? ' is-drop-target' : ''}`}
            style={{ paddingLeft: 8 }}
            onDragOver={(e) => { e.preventDefault(); if (dragId) setDropTarget({ type: 'inside', id: '__root' }); }}
            onDragLeave={handleDragLeave}
            onDrop={handleDropRoot}
          >
            <span className="kb-tree-chevron" />
            <button type="button" className="kb-tree-item-btn" onClick={selectRoot}>
              <span className="kb-tree-folder-icon folder-blue"><Icon name="folder" size={15} /></span>
              <span className="kb-tree-item-label">База знаний</span>
            </button>
            <span className="kb-tree-count">{rootCount}</span>
          </div>

          {openIds.__root !== false && (
            <>
              {roots.map((s) => (
                <React.Fragment key={s.id}>
                  <TreeDropZone
                    depth={0}
                    active={dropTarget?.type === 'before' && dropTarget.id === s.id && dropTarget.parentId === null}
                    onDragOver={(e) => handleDragOverBefore(e, s.id, null)}
                    onDrop={(e) => handleDropBefore(e, s.id, null)}
                    onDragLeave={handleDragLeave}
                  />
                  <TreeFolder
                    section={s}
                    depth={0}
                    selectedId={specialView ? null : selectedId}
                    openIds={openIds}
                    onSelect={selectFolder}
                    onToggle={toggleFolder}
                    addingFor={addingFor}
                    editingId={editingId}
                    form={form}
                    setForm={setForm}
                    onSaveFolder={handleSaveFolder}
                    onCancelForm={resetForm}
                    onDeleteFolder={handleDeleteFolder}
                    filters={filters}
                    dragId={dragId}
                    dropTarget={dropTarget}
                    onDragStart={handleDragStart}
                    onDragOver={handleDragOver}
                    onDragOverBefore={handleDragOverBefore}
                    onDragOverAppend={handleDragOverAppend}
                    onDragLeave={handleDragLeave}
                    onDrop={handleDrop}
                    onDropBefore={handleDropBefore}
                    onDropAppend={handleDropAppend}
                  />
                </React.Fragment>
              ))}
              {roots.length > 0 && (
                <TreeDropZone
                  depth={0}
                  active={dropTarget?.type === 'append' && dropTarget.parentId === null}
                  onDragOver={(e) => handleDragOverAppend(e, null)}
                  onDrop={(e) => handleDropAppend(e, null)}
                  onDragLeave={handleDragLeave}
                />
              )}
              {addingFor === '__root' && (
                <div className="kb-tree-folder-create" style={{ paddingLeft: 26 }}>
                  <input
                    className="kb-tree-input"
                    placeholder="Название папки"
                    value={form.name}
                    onChange={(e) => setForm((f) => ({ ...f, name: e.target.value }))}
                    autoFocus
                  />
                  <AccessPicker
                    compact
                    value={form.access}
                    onChange={(access) => setForm((f) => ({ ...f, access }))}
                  />
                  <div className="kb-tree-form-actions">
                    <button type="button" className="btn btn-primary btn-sm" onClick={() => handleSaveFolder(null)}>Создать</button>
                    <button type="button" className="btn btn-ghost btn-sm" onClick={resetForm}>Отмена</button>
                  </div>
                </div>
              )}
            </>
          )}

          <div className="kb-tree-divider" />
          {Object.entries(SPECIAL).map(([id, cfg]) => {
            const cnt = applyFilters(window.ARTICLES.filter(cfg.filter), filters).length;
            return (
              <button
                key={id}
                type="button"
                className={`kb-tree-item kb-tree-item--special${specialView === id ? ' is-selected' : ''}`}
                onClick={() => { setSpecialView(id); setSelectedId(null); }}
              >
                <span className="kb-tree-chevron" />
                <span className="kb-tree-folder-icon folder-violet"><Icon name={cfg.icon} size={14} /></span>
                <span className="kb-tree-item-label">{cfg.label}</span>
                <span className="kb-tree-count">{cnt}</span>
              </button>
            );
          })}
        </div>
      </aside>

      <main className="kb-layout-main">
        <nav className="kb-main-crumbs">
          <span>База знаний</span>
          {sectionPath.map((s) => (
            <React.Fragment key={s.id}>
              <Icon name="chevronRight" size={12} />
              <button type="button" onClick={() => selectFolder(s.id)}>{s.name}</button>
            </React.Fragment>
          ))}
        </nav>

        <div className="kb-main-head">
          <div>
            <h1 className="kb-main-title">{panelTitle}</h1>
            <p className="kb-main-desc">{panelDesc}</p>
          </div>
          {!specialView && (
            <div className="kb-add-menu-wrap" ref={addRef}>
              <button type="button" className="btn btn-primary" onClick={() => setAddMenuOpen((v) => !v)}>
                <Icon name="plus" size={14} /> Создать статью
                <Icon name="chevronDown" size={12} />
              </button>
              {addMenuOpen && (
                <div className="kb-add-menu">
                  <button type="button" onClick={() => { setAddMenuOpen(false); onCreate(selectedId); }}>
                    <Icon name="file" size={14} /> Новая статья
                  </button>
                  <button type="button" onClick={() => { resetForm(); setAddMenuOpen(false); setAddingFor(selectedId || '__root'); }}>
                    <Icon name="folder" size={14} /> Новая папка
                  </button>
                </div>
              )}
            </div>
          )}
        </div>

        <div className="kb-main-toolbar">
          <div className="kb-main-search">
            <Icon name="search" size={14} />
            <input placeholder="Поиск по статьям…" value={localSearch} onChange={(e) => setLocalSearch(e.target.value)} />
          </div>
          <select className="kb-main-select" value={statusFilter} onChange={(e) => setStatusFilter(e.target.value)}>
            <option value="">Статус</option>
            <option value="published">Опубликовано</option>
            <option value="draft">Черновик</option>
            <option value="review">На ревью</option>
          </select>
          <select className="kb-main-select" value={tagFilter} onChange={(e) => setTagFilter(e.target.value)}>
            <option value="">Теги</option>
            {window.TAGS.map((t) => <option key={t.id} value={t.id}>{t.name}</option>)}
          </select>
          <select className="kb-main-select" value={sort} onChange={(e) => setSort(e.target.value)}>
            <option value="updated">Сначала новые</option>
            <option value="title">По алфавиту</option>
            <option value="views">По просмотрам</option>
          </select>
          <div className="view-toggle">
            <button type="button" className={viewMode === 'grid' ? 'active' : ''} onClick={() => setViewMode('grid')}>
              <Icon name="grid" size={12} />
            </button>
            <button type="button" className={viewMode === 'list' ? 'active' : ''} onClick={() => setViewMode('list')}>
              <Icon name="list" size={12} />
            </button>
          </div>
        </div>

        {specialView === 'trash' && (
          <div className="kb-trash-banner">
            <Icon name="info" size={16} />
            <span>Статьи хранятся {window.KB.TRASH_RETENTION_DAYS} дней. После этого удаляются автоматически. Восстановите или удалите навсегда через меню ⋮.</span>
          </div>
        )}

        {articles.length === 0 ? (
          <div className="kb-main-empty">
            <Icon name="file" size={32} />
            <p>{specialView === 'trash' ? 'Корзина пуста' : 'Нет статей'}</p>
            {!specialView && (
              <button type="button" className="btn btn-primary btn-sm" onClick={() => onCreate(selectedId)}>
                Создать статью
              </button>
            )}
          </div>
        ) : viewMode === 'grid' ? (
          <div className="kb-main-grid">
            {pinnedArticles.length > 0 && (
              <>
                <div className="kb-articles-group-label">Закреплённые</div>
                {pinnedArticles.map((a) => (
                  <KbGridCard key={a.id} article={a} onOpen={openArticle} onMenu={openCardMenu} inTrash={specialView === 'trash'} />
                ))}
              </>
            )}
            {restArticles.length > 0 && (
              <>
                {pinnedArticles.length > 0 && <div className="kb-articles-group-label">Остальные</div>}
                {restArticles.map((a) => (
                  <KbGridCard key={a.id} article={a} onOpen={openArticle} onMenu={openCardMenu} inTrash={specialView === 'trash'} />
                ))}
              </>
            )}
          </div>
        ) : (
          <div className="kb-main-list">
            {pinnedArticles.length > 0 && (
              <>
                <div className="kb-articles-group-label kb-articles-group-label--list">Закреплённые</div>
                {pinnedArticles.map((a) => {
                  const sectionLabel = window.getSectionLabel(a.section);
                  return (
                    <div key={a.id} className="kb-main-list-row-wrap">
                      <button type="button" className="kb-main-list-row" onClick={() => openArticle(a)}>
                        <Icon name="file" size={14} />
                        <span className="kb-main-list-main">
                          <span className="kb-main-list-title">{a.title}</span>
                          {sectionLabel !== '—' && (
                            <span className="kb-main-list-section">
                              <Icon name="folder" size={11} /> {sectionLabel}
                            </span>
                          )}
                        </span>
                        <ArticleMarkers article={a} size={15} />
                        <span className="kb-grid-card-pin" title="Закреплено"><Icon name="bookmark" size={12} /></span>
                        {!specialView && <ArticleCardStats article={a} />}
                        <span className={`kb-grid-card-status ${specialView === 'trash' ? 'kb-status--trash' : (STATUS_MAP[a.status]?.cls || '')}`}>
                          {specialView === 'trash' ? window.KB.formatTrashEta(a) : STATUS_MAP[a.status]?.label}
                        </span>
                      </button>
                      <button type="button" className="kb-main-list-more" onClick={(e) => openCardMenu(e, a)}>
                        <Icon name="more" size={14} />
                      </button>
                    </div>
                  );
                })}
              </>
            )}
            {restArticles.length > 0 && (
              <>
                {pinnedArticles.length > 0 && <div className="kb-articles-group-label kb-articles-group-label--list">Остальные</div>}
                {restArticles.map((a) => {
                  const sectionLabel = window.getSectionLabel(a.section);
                  return (
                    <div key={a.id} className="kb-main-list-row-wrap">
                      <button type="button" className="kb-main-list-row" onClick={() => openArticle(a)}>
                        <Icon name="file" size={14} />
                        <span className="kb-main-list-main">
                          <span className="kb-main-list-title">{a.title}</span>
                          {sectionLabel !== '—' && (
                            <span className="kb-main-list-section">
                              <Icon name="folder" size={11} /> {sectionLabel}
                            </span>
                          )}
                        </span>
                        <ArticleMarkers article={a} size={15} />
                        {!specialView && <ArticleCardStats article={a} />}
                        <span className={`kb-grid-card-status ${specialView === 'trash' ? 'kb-status--trash' : (STATUS_MAP[a.status]?.cls || '')}`}>
                          {specialView === 'trash' ? window.KB.formatTrashEta(a) : STATUS_MAP[a.status]?.label}
                        </span>
                      </button>
                      <button type="button" className="kb-main-list-more" onClick={(e) => openCardMenu(e, a)}>
                        <Icon name="more" size={14} />
                      </button>
                    </div>
                  );
                })}
              </>
            )}
          </div>
        )}
      </main>

      {!specialView && selectedSection && (
        <aside className="kb-layout-aside">
          <h2 className="kb-aside-title">О разделе</h2>
          <div className="kb-aside-folder">
            <span className={`kb-aside-folder-icon ${FOLDER_TONES[sectionPath.length % FOLDER_TONES.length]}`}>
              <Icon name="folder" size={20} />
            </span>
            <div>
              <div className="kb-aside-folder-name">{selectedSection.name}</div>
              <div className="kb-aside-folder-path">{sectionPath.map((s) => s.name).join(' / ')}</div>
            </div>
          </div>
          <p className="kb-aside-desc">{panelDesc}</p>

          <dl className="kb-aside-meta">
            <div><dt>Статей</dt><dd>{articles.length}</dd></div>
            <div><dt>Подпапок</dt><dd>{window.getChildSections(selectedId).length}</dd></div>
            <div><dt>Обновлён</dt><dd>{articles[0]?.updated || '—'}</dd></div>
          </dl>

          <div className="kb-aside-responsible">
            <span className="kb-aside-label">Ответственный</span>
            {(() => {
              const authorId = articles.find((a) => a.authorId || a.responsibles?.[0])?.authorId
                || articles.find((a) => a.responsibles?.[0])?.responsibles?.[0];
              const p = authorId ? window.findPerson(authorId) : null;
              return p ? (
                <div className="kb-aside-person">
                  <span className={`avatar ${p.av}`}>{p.name.split(' ').map((n) => n[0]).join('').slice(0, 2)}</span>
                  <span>{p.name}</span>
                </div>
              ) : <span className="kb-aside-empty">—</span>;
            })()}
          </div>

          <button type="button" className="btn btn-ghost kb-aside-edit" onClick={() => { setEditingId(selectedId); setForm({ name: selectedSection.name }); }}>
            <Icon name="edit" size={14} /> Редактировать раздел
          </button>

          <button
            type="button"
            className={`btn btn-ghost kb-aside-subscribe${window.KB.isSubscribed(selectedId) ? ' is-active' : ''}`}
            onClick={async () => {
              try {
                const r = await window.KB.toggleSectionSubscription(selectedId);
                showToast(r.subscribed ? 'Подписка на раздел оформлена' : 'Подписка отменена');
                bumpRevision();
              } catch (err) {
                await window.UI.alert({ title: 'Ошибка', message: err.message });
              }
            }}
          >
            <Icon name="bell" size={14} />
            {window.KB.isSubscribed(selectedId) ? 'Отписаться от обновлений' : 'Подписаться на обновления'}
          </button>

          <div className="kb-aside-access">
            <h3 className="kb-aside-subtitle">Доступ к разделу</h3>
            {sectionAccess && (sectionAccess.people.length > 0 || sectionAccess.departments.length > 0) ? (
              <ul className="kb-aside-access-summary">
                {sectionAccess.departments.map((did) => {
                  const dept = window.DEPARTMENTS?.find((d) => d.id === did);
                  return <li key={did}><Icon name="grid" size={12} /> {dept?.name || did}</li>;
                })}
                {sectionAccess.people.map((pid) => {
                  const p = window.findPerson(pid);
                  return p ? <li key={pid}><span className={`avatar ${p.av}`} style={{ width: 18, height: 18, fontSize: 8 }}>{p.name.split(' ').map((n) => n[0]).join('').slice(0, 2)}</span> {p.name}</li> : null;
                })}
              </ul>
            ) : (
              <p className="kb-aside-access-note">Доступ не настроен — видят все сотрудники</p>
            )}
            <button type="button" className="btn btn-primary btn-sm kb-aside-access-btn" onClick={() => setAccessModalOpen(true)}>
              Управление доступом
            </button>
          </div>
        </aside>
      )}

      {cardMenu && (
        <ArticleCardMenu
          article={cardMenu.article}
          top={cardMenu.top}
          left={cardMenu.left}
          onClose={() => setCardMenu(null)}
          onEdit={onEdit}
          onAction={handleArticleAction}
          inTrash={specialView === 'trash'}
        />
      )}

      <TagsModal open={tagsModalOpen} onClose={() => setTagsModalOpen(false)} onChange={refreshArticles} />
      <AccessModal
        open={accessModalOpen}
        section={selectedSection}
        onClose={() => setAccessModalOpen(false)}
        onSaved={() => bumpRevision()}
      />

      {toast && (
        <div className="kb-toast" role="status">
          <Icon name="check" size={16} />
          {toast}
        </div>
      )}
    </div>
  );
};

window.KbStructureView = KbStructureView;
