// org.jsx — Организационная структура (интерактивная схема)

const ORG_ZOOM_MIN = 0.35;
const ORG_ZOOM_MAX = 1.75;
const ORG_ZOOM_STEP = 0.05;
const ORG_WHEEL_ZOOM = 0.0012;

const { SettingsModal, PersonForm, DepartmentForm, PositionForm } = window.WorkspaceForms;

const clampZoom = (v) => Math.min(ORG_ZOOM_MAX, Math.max(ORG_ZOOM_MIN, v));

const orgAccessLevel = (person, sectionId) => {
  if (!person || !sectionId) return 'none';
  if (person.isAdmin || person.canManageKb) return 'full';

  const path = window.getSectionPath?.(sectionId) || [];
  for (const s of path) {
    const grant = window.KB.getSectionAccess(s.id);
    const restricted = Boolean(grant.people?.length || grant.departments?.length);
    if (!restricted) continue;
    const allowed = grant.people.includes(person.id)
      || (person.departmentId && grant.departments.includes(person.departmentId));
    if (!allowed) return 'none';
  }
  return 'read';
};

const orgAccessLabel = (level) => {
  if (level === 'full') return 'Полный доступ';
  if (level === 'read') return 'Только чтение';
  return 'Нет доступа';
};

const orgMatchesSearch = (person, query) => {
  if (!query) return true;
  const q = query.toLowerCase();
  return (
    person.name.toLowerCase().includes(q)
    || (person.role || '').toLowerCase().includes(q)
    || (person.email || '').toLowerCase().includes(q)
    || (person.dept || '').toLowerCase().includes(q)
  );
};

const OrgCard = ({
  person,
  vacant,
  positionId,
  dept,
  deptColor,
  roleTitle,
  editMode,
  selected,
  dimmed,
  draggable,
  isDragging,
  isDropTarget,
  isInvalidDrop,
  isManager,
  onSelect,
  onSelectVacant,
  onDragStart,
  onDragEnd,
  onDragOver,
  onDragLeave,
  onDrop,
  onDragOverVacant,
  onDropOnVacant,
  onAddSubordinate,
  onAssignVacant,
}) => {
  const initials = person
    ? person.name.split(' ').map((s) => s[0]).join('').slice(0, 2)
    : '+';

  return (
    <div
      className={[
        'org-v2-card',
        vacant && 'org-v2-card--vacant',
        editMode && 'org-v2-card--editable',
        selected && 'org-v2-card--selected',
        dimmed && 'org-v2-card--dimmed',
        isDragging && 'org-v2-card--dragging',
        isDropTarget && 'org-v2-card--drop-target',
        isInvalidDrop && 'org-v2-card--drop-invalid',
      ].filter(Boolean).join(' ')}
      draggable={draggable}
      onDragStart={onDragStart}
      onDragEnd={onDragEnd}
      onDragOver={onDragOver}
      onDragLeave={onDragLeave}
      onDrop={onDrop}
      onPointerDown={(e) => e.stopPropagation()}
      onClick={(e) => {
        e.stopPropagation();
        if (vacant) onSelectVacant?.(positionId);
        else if (person) onSelect?.(person.id);
      }}
      role="button"
      tabIndex={0}
      onKeyDown={(e) => {
        if (e.key === 'Enter' || e.key === ' ') {
          e.preventDefault();
          if (vacant) onSelectVacant?.(positionId);
          else if (person) onSelect?.(person.id);
        }
      }}
    >
      {dept && (
        <div className={`org-v2-card-dept ${deptColor || 'dept-blue'}`}>{dept}</div>
      )}
      <div className="org-v2-card-main">
        <div className="org-v2-card-text">
          <div className="org-v2-card-role">{roleTitle || person?.role || 'Должность'}</div>
          {vacant ? (
            <div className="org-v2-card-vacant-label">Вакантно</div>
          ) : (
            <>
              <div className="org-v2-card-name">{person.name}</div>
              <div className="org-v2-card-email">{person.email || '—'}</div>
            </>
          )}
        </div>
        {!vacant && (
          <span className={`org-v2-card-avatar avatar avatar-lg ${person.av}`}>{initials}</span>
        )}
      </div>
      <div className="org-v2-card-footer">
        {isManager && !vacant && (
          <span className="org-v2-card-star" title="Руководитель">
            <Icon name="star" size={12} />
          </span>
        )}
        {editMode && vacant && (
          <button
            type="button"
            className="org-v2-card-add"
            title="Назначить сотрудника"
            onClick={(e) => {
              e.stopPropagation();
              onAssignVacant?.(positionId);
            }}
          >
            <Icon name="plus" size={14} />
          </button>
        )}
        {editMode && person && (
          <button
            type="button"
            className="org-v2-card-add"
            title="Добавить подчинённого"
            onClick={(e) => {
              e.stopPropagation();
              onAddSubordinate?.(person.id);
            }}
          >
            <Icon name="plus" size={14} />
          </button>
        )}
      </div>
    </div>
  );
};

const OrgChartItem = ({
  node,
  editMode,
  selectedId,
  selectedVacantId,
  searchQuery,
  draggingId,
  dropTargetId,
  dropVacantId,
  invalidDropId,
  onSelect,
  onSelectVacant,
  onDragStart,
  onDragEnd,
  onDragOverCard,
  onDragLeaveCard,
  onDropOnCard,
  onDragOverVacant,
  onDragLeaveVacant,
  onDropOnVacant,
  onAddSubordinate,
  onAssignVacant,
}) => {
  const person = node.personId ? window.findPerson(node.personId) : null;
  const vacant = node.type === 'vacant';
  const hasChildren = node.children?.length > 0;
  const dimmed = searchQuery && person && !orgMatchesSearch(person, searchQuery);

  return (
    <li className="org-v2-chart-item">
      <OrgCard
        person={person}
        vacant={vacant}
        positionId={node.positionId}
        dept={node.dept}
        deptColor={node.deptColor}
        roleTitle={node.roleTitle}
        editMode={editMode}
        selected={vacant ? selectedVacantId === node.positionId : selectedId === node.personId}
        dimmed={dimmed}
        draggable={editMode && Boolean(person)}
        isDragging={draggingId === node.personId}
        isDropTarget={vacant ? dropVacantId === node.positionId : dropTargetId === node.personId}
        isInvalidDrop={!vacant && invalidDropId === node.personId}
        isManager={hasChildren}
        onSelect={onSelect}
        onSelectVacant={onSelectVacant}
        onDragStart={(e) => person && onDragStart(e, node.personId)}
        onDragEnd={onDragEnd}
        onDragOver={(e) => {
          if (vacant && editMode) onDragOverVacant?.(e, node.positionId);
          else if (person) onDragOverCard(e, node.personId);
        }}
        onDragLeave={(e) => {
          if (vacant) onDragLeaveVacant?.(e, node.positionId);
          else if (person) onDragLeaveCard(e, node.personId);
        }}
        onDrop={(e) => {
          if (vacant) onDropOnVacant?.(e, node.positionId);
          else if (person) onDropOnCard(e, node.personId);
        }}
        onAddSubordinate={onAddSubordinate}
        onAssignVacant={onAssignVacant}
      />
      {hasChildren && (
        <ul className="org-v2-chart">
          {node.children.map((child) => (
            <OrgChartItem
              key={child.key}
              node={child}
              editMode={editMode}
              selectedId={selectedId}
              selectedVacantId={selectedVacantId}
              searchQuery={searchQuery}
              draggingId={draggingId}
              dropTargetId={dropTargetId}
              dropVacantId={dropVacantId}
              invalidDropId={invalidDropId}
              onSelect={onSelect}
              onSelectVacant={onSelectVacant}
              onDragStart={onDragStart}
              onDragEnd={onDragEnd}
              onDragOverCard={onDragOverCard}
              onDragLeaveCard={onDragLeaveCard}
              onDropOnCard={onDropOnCard}
              onDragOverVacant={onDragOverVacant}
              onDragLeaveVacant={onDragLeaveVacant}
              onDropOnVacant={onDropOnVacant}
              onAddSubordinate={onAddSubordinate}
              onAssignVacant={onAssignVacant}
            />
          ))}
        </ul>
      )}
    </li>
  );
};

const buildOrgNodes = () => {
  const { roots } = window.buildOrgTreeStrict();
  const buildNode = (n) => ({
    key: n.id,
    type: 'person',
    personId: n.id,
    dept: n.dept,
    deptColor: n.deptColor,
    roleTitle: window.findPerson(n.id)?.role,
    children: (n.children || []).map(buildNode),
  });
  return roots.map(buildNode);
};

const OrgUnassignedDock = ({
  unassigned,
  editMode,
  onAssignPerson,
  onAddPerson,
  onDragStart,
  onClose,
}) => {
  if (!editMode || !unassigned.length) return null;

  return (
    <aside className="org-v2-dock">
      <div className="org-v2-dock-head">
        <div>
          <strong>Не на схеме</strong>
          <span className="org-v2-dock-count">{unassigned.length}</span>
        </div>
        <button type="button" className="topbar-icon-btn" onClick={onClose} aria-label="Скрыть">
          <Icon name="x" size={14} />
        </button>
      </div>
      <p className="org-v2-dock-hint">
        Назначьте отдел и руководителя — или перетащите на карточку на схеме.
      </p>
      <div className="org-v2-dock-list">
        {unassigned.map((p) => (
          <div
            key={p.id}
            className="org-v2-dock-row"
            draggable
            onDragStart={(e) => onDragStart(e, p.id)}
          >
            <span className={`avatar avatar-sm ${p.av}`}>
              {p.name.split(' ').map((n) => n[0]).join('').slice(0, 2)}
            </span>
            <div className="org-v2-dock-row-main">
              <span className="org-v2-dock-row-name">{p.name}</span>
              <span className="org-v2-dock-row-sub">{p.role || 'Без должности'}</span>
            </div>
            <button type="button" className="org-v2-dock-row-btn" onClick={() => onAssignPerson(p.id)}>
              Назначить
            </button>
          </div>
        ))}
      </div>
      <button type="button" className="org-v2-dock-add btn btn-primary btn-sm" onClick={onAddPerson}>
        <Icon name="plus" size={14} /> Новый сотрудник
      </button>
    </aside>
  );
};

const VacantAssignModal = ({
  positionId,
  unassignedPeople,
  saving,
  onClose,
  onAssign,
  onCreateNew,
}) => {
  const pos = window.findPosition(positionId);
  const dept = window.findDepartment(pos?.departmentId);

  return (
    <div className="settings-modal-backdrop" onClick={onClose}>
      <div className="settings-modal" onClick={(e) => e.stopPropagation()}>
        <div className="settings-modal-head">
          <h3>Назначить на должность</h3>
          <button type="button" className="topbar-icon-btn" onClick={onClose} aria-label="Закрыть">×</button>
        </div>
        <div className="settings-modal-body">
          <div className="settings-info-box">
            <strong>{pos?.name || 'Должность'}</strong>
            {(dept?.name || pos?.departmentName) && (
              <span className="text-xs text-3"> · {dept?.name || pos?.departmentName}</span>
            )}
          </div>
          {unassignedPeople.length > 0 ? (
            <>
              <p className="org-v2-dock-hint" style={{ marginTop: 12 }}>
                Выберите сотрудника без места на схеме или создайте нового.
              </p>
              <div className="org-v2-pick-list">
                {unassignedPeople.map((p) => (
                  <button
                    key={p.id}
                    type="button"
                    className="org-v2-pick-row"
                    disabled={saving}
                    onClick={() => onAssign(p.id)}
                  >
                    <span className="org-v2-dock-row-name">{p.name}</span>
                    <span className="text-xs text-3">{p.role || 'Без должности'}</span>
                  </button>
                ))}
              </div>
            </>
          ) : (
            <p className="org-v2-dock-hint" style={{ marginTop: 12 }}>
              Нет свободных сотрудников — создайте нового для этой должности.
            </p>
          )}
          <div className="settings-form-actions" style={{ marginTop: 16 }}>
            <button type="button" className="btn btn-ghost" onClick={onClose} disabled={saving}>
              Отмена
            </button>
            <button type="button" className="btn btn-primary" onClick={onCreateNew} disabled={saving}>
              + Новый сотрудник
            </button>
          </div>
        </div>
      </div>
    </div>
  );
};

const PositionPickModal = ({ options, personName, onPick, onClose, title, hint }) => (
  <div className="settings-modal-backdrop" onClick={onClose}>
    <div className="settings-modal" onClick={(e) => e.stopPropagation()}>
      <div className="settings-modal-head">
        <h3>{title || `Выберите должность для ${personName}`}</h3>
        <button type="button" className="topbar-icon-btn" onClick={onClose} aria-label="Закрыть">×</button>
      </div>
      <div className="settings-modal-body">
        <p className="org-access-modal-hint">
          {hint || 'У руководителя несколько вакантных подчинённых должностей.'}
        </p>
        <div className="org-v2-pick-list">
          {options.map((pos) => (
            <button key={pos.id} type="button" className="org-v2-pick-row" onClick={() => onPick(pos.id)}>
              <span>{pos.name}</span>
              <span className="text-xs text-3">{pos.departmentName || '—'}</span>
            </button>
          ))}
        </div>
      </div>
    </div>
  </div>
);

const OrgAccessModal = ({ person, onClose, onSaved }) => {
  const sections = window.getRootSections().filter((s) => s.id !== 'all');
  const [draft, setDraft] = React.useState(() =>
    Object.fromEntries(sections.map((s) => [s.id, { ...window.KB.getSectionAccess(s.id) }])));
  const [saving, setSaving] = React.useState(false);

  const togglePerson = (sectionId) => {
    setDraft((prev) => {
      const grant = { ...prev[sectionId], people: [...(prev[sectionId].people || [])] };
      grant.people = grant.people.includes(person.id)
        ? grant.people.filter((id) => id !== person.id)
        : [...grant.people, person.id];
      return { ...prev, [sectionId]: grant };
    });
  };

  const handleSave = async () => {
    setSaving(true);
    try {
      for (const s of sections) {
        await window.KB.saveSectionAccess(s.id, draft[s.id]);
      }
      onSaved?.();
      onClose();
    } catch (err) {
      await window.UI.alert({ title: 'Ошибка', message: err.message });
    } finally {
      setSaving(false);
    }
  };

  return (
    <div className="settings-modal-backdrop" onClick={onClose}>
      <div className="settings-modal org-access-modal" onClick={(e) => e.stopPropagation()}>
        <div className="settings-modal-head">
          <h3>Права доступа — {person.name}</h3>
          <button type="button" className="topbar-icon-btn" onClick={onClose} aria-label="Закрыть">×</button>
        </div>
        <div className="settings-modal-body">
          <p className="org-access-modal-hint">
            Отметьте разделы, к которым сотрудник имеет доступ при ограниченной видимости папки.
          </p>
          <div className="org-access-modal-list">
            {sections.map((s) => {
              const grant = draft[s.id];
              const restricted = Boolean(grant.people?.length || grant.departments?.length);
              const checked = grant.people.includes(person.id);
              return (
                <label key={s.id} className="org-access-modal-row">
                  <input
                    type="checkbox"
                    checked={checked}
                    onChange={() => togglePerson(s.id)}
                  />
                  <span className="org-access-modal-row-main">
                    <span>{s.name}</span>
                    <span className="org-access-modal-row-sub">
                      {restricted ? 'Ограниченный раздел' : 'Открытый раздел — доступ у всех'}
                    </span>
                  </span>
                </label>
              );
            })}
          </div>
          <div className="settings-form-actions">
            <button type="button" className="btn btn-ghost" onClick={onClose}>Отмена</button>
            <button type="button" className="btn btn-primary" disabled={saving} onClick={handleSave}>
              {saving ? 'Сохранение…' : 'Сохранить'}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
};

const OrgDetailPanel = ({
  personId,
  editMode,
  isAdmin,
  onClose,
  onEditManager,
  onEditPerson,
  onOpenSettings,
  onAddSubordinate,
}) => {
  const person = window.findPerson(personId);
  const [accessOpen, setAccessOpen] = React.useState(false);

  if (!person) return null;

  const dept = window.findDepartment(person.departmentId)
    || (window.DEPARTMENTS || []).find((d) => d.name === person.dept);
  const subordinates = window.getPositionSubordinates(person.id);
  const kbSections = window.getRootSections().filter((s) => s.id !== 'all');

  return (
    <>
      <aside className="org-v2-panel">
        <div className="org-v2-panel-head">
          <div>
            <div className="org-v2-panel-kicker">Должность</div>
            <h2 className="org-v2-panel-title">{person.role || '—'}</h2>
          </div>
          <button type="button" className="topbar-icon-btn" onClick={onClose} aria-label="Закрыть">
            <Icon name="x" size={16} />
          </button>
        </div>

        <div className="org-v2-panel-profile">
          {dept && <span className={`org-v2-panel-dept ${dept.color}`}>{dept.name}</span>}
          <div className="org-v2-panel-person">
            <span className={`avatar avatar-lg ${person.av}`}>
              {person.name.split(' ').map((n) => n[0]).join('').slice(0, 2)}
            </span>
            <div>
              <div className="org-v2-panel-name">{person.name}</div>
              <div className="org-v2-panel-email">{person.email || '—'}</div>
            </div>
          </div>
          <div className="org-v2-panel-meta">
            <span>{person.workspaceRoleName || 'Сотрудник'}</span>
            {isAdmin && (
              <button type="button" className="org-v2-panel-link" onClick={() => onEditPerson?.(person.id)}>
                Редактировать
              </button>
            )}
          </div>
        </div>

        <section className="org-v2-panel-section">
          <div className="org-v2-panel-section-head">
            <h3>Сотрудники в подчинении ({subordinates.length})</h3>
            {editMode && isAdmin && (
              <button type="button" className="org-v2-panel-link" onClick={() => onAddSubordinate?.(person.id)}>
                + Добавить
              </button>
            )}
          </div>
          {subordinates.length === 0 ? (
            <p className="org-v2-panel-empty">Нет подчинённых</p>
          ) : (
            <div className="org-v2-panel-list">
              {subordinates.map((s) => (
                <div key={s.id} className="org-v2-panel-list-item">
                  <span className={`avatar ${s.av}`}>
                    {s.name.split(' ').map((n) => n[0]).join('').slice(0, 2)}
                  </span>
                  <div>
                    <div className="org-v2-panel-list-name">{s.name}</div>
                    <div className="org-v2-panel-list-sub">{s.role || '—'}</div>
                  </div>
                </div>
              ))}
            </div>
          )}
        </section>

        <section className="org-v2-panel-section">
          <h3>Права доступа к разделам базы знаний</h3>
          <div className="org-v2-access-list">
            {kbSections.map((s) => {
              const level = orgAccessLevel(person, s.id);
              return (
                <div key={s.id} className="org-v2-access-row">
                  <span className="org-v2-access-name">{s.name}</span>
                  <span className={`org-v2-access-badge org-v2-access-badge--${level}`}>
                    {orgAccessLabel(level)}
                  </span>
                </div>
              );
            })}
          </div>
          {isAdmin && (
            <button type="button" className="org-v2-panel-access-btn" onClick={() => setAccessOpen(true)}>
              <Icon name="lock" size={14} />
              Редактировать права
            </button>
          )}
        </section>

        {editMode && isAdmin && (
          <div className="org-v2-panel-actions">
            <button type="button" className="btn btn-ghost btn-sm" onClick={() => onEditManager?.(person.id)}>
              Изменить руководителя
            </button>
          </div>
        )}
      </aside>

      {accessOpen && (
        <OrgAccessModal
          person={person}
          onClose={() => setAccessOpen(false)}
          onSaved={() => {}}
        />
      )}
    </>
  );
};

const OrgPersonEditor = ({ personId, onClose, onSave, saving }) => {
  const person = window.findPerson(personId);
  const [managerId, setManagerId] = React.useState(person?.managerId || '');

  React.useEffect(() => {
    setManagerId(person?.managerId || '');
  }, [personId, person?.managerId]);

  if (!person) return null;

  const candidates = window.getActivePeople().filter((p) => {
    if (p.id === person.id) return false;
    return !window.wouldCreateManagerCycle(person.id, p.id);
  });

  return (
    <div className="settings-modal-backdrop" onClick={onClose}>
      <div className="settings-modal" onClick={(e) => e.stopPropagation()}>
        <div className="settings-modal-head">
          <h3>Руководитель — {person.name}</h3>
          <button type="button" className="topbar-icon-btn" onClick={onClose} aria-label="Закрыть">×</button>
        </div>
        <div className="settings-modal-body">
          <label className="settings-field">
            <span>Кому подчиняется</span>
            <select value={managerId} onChange={(e) => setManagerId(e.target.value)}>
              <option value="">— верхний уровень (без руководителя) —</option>
              {candidates.map((p) => (
                <option key={p.id} value={p.id}>{p.name} · {p.role || '—'}</option>
              ))}
            </select>
          </label>
          <div className="settings-form-actions">
            <button type="button" className="btn btn-ghost" onClick={onClose}>Отмена</button>
            <button
              type="button"
              className="btn btn-primary"
              disabled={saving}
              onClick={() => onSave(person.id, managerId || null)}
            >
              {saving ? 'Сохранение…' : 'Сохранить'}
            </button>
          </div>
        </div>
      </div>
    </div>
  );
};

const OrgView = ({ onOpenSettings, onNavigateKb, refreshKey, onRefresh }) => {
  const [editMode, setEditMode] = React.useState(false);
  const [treeKey, setTreeKey] = React.useState(0);
  const [selectedId, setSelectedId] = React.useState(null);
  const [selectedVacantId, setSelectedVacantId] = React.useState(null);
  const [searchQuery, setSearchQuery] = React.useState('');
  const [scale, setScale] = React.useState(1);
  const [pan, setPan] = React.useState({ x: 0, y: 40 });
  const [isPanning, setIsPanning] = React.useState(false);
  const [draggingId, setDraggingId] = React.useState(null);
  const [dropTargetId, setDropTargetId] = React.useState(null);
  const [dropVacantId, setDropVacantId] = React.useState(null);
  const [invalidDropId, setInvalidDropId] = React.useState(null);
  const [rootDropActive, setRootDropActive] = React.useState(false);
  const [editorPersonId, setEditorPersonId] = React.useState(null);
  const [modal, setModal] = React.useState(null);
  const [positionPick, setPositionPick] = React.useState(null);
  const [saving, setSaving] = React.useState(false);
  const [isFullscreen, setIsFullscreen] = React.useState(false);
  const [dockOpen, setDockOpen] = React.useState(true);

  const canvasRef = React.useRef(null);
  const chartRef = React.useRef(null);
  const panOrigin = React.useRef(null);

  React.useEffect(() => {
    setTreeKey((k) => k + 1);
  }, [refreshKey]);

  React.useEffect(() => {
    const onFs = () => setIsFullscreen(Boolean(document.fullscreenElement));
    document.addEventListener('fullscreenchange', onFs);
    return () => document.removeEventListener('fullscreenchange', onFs);
  }, []);

  React.useEffect(() => {
    if (!modal) return;
    setIsPanning(false);
    panOrigin.current = null;
  }, [modal]);

  const isAdmin = window.KB?.isAdmin?.();
  const roots = React.useMemo(() => buildOrgNodes(), [treeKey, editMode]);
  const unassignedPeople = React.useMemo(() => window.KB.getUnassignedPeople(), [treeKey]);

  const reloadOrg = async () => {
    await window.KB.reloadWorkspace();
    setTreeKey((k) => k + 1);
    onRefresh?.();
  };

  React.useEffect(() => {
    if (!isAdmin) return;
    let cancelled = false;
    (async () => {
      const orphans = await window.KB.repairOrgOrphans();
      if (!cancelled && orphans > 0) {
        await window.KB.reloadWorkspace();
        if (!cancelled) setTreeKey((k) => k + 1);
      }
    })();
    return () => { cancelled = true; };
  }, [isAdmin]);

  const openAssignPerson = (personId) => {
    const person = window.findPerson(personId);
    setModal({ type: 'person', data: person || { id: personId } });
  };

  const openAssignVacant = () => {};

  const openCreatePersonForVacant = () => {};

  const openAddSubordinate = (managerPersonId) => {
    const manager = window.findPerson(managerPersonId);
    const viewerRole = (window.WORKSPACE_ROLES || []).find((r) => r.slug === 'viewer');
    setModal({
      type: 'person',
      data: {
        departmentId: manager?.departmentId || '',
        managerId: managerPersonId,
        workspaceRoleId: viewerRole?.id || '',
        lockManagerId: true,
      },
    });
  };

  const assignToPosition = async (personId, positionId) => {
    setSaving(true);
    try {
      await window.KB.assignPersonToPosition(personId, positionId);
      await reloadOrg();
    } catch (err) {
      await window.UI.alert({ title: 'Ошибка', message: err.message || 'Не удалось назначить' });
    } finally {
      setSaving(false);
    }
  };

  const reassignSubordinate = async (personId, managerPersonId) => {
    setSaving(true);
    try {
      await window.KB.reassignPersonSubordinate(personId, managerPersonId);
      await reloadOrg();
    } catch (err) {
      await window.UI.alert({ title: 'Ошибка', message: err.message });
    } finally {
      setSaving(false);
    }
  };

  const assignManager = async (personId, managerId) => {
    if (managerId && window.wouldCreateManagerCycle(personId, managerId)) {
      await window.UI.alert({
        title: 'Невозможно переместить',
        message: 'Нельзя назначить подчинённого руководителем.',
      });
      return;
    }
    setSaving(true);
    try {
      const person = window.findPerson(personId);
      await window.KB.savePerson({
        ...person,
        managerId: managerId || null,
        departmentId: person.departmentId,
        role: person.role,
        workspaceRoleId: person.workspaceRoleId,
      });
      await reloadOrg();
    } catch (err) {
      await window.UI.alert({ title: 'Ошибка', message: err.message });
    } finally {
      setSaving(false);
    }
  };

  const assignToRootLevel = async (personId) => {
    setSaving(true);
    try {
      const person = window.findPerson(personId);
      await window.KB.savePerson({
        ...person,
        managerId: null,
        departmentId: person.departmentId,
        role: person.role,
        workspaceRoleId: person.workspaceRoleId,
      });
      await reloadOrg();
    } catch (err) {
      await window.UI.alert({ title: 'Ошибка', message: err.message });
    } finally {
      setSaving(false);
    }
  };

  const handleSavePerson = async (person) => {
    setSaving(true);
    try {
      await window.KB.savePerson({
        id: person.id,
        name: person.name,
        email: person.email,
        phone: person.phone,
        av: person.av,
        status: person.status,
        isActive: person.isActive,
        role: person.role,
        departmentId: person.departmentId || null,
        managerId: person.managerId || null,
        workspaceRoleId: person.workspaceRoleId || null,
      });
      await reloadOrg();
      setModal(null);
      setSelectedVacantId(null);
    } catch (err) {
      await window.UI.alert({ title: 'Ошибка', message: err.message || 'Не удалось сохранить' });
    } finally {
      setSaving(false);
    }
  };

  const handleSaveDepartment = async (dept) => {
    setSaving(true);
    try {
      await window.KB.saveDepartment(dept);
      await reloadOrg();
      setModal(null);
    } catch (err) {
      await window.UI.alert({ title: 'Ошибка', message: err.message });
    } finally {
      setSaving(false);
    }
  };

  const handleSavePosition = async (position) => {
    setSaving(true);
    try {
      const saved = await window.KB.savePosition(position);
      const chain = modal?.chain;
      if (chain?.kind === 'person' && chain.personId) {
        await window.KB.assignPersonToPosition(chain.personId, saved.id);
      }
      await reloadOrg();
      if (chain?.kind === 'person' && !chain.personId) {
        setModal({
          type: 'person',
          data: {
            managerId: chain.managerId,
            positionId: saved.id,
          },
        });
      } else {
        setModal(null);
      }
    } catch (err) {
      await window.UI.alert({ title: 'Ошибка', message: err.message || 'Не удалось сохранить должность' });
      throw err;
    } finally {
      setSaving(false);
    }
  };

  const clearDragState = () => {
    setDraggingId(null);
    setDropTargetId(null);
    setDropVacantId(null);
    setInvalidDropId(null);
    setRootDropActive(false);
  };

  const handleDragStart = (e, personId) => {
    e.dataTransfer.effectAllowed = 'move';
    e.dataTransfer.setData('text/person-id', personId);
    setDraggingId(personId);
  };

  const handleDragEnd = () => clearDragState();

  const canDropOn = (targetId) => {
    if (!draggingId || draggingId === targetId) return false;
    return !window.wouldCreateManagerCycle(draggingId, targetId);
  };

  const handleDragOverCard = (e, targetId) => {
    if (!editMode || !draggingId) return;
    e.preventDefault();
    e.stopPropagation();
    e.dataTransfer.dropEffect = canDropOn(targetId) ? 'move' : 'none';
    setDropTargetId(targetId);
    setInvalidDropId(canDropOn(targetId) ? null : targetId);
    setRootDropActive(false);
  };

  const handleDragLeaveCard = (e, targetId) => {
    if (dropTargetId === targetId) {
      setDropTargetId(null);
      setInvalidDropId(null);
    }
  };

  const handleDropOnCard = async (e, targetId) => {
    e.preventDefault();
    e.stopPropagation();
    const personId = e.dataTransfer.getData('text/person-id') || draggingId;
    clearDragState();
    if (!personId || !canDropOn(targetId)) return;
    await reassignSubordinate(personId, targetId);
  };

  const handleDragOverVacant = (e, positionId) => {
    if (!editMode || !draggingId) return;
    e.preventDefault();
    e.stopPropagation();
    e.dataTransfer.dropEffect = 'move';
    setDropVacantId(positionId);
    setDropTargetId(null);
    setInvalidDropId(null);
    setRootDropActive(false);
  };

  const handleDragLeaveVacant = (e, positionId) => {
    if (dropVacantId === positionId) setDropVacantId(null);
  };

  const handleDropOnVacant = async (e, positionId) => {
    e.preventDefault();
    e.stopPropagation();
    const personId = e.dataTransfer.getData('text/person-id') || draggingId;
    clearDragState();
    if (!personId || !positionId) return;
    await assignToPosition(personId, positionId);
  };

  const handleSelectVacant = (positionId) => {
    if (!isAdmin) return;
    openAssignVacant(positionId);
  };

  const handleDragOverRoot = (e) => {
    if (!editMode || !draggingId) return;
    e.preventDefault();
    setRootDropActive(true);
    setDropTargetId(null);
    setInvalidDropId(null);
  };

  const handleDropOnRoot = async (e) => {
    e.preventDefault();
    const personId = e.dataTransfer.getData('text/person-id') || draggingId;
    clearDragState();
    if (!personId) return;
    await assignManager(personId, null);
  };

  const handleCanvasPointerDown = (e) => {
    if (e.button !== 0 && e.button !== 1) return;
    const tag = e.target.closest('.org-v2-card, button, input, select, a');
    if (tag) return;
    panOrigin.current = { x: e.clientX - pan.x, y: e.clientY - pan.y, moved: false };
    setIsPanning(true);
    canvasRef.current?.setPointerCapture?.(e.pointerId);
  };

  const handleCanvasPointerMove = (e) => {
    if (!isPanning || !panOrigin.current) return;
    const nx = e.clientX - panOrigin.current.x;
    const ny = e.clientY - panOrigin.current.y;
    if (Math.abs(nx - pan.x) > 2 || Math.abs(ny - pan.y) > 2) panOrigin.current.moved = true;
    setPan({ x: nx, y: ny });
  };

  const handleCanvasPointerUp = (e) => {
    if (panOrigin.current?.moved === false) setSelectedId(null);
    setIsPanning(false);
    panOrigin.current = null;
    canvasRef.current?.releasePointerCapture?.(e.pointerId);
  };

  const handleWheel = (e) => {
    e.preventDefault();
    const delta = -e.deltaY * ORG_WHEEL_ZOOM;
    if (Math.abs(delta) < 0.001) return;
    setScale((s) => clampZoom(s + delta));
  };

  const fitWidth = () => {
    const wrap = canvasRef.current;
    const chart = chartRef.current;
    if (!wrap || !chart) return;
    const padding = 80;
    const nextScale = clampZoom((wrap.clientWidth - padding) / chart.offsetWidth);
    setScale(nextScale);
    setPan({ x: (wrap.clientWidth - chart.offsetWidth * nextScale) / 2, y: 40 });
  };

  React.useEffect(() => {
    if (roots.length) {
      const t = setTimeout(fitWidth, 60);
      return () => clearTimeout(t);
    }
  }, [treeKey, roots.length]);

  const toggleFullscreen = async () => {
    const el = canvasRef.current?.closest('.org-v2-layout');
    if (!el) return;
    if (!document.fullscreenElement) await el.requestFullscreen?.();
    else await document.exitFullscreen?.();
  };

  return (
    <div className="org-v2-layout">
      <header className="org-v2-header">
        <div className="org-v2-header-left">
          <button type="button" className="org-v2-back" onClick={() => onNavigateKb?.()}>
            <Icon name="arrowLeft" size={16} />
            База знаний
          </button>
          <div className="org-v2-title-block">
            <h1 className="org-v2-title">Организационная структура</h1>
            <p className="org-v2-subtitle">Иерархия отделов и должностей компании</p>
          </div>
        </div>

        <div className="org-v2-search">
          <Icon name="search" size={16} />
          <input
            type="search"
            placeholder="Поиск по должностям и сотрудникам…"
            value={searchQuery}
            onChange={(e) => setSearchQuery(e.target.value)}
          />
        </div>

        <div className="org-v2-header-actions">
          <div className="org-v2-mode-toggle">
            <button
              type="button"
              className={!editMode ? 'active' : ''}
              onClick={() => setEditMode(false)}
            >
              Просмотр
            </button>
            <button
              type="button"
              className={editMode ? 'active' : ''}
              onClick={() => {
                if (!isAdmin) return;
                setEditMode(true);
                setDockOpen(true);
              }}
              disabled={!isAdmin}
            >
              Редактирование
            </button>
          </div>

          <div className="org-v2-zoom">
            <button type="button" className="org-v2-zoom-btn" onClick={() => setScale((s) => clampZoom(s - ORG_ZOOM_STEP))} aria-label="Уменьшить">
              <Icon name="minus" size={14} />
            </button>
            <span className="org-v2-zoom-label">{Math.round(scale * 100)}%</span>
            <button type="button" className="org-v2-zoom-btn" onClick={() => setScale((s) => clampZoom(s + ORG_ZOOM_STEP))} aria-label="Увеличить">
              <Icon name="plus" size={14} />
            </button>
            <button type="button" className="org-v2-zoom-text" onClick={fitWidth}>По ширине</button>
            <button type="button" className="org-v2-zoom-btn" onClick={toggleFullscreen} aria-label="На весь экран">
              <Icon name="maximize" size={14} />
            </button>
          </div>

          {editMode && isAdmin && (
            <div className="org-v2-create-actions">
              <button type="button" className="btn btn-ghost btn-sm" onClick={() => setModal({ type: 'department', data: null })}>
                <Icon name="plus" size={14} /> Отдел
              </button>
              <button type="button" className="btn btn-primary btn-sm" onClick={() => setModal({ type: 'person', data: null })}>
                <Icon name="plus" size={14} /> Сотрудник
              </button>
            </div>
          )}
        </div>
      </header>

      {editMode && isAdmin && (
        <div className="org-v2-edit-hint">
          <span>
            Перетащите сотрудника на карточку руководителя или в зону «Верхний уровень».
          </span>
          {unassignedPeople.length > 0 && (
            <button type="button" className="org-v2-dock-toggle" onClick={() => setDockOpen((v) => !v)}>
              Без отдела: {unassignedPeople.length}
            </button>
          )}
        </div>
      )}

      {!editMode && isAdmin && unassignedPeople.length > 0 && (
        <div className="org-v2-edit-hint">
          <span>{unassignedPeople.length} сотрудник(ов) не на схеме — включите редактирование, чтобы назначить.</span>
          <button type="button" className="btn btn-primary btn-sm" onClick={() => { setEditMode(true); setDockOpen(true); }}>
            Редактировать структуру
          </button>
        </div>
      )}

      <div className={`org-v2-body${selectedId ? ' org-v2-body--panel-open' : ''}${dockOpen && unassignedPeople.length ? ' org-v2-body--dock-open' : ''}`}>
        {editMode && dockOpen && unassignedPeople.length > 0 && (
          <OrgUnassignedDock
            unassigned={unassignedPeople}
            editMode={editMode}
            onAssignPerson={openAssignPerson}
            onAddPerson={() => setModal({ type: 'person', data: null })}
            onDragStart={handleDragStart}
            onClose={() => setDockOpen(false)}
          />
        )}
        <div
          ref={canvasRef}
          className={`org-v2-canvas${isPanning ? ' org-v2-canvas--panning' : ''}${isFullscreen ? ' org-v2-canvas--fullscreen' : ''}`}
          onPointerDown={handleCanvasPointerDown}
          onPointerMove={handleCanvasPointerMove}
          onPointerUp={handleCanvasPointerUp}
          onPointerLeave={handleCanvasPointerUp}
          onWheel={handleWheel}
        >
          {editMode && (
            <div
              className={`org-v2-root-drop${rootDropActive ? ' org-v2-root-drop--active' : ''}`}
              onDragOver={handleDragOverRoot}
              onDragLeave={() => setRootDropActive(false)}
              onDrop={handleDropOnRoot}
            >
              <Icon name="org" size={16} />
              Перетащите сюда для верхнего уровня (CEO / без руководителя)
            </div>
          )}

          <div
            className="org-v2-stage"
            style={{ transform: `translate(${pan.x}px, ${pan.y}px) scale(${scale})` }}
          >
            <div ref={chartRef} className="org-v2-chart-wrap">
              {roots.length === 0 ? (
                <div className="org-v2-empty">Нет данных для построения иерархии</div>
              ) : (
                <ul className="org-v2-chart org-v2-chart--root">
                  {roots.map((node) => (
                    <OrgChartItem
                      key={node.key}
                      node={node}
                      editMode={editMode}
                      selectedId={selectedId}
                      selectedVacantId={selectedVacantId}
                      searchQuery={searchQuery}
                      draggingId={draggingId}
                      dropTargetId={dropTargetId}
                      dropVacantId={dropVacantId}
                      invalidDropId={invalidDropId}
                      onSelect={setSelectedId}
                      onSelectVacant={handleSelectVacant}
                      onDragStart={handleDragStart}
                      onDragEnd={handleDragEnd}
                      onDragOverCard={handleDragOverCard}
                      onDragLeaveCard={handleDragLeaveCard}
                      onDropOnCard={handleDropOnCard}
                      onDragOverVacant={handleDragOverVacant}
                      onDragLeaveVacant={handleDragLeaveVacant}
                      onDropOnVacant={handleDropOnVacant}
                      onAddSubordinate={openAddSubordinate}
                      onAssignVacant={openAssignVacant}
                    />
                  ))}
                </ul>
              )}
            </div>
          </div>

          <div className="org-v2-minimap" aria-hidden="true">
            <div className="org-v2-minimap-inner" style={{ transform: `scale(${Math.min(0.12, 48 / (chartRef.current?.offsetWidth || 400))})` }}>
              <div className="org-v2-minimap-dot" />
            </div>
          </div>

          {editMode && saving && (
            <div className="org-v2-saving">Сохранение…</div>
          )}
        </div>

        {selectedId && (
          <OrgDetailPanel
            personId={selectedId}
            editMode={editMode}
            isAdmin={isAdmin}
            onClose={() => setSelectedId(null)}
            onEditManager={setEditorPersonId}
            onEditPerson={(id) => setModal({ type: 'person', data: window.findPerson(id) })}
            onOpenSettings={onOpenSettings}
            onAddSubordinate={openAddSubordinate}
          />
        )}
      </div>

      {modal?.type === 'vacant' && (
        <VacantAssignModal
          positionId={modal.data.positionId}
          unassignedPeople={unassignedPeople}
          saving={saving}
          onClose={() => { setModal(null); setSelectedVacantId(null); }}
          onAssign={async (personId) => {
            const { positionId } = modal.data;
            setModal(null);
            setSelectedVacantId(null);
            await assignToPosition(personId, positionId);
          }}
          onCreateNew={() => openCreatePersonForVacant(modal.data.positionId)}
        />
      )}
      {modal?.type === 'person' && (
        <SettingsModal
          title={
            modal.data?.id
              ? 'Редактировать сотрудника'
              : modal.data?.lockPositionId
                ? 'Новый сотрудник на должность'
                : 'Новый сотрудник'
          }
          onClose={() => { setModal(null); setSelectedVacantId(null); }}
          wide
        >
          <PersonForm
            initial={modal.data}
            saving={saving}
            onCancel={() => { setModal(null); setSelectedVacantId(null); }}
            onSave={handleSavePerson}
          />
        </SettingsModal>
      )}
      {modal?.type === 'department' && (
        <SettingsModal
          title={modal.data?.id ? 'Редактировать отдел' : 'Новый отдел'}
          onClose={() => setModal(null)}
        >
          <DepartmentForm
            initial={modal.data}
            saving={saving}
            onCancel={() => setModal(null)}
            onSave={handleSaveDepartment}
          />
        </SettingsModal>
      )}
      {modal?.type === 'position' && (
        <SettingsModal
          title={modal.data?.id ? 'Редактировать должность' : 'Новая должность'}
          onClose={() => setModal(null)}
          wide
        >
          <PositionForm
            initial={modal.data}
            saving={saving}
            onCancel={() => setModal(null)}
            onSave={handleSavePosition}
          />
        </SettingsModal>
      )}
      {positionPick && (
        <PositionPickModal
          options={positionPick.options}
          personName={positionPick.personName}
          title={
            positionPick.assignMode
              ? `Назначить: ${positionPick.personName}`
              : undefined
          }
          hint={
            positionPick.assignMode
              ? 'Выберите вакантную должность — сотрудник сразу появится на схеме.'
              : undefined
          }
          onClose={() => setPositionPick(null)}
          onPick={async (positionId) => {
            const { personId } = positionPick;
            setPositionPick(null);
            await assignToPosition(personId, positionId);
          }}
        />
      )}

      {editorPersonId && (
        <OrgPersonEditor
          personId={editorPersonId}
          saving={saving}
          onClose={() => setEditorPersonId(null)}
          onSave={async (id, mgr) => {
            await assignManager(id, mgr);
            setEditorPersonId(null);
          }}
        />
      )}
    </div>
  );
};

window.OrgView = OrgView;
