/* ctor-hierarchy.jsx — дерево Фаза → Этап → Задача → Подзадача (+ выбор узла для inspector, draft-бейджи) */
const { useState: hUseState, useEffect: hUseEffect, useLayoutEffect: hUseLayoutEffect } = React;
const hUid = window.ConstructorData.uid;

/* ---------- экономика узла (для inline-чипа в дереве) ---------- */
function hNodeClient(n) { const c = +n.cost || 0, m = n.margin == null ? 30 : +n.margin; return c + Math.round(c * m / 100); }
function hTaskClient(tk) { return (tk.sub && tk.sub.length) ? tk.sub.reduce((a, w) => a + hNodeClient(w), 0) : hNodeClient(tk); }
function hStageClient(st) { return (st.tasks || []).reduce((a, tk) => a + hTaskClient(tk), 0); }
function hRub(n) { return (n || 0).toLocaleString('ru-RU'); }
function MoneyChip({ value, strong, title }) {
  if (!value) return null;
  return (
    <span title={title || 'Цена для клиента'} className={'chip chip--money' + (strong ? ' chip--money-strong' : '')}>{hRub(value)} ₽</span>);
}

/* ---------- immutable helpers над каталогом ---------- */
function mapPhases(catalog, phaseId, fn) {
  return catalog.map((ph) => (ph.id === phaseId ? fn(ph) : ph));
}
function mapStages(ph, stageId, fn) {
  return { ...ph, stages: ph.stages.map((st) => (st.id === stageId ? fn(st) : st)) };
}
function mapTasks(st, taskId, fn) {
  return { ...st, tasks: st.tasks.map((tk) => (tk.id === taskId ? fn(tk) : tk)) };
}

/* маленький бейдж «изменено относительно опубликованного» */
function DraftDot({ on }) {
  if (!on) return null;
  return <span title="Не опубликовано" style={{ width: 7, height: 7, borderRadius: '50%', background: '#e8793a', flexShrink: 0, boxShadow: '0 0 0 3px #e8793a22' }}></span>;
}

/* бейдж BOM — материалы по умолчанию на узле (C-8), намеренно тихий */
function BomBadge({ n, onClick }) {
  if (!n) return null;
  return (
    <span onClick={onClick} title={n + ' материалов по умолчанию'}
      className={'chip chip--materials' + (onClick ? ' clickable' : '')}>{Icon.box} {n}</span>
  );
}

const HV = () => window.HierarchyView || {
  STORAGE_KEY: 'ctor_hierarchy_view',
  EXPAND_OPTIONS: [{ v: 'stage', l: 'Этапы' }, { v: 'task', l: 'Задачи' }, { v: 'sub', l: 'Подзадачи' }],
  FIELD_DEFS: [],
  defaultView: () => ({ expandDepth: 'task', fields: {} }),
  loadView: () => ({ expandDepth: 'task', fields: {} }),
  saveView: () => {},
  bumpField: (v) => v,
  opacityFor: () => 1,
  hiddenFor: () => false,
  visibleFor: () => true,
  expandToDepth: () => ({ collapsed: {}, expandedTasks: {} }),
};
const HVis = (props) => (window.HierarchyVis ? window.HierarchyVis(props) : props.children);
const ViewBar = () => window.HierarchyViewBar;

function normalizeHierarchyView(raw) {
  if (!raw) return HV().loadView();
  const defaults = HV().defaultView();
  return {
    expandDepth: ['stage', 'task', 'sub'].includes(raw.expandDepth) ? raw.expandDepth : defaults.expandDepth,
    fields: { ...defaults.fields, ...(raw.fields || {}) },
  };
}

function HierarchyPanel({ catalog, setCatalog, roles, density = 'comfortable', showSlugs, selected, onSelect, changedIds, noteCounts, notes, setNotes, inspectorNode, inspectorPatch, onInspectorClose, onTabSwitch, checklists, options, measures, bindings, setBindings, materialsLib, hierarchyView, setHierarchyView, applyExpandRef }) {
  const view = normalizeHierarchyView(hierarchyView);
  const setView = (next) => {
    if (setHierarchyView) setHierarchyView(next);
    else HV().saveView(next);
  };
  const [collapsed, setCollapsed] = hUseState({});       // phaseId/stageId → true
  const [expandedTasks, setExpandedTasks] = hUseState({}); // taskId → true (показать подзадачи)
  const [notePop, setNotePop] = hUseState(null);          // { anchorRect, nodeId, nodeName, nodePath, nodeColor }
  const [dragStage, setDragStage] = hUseState(null);     // { phaseId, stageId }
  const [dropStage, setDropStage] = hUseState(null);
  const [dragTask, setDragTask] = hUseState(null);       // { phaseId, stageId, taskId }
  const [dropTask, setDropTask] = hUseState(null);

  const compact = density === 'compact';
  const ch = changedIds || new Set();
  const nc = noteCounts || {};
  const bc = bindings || {};
  const toggle = (id) => setCollapsed((c) => ({ ...c, [id]: !c[id] }));
  const applyExpand = ({ collapsed: c, expandedTasks: e }) => {
    setCollapsed(c);
    setExpandedTasks(e);
  };
  hUseLayoutEffect(() => {
    if (applyExpandRef) applyExpandRef.current = applyExpand;
  });
  hUseEffect(() => {
    if (!catalog.length) return;
    applyExpand(HV().expandToDepth(catalog, view.expandDepth));
  }, [view.expandDepth, catalog.length]);
  const toggleTask = (id) => setExpandedTasks((c) => ({ ...c, [id]: !c[id] }));
  const sel = selected || {};
  const isSel = (id) => sel.id === id;
  const [inspectorOpenId, setInspectorOpenId] = hUseState(null);
  const [inspectorFocus, setInspectorFocus] = hUseState(null);
  const nodeInc = (n) => n.inc || (n.cond ? 'conditional' : 'required');
  const CD = window.ConstructorData;
  const usedBy = React.useMemo(() => (CD.buildUsedByIndex ? CD.buildUsedByIndex(catalog) : {}), [catalog]);
  const depBadges = (n) => {
    const inc = nodeInc(n);
    if (inc !== 'conditional') return null;
    return (
      <React.Fragment>
        <DepBadge deps={n.dependsOn} legacyCond={!n.dependsOn || !n.dependsOn.length ? n.cond : ''} />
        <UsedByBadge dependents={usedBy[n.id]} />
      </React.Fragment>
    );
  };
  const condBtnStyle = (active) => ({
    display: 'inline-flex', alignItems: 'center', gap: 5, padding: '3px 10px', borderRadius: 980,
    border: '1px solid ' + (active ? '#92620a' : '#e4ddd2'),
    background: active ? '#fffbeb' : 'transparent',
    color: active ? '#92620a' : '#8a817a',
    fontSize: 11.5, fontWeight: 700, fontFamily: 'inherit', cursor: 'pointer', whiteSpace: 'nowrap', flexShrink: 0,
  });
  const inspectorOpen = (id) => inspectorOpenId === id;
  const pick = (node) => {
    if (onSelect) onSelect(node);
    setInspectorOpenId(null);
  };
  const openInspector = (node, focus) => {
    if (onSelect) onSelect(node);
    setInspectorFocus(focus || null);
    setInspectorOpenId((cur) => (cur === node.id && !focus ? null : node.id));
  };
  const openCondition = (node) => openInspector(node, 'condition');
  const closeInspector = () => setInspectorOpenId(null);
  // Клик по строке — только выделение, без блока настроек
  const rowClick = (e, node) => {
    if (e.target.closest('button, input, textarea, select, [draggable="true"], [contenteditable="true"]')) return;
    pick(node);
  };
  const openNotePop = (e, info) => {
    e.stopPropagation();
    const rect = e.currentTarget.getBoundingClientRect();
    setNotePop({ anchorRect: rect, ...info });
  };

  hUseEffect(() => {
    if (!selected || !selected.stageId) return;
    if (view.expandDepth === 'stage') return;
    setCollapsed((c) => ({ ...c, [selected.stageId]: false }));
    if (selected.taskId && view.expandDepth === 'sub') {
      setExpandedTasks((e) => ({ ...e, [selected.taskId]: true }));
    }
  }, [selected && selected.id, view.expandDepth]);

  /* ----- mutations ----- */
  const updateStage = (phaseId, stageId, patch) =>
    setCatalog((cat) => mapPhases(cat, phaseId, (ph) => mapStages(ph, stageId, (st) => ({ ...st, ...patch }))));

  const updateTask = (phaseId, stageId, taskId, patch) =>
    setCatalog((cat) => mapPhases(cat, phaseId, (ph) => mapStages(ph, stageId, (st) => mapTasks(st, taskId, (tk) => ({ ...tk, ...patch })))));

  const addStage = (phaseId) =>
    setCatalog((cat) => mapPhases(cat, phaseId, (ph) => ({
      ...ph, stages: [...ph.stages, { id: hUid('st'), slug: 'new_stage', name: 'Новый этап', color: '#8a817a', inc: 'required', cond: '', dependsOn: [], dependsMode: 'all', note: '', tasks: [] }],
    })));

  const deleteStage = (phaseId, stageId) =>
    setCatalog((cat) => mapPhases(cat, phaseId, (ph) => ({ ...ph, stages: ph.stages.filter((s) => s.id !== stageId) })));

  const duplicateStage = (phaseId, stageId) =>
    setCatalog((cat) => mapPhases(cat, phaseId, (ph) => {
      const idx = ph.stages.findIndex((s) => s.id === stageId);
      if (idx < 0) return ph;
      const src = ph.stages[idx];
      const clone = {
        ...src, id: hUid('st'), name: src.name + ' (копия)',
        tasks: src.tasks.map((tk) => ({ ...tk, id: hUid('tk'), sub: (tk.sub || []).map((s) => ({ ...s, id: hUid('su') })) })),
      };
      const stages = [...ph.stages];
      stages.splice(idx + 1, 0, clone);
      return { ...ph, stages };
    }));

  const addTask = (phaseId, stageId) =>
    setCatalog((cat) => mapPhases(cat, phaseId, (ph) => mapStages(ph, stageId, (st) => ({
      ...st, tasks: [...st.tasks, { id: hUid('tk'), slug: 'new_task', name: 'Новая задача', days: 1, pause: 0, role: null, inc: 'required', cond: '', dependsOn: [], dependsMode: 'all', sub: [] }],
    }))));

  const deleteTask = (phaseId, stageId, taskId) =>
    setCatalog((cat) => mapPhases(cat, phaseId, (ph) => mapStages(ph, stageId, (st) => ({ ...st, tasks: st.tasks.filter((t) => t.id !== taskId) }))));

  const duplicateTask = (phaseId, stageId, taskId) =>
    setCatalog((cat) => mapPhases(cat, phaseId, (ph) => mapStages(ph, stageId, (st) => {
      const idx = st.tasks.findIndex((t) => t.id === taskId);
      if (idx < 0) return st;
      const src = st.tasks[idx];
      const clone = { ...src, id: hUid('tk'), name: src.name + ' (копия)', sub: (src.sub || []).map((s) => ({ ...s, id: hUid('su') })) };
      const tasks = [...st.tasks];
      tasks.splice(idx + 1, 0, clone);
      return { ...st, tasks };
    })));

  const nextInc = (cur) => {
    const order = ['required', 'optional', 'conditional'];
    return order[(order.indexOf(cur || 'required') + 1) % order.length];
  };

  const cycleInc = (phaseId, stageId, taskId, cur) => {
    updateTask(phaseId, stageId, taskId, { inc: nextInc(cur) });
  };
  const cycleStageInc = (phaseId, stageId, cur) => {
    updateStage(phaseId, stageId, { inc: nextInc(cur) });
  };
  const cycleSubInc = (phaseId, stageId, taskId, subId, cur) => {
    updateSub(phaseId, stageId, taskId, subId, { inc: nextInc(cur) });
  };

  /* subtasks */
  const addSub = (phaseId, stageId, taskId, role) =>
    setCatalog((cat) => mapPhases(cat, phaseId, (ph) => mapStages(ph, stageId, (st) => mapTasks(st, taskId, (tk) => ({
      ...tk, sub: [...(tk.sub || []), { id: hUid('su'), name: 'Новая подзадача', role: role || null, inc: 'required', cond: '', dependsOn: [], dependsMode: 'all' }],
    })))));
  const updateSub = (phaseId, stageId, taskId, subId, patch) =>
    setCatalog((cat) => mapPhases(cat, phaseId, (ph) => mapStages(ph, stageId, (st) => mapTasks(st, taskId, (tk) => ({
      ...tk, sub: tk.sub.map((s) => (s.id === subId ? { ...s, ...patch } : s)),
    })))));
  const deleteSub = (phaseId, stageId, taskId, subId) =>
    setCatalog((cat) => mapPhases(cat, phaseId, (ph) => mapStages(ph, stageId, (st) => mapTasks(st, taskId, (tk) => ({
      ...tk, sub: tk.sub.filter((s) => s.id !== subId),
    })))));

  /* reorder */
  const moveStage = (phaseId, fromId, toId) =>
    setCatalog((cat) => mapPhases(cat, phaseId, (ph) => {
      if (fromId === toId) return ph;
      const from = ph.stages.findIndex((s) => s.id === fromId);
      const stages = [...ph.stages];
      const [m] = stages.splice(from, 1);
      const to = stages.findIndex((s) => s.id === toId);
      stages.splice(to, 0, m);
      return { ...ph, stages };
    }));
  const moveTask = (phaseId, stageId, fromId, toId) =>
    setCatalog((cat) => mapPhases(cat, phaseId, (ph) => mapStages(ph, stageId, (st) => {
      if (fromId === toId) return st;
      const from = st.tasks.findIndex((t) => t.id === fromId);
      const tasks = [...st.tasks];
      const [m] = tasks.splice(from, 1);
      const to = tasks.findIndex((t) => t.id === toId);
      tasks.splice(to, 0, m);
      return { ...st, tasks };
    })));

  const addPhase = () =>
    setCatalog((cat) => [...cat, { id: hUid('ph'), name: 'Новая фаза', stages: [] }]);
  const updatePhase = (phaseId, patch) => setCatalog((cat) => mapPhases(cat, phaseId, (ph) => ({ ...ph, ...patch })));
  const deletePhase = (phaseId) => setCatalog((cat) => cat.filter((p) => p.id !== phaseId));

  /* пустой каталог */
  if (!catalog.length) {
    return (
      <EmptyState icon="layers"
        title="Каталог пуст"
        text="Добавьте первую фазу или восстановите заводской seed из меню «···»."
        action={<button onClick={addPhase} style={ctorStyles.btnPrimary}>{Icon.plus} Добавить фазу</button>} />
    );
  }

  return (
    <div className="hier-panel">
      {ViewBar() && React.createElement(ViewBar(), { view, onChange: setView, catalog, onApplyExpand: applyExpand, showExpand: false })}
      {catalog.map((ph) => {
        const open = !collapsed[ph.id];
        const phStats = ph.stages.reduce((a, st) => {
          a.stages++; st.tasks.forEach((tk) => { a.days += +tk.days || 0; a.pause += +tk.pause || 0; });
          return a;
        }, { stages: 0, days: 0, pause: 0 });
        return (
          <section key={ph.id} className="hier-phase">
            <header className="hier-phase-head">
              <button onClick={() => toggle(ph.id)} style={{ ...ctorStyles.iconBtn, color: '#8a817a', transform: open ? 'rotate(90deg)' : 'none', transition: 'transform .15s' }}>
                {Icon.chevron}
              </button>
              <DraftDot on={ch.has(ph.id)} />
              <InlineInput value={ph.name} onCommit={(v) => updatePhase(ph.id, { name: v || 'Фаза' })}
                style={{ fontSize: 19, fontWeight: 600, letterSpacing: '-.02em', color: '#1a1714', flex: 1, minWidth: 0 }} />
              <span className="hier-phase-stats">
                {phStats.stages} эт · {Math.round(phStats.days)} раб.дн{phStats.pause ? ` · +${phStats.pause} простой` : ''}
              </span>
              <KebabMenu items={[
                { icon: Icon.plus, label: 'Добавить этап', onClick: () => addStage(ph.id) },
                { icon: Icon.trash, label: 'Удалить фазу', onClick: () => { if (confirm('Удалить фазу «' + ph.name + '» со всеми этапами?')) deletePhase(ph.id); }, danger: true },
              ]} />
            </header>

            {open && (
              <div className="hier-phase-body">
                {ph.stages.map((st) => {
                  if (HV().hiddenFor(view, 'stages')) return null;
                  const stageOp = HV().opacityFor(view, 'stages');
                  const sOpen = !collapsed[st.id];
                  const sDays = st.tasks.reduce((a, t) => a + (+t.days || 0), 0);
                  const sPause = st.tasks.reduce((a, t) => a + (+t.pause || 0), 0);
                  const isDragging = dragStage && dragStage.stageId === st.id;
                  const isDrop = dropStage && dropStage.stageId === st.id && dragStage && dragStage.stageId !== st.id;
                  const stageSelected = isSel(st.id);
                  const stageInspectorOpen = inspectorOpen(st.id);
                  return (
                    <React.Fragment key={st.id}>
                    <div
                      id={window.catalogNodeAnchorId ? window.catalogNodeAnchorId(st.id) : undefined}
                      className={'hier-stage' + (stageSelected ? ' is-selected' : '') + (stageInspectorOpen ? ' is-inspector-open' : '') + (isDrop ? ' is-drop' : '') + (isDragging ? ' is-dragging' : '')}
                      style={{ opacity: isDragging ? 0.4 * stageOp : stageOp, padding: compact ? '10px 12px' : undefined }}
                      onDragOver={(e) => { if (dragStage && dragStage.phaseId === ph.id && dragStage.stageId !== st.id) { e.preventDefault(); setDropStage({ phaseId: ph.id, stageId: st.id }); } }}
                      onDrop={(e) => { if (dragStage && dragStage.phaseId === ph.id) { e.preventDefault(); moveStage(ph.id, dragStage.stageId, st.id); } setDragStage(null); setDropStage(null); }}
                    >
                      <div className="hier-stage-head"
                        onClick={(e) => rowClick(e, { type: 'stage', id: st.id, phaseId: ph.id, stageId: st.id })}>
                        <span draggable className="hier-drag"
                          onDragStart={(e) => { e.dataTransfer.effectAllowed = 'move'; setDragStage({ phaseId: ph.id, stageId: st.id }); }}
                          onDragEnd={() => { setDragStage(null); setDropStage(null); }}
                          title="Перетащить этап">⋮⋮</span>
                        <button onClick={() => toggle(st.id)} style={{ ...ctorStyles.iconBtnSm, color: '#8a817a', transform: sOpen ? 'rotate(90deg)' : 'none', transition: 'transform .15s' }}>
                          {Icon.chevron}
                        </button>
                        <ColorDot color={st.color} onPick={(c) => updateStage(ph.id, st.id, { color: c })} />
                        <DraftDot on={ch.has(st.id)} />
                        <div style={{ flex: 1, minWidth: 0, cursor: 'pointer' }}
                          onClick={() => pick({ type: 'stage', id: st.id, phaseId: ph.id, stageId: st.id })}>
                          <InlineInput value={st.name} onCommit={(v) => updateStage(ph.id, st.id, { name: v || 'Этап' })}
                            onActivate={() => pick({ type: 'stage', id: st.id, phaseId: ph.id, stageId: st.id })}
                            style={{ fontSize: 16, fontWeight: 600, letterSpacing: '-.015em', color: '#1a1714', width: '100%' }} />
                          {showSlugs && (
                            <InlineInput value={st.slug} onCommit={(v) => updateStage(ph.id, st.id, { slug: v })}
                              style={{ fontSize: 11, fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace', color: '#a89e92', width: '100%' }} />
                          )}
                        </div>
                        <InclusionBadge
                          inc={nodeInc(st)}
                          cond={st.cond}
                          dependsOn={st.dependsOn}
                          onCycle={() => cycleStageInc(ph.id, st.id, st.inc || nodeInc(st))}
                        />
                        {depBadges(st)}
                        {st.tasks.some((tk) => tk.subcontractorOn) && (
                          <span title="В этапе есть субподрядные работы" className="chip chip--materials">{Icon.briefcase} субподряд</span>
                        )}
                        <span className="hier-stage-meta">
                          {st.tasks.length} зад
                          <HVis view={view} field="duration" inline>{' · ' + Math.round(sDays) + 'д'}</HVis>
                          <HVis view={view} field="pauses" inline>{sPause ? ` · +${sPause}` : ''}</HVis>
                        </span>
                        <HVis view={view} field="cost" inline><MoneyChip value={hStageClient(st)} strong title="Цена этапа для клиента (Σ)" /></HVis>
                        <HVis view={view} field="notes" inline><NoteBadge n={nc[st.id]} onClick={notes ? (e) => openNotePop(e, { nodeId: st.id, nodeName: st.name, nodePath: ph.name, nodeColor: st.color }) : undefined} /></HVis>
                        <HVis view={view} field="materials" inline><BomBadge n={(bc[st.id]||[]).length} onClick={(e) => { e.stopPropagation(); pick({ type: 'stage', id: st.id, phaseId: ph.id, stageId: st.id }); }} /></HVis>
                        <button onClick={() => openCondition({ type: 'stage', id: st.id, phaseId: ph.id, stageId: st.id })}
                          title="Условие включения" style={condBtnStyle(stageInspectorOpen && inspectorFocus === 'condition')}>
                          Условие
                        </button>
                        <button onClick={() => openInspector({ type: 'stage', id: st.id, phaseId: ph.id, stageId: st.id })}
                          title="Настройки этапа" style={{ display: 'inline-flex', alignItems: 'center', gap: 5, padding: '3px 10px', borderRadius: 980, border: '1px solid ' + (stageInspectorOpen ? '#e8793a' : '#e4ddd2'), background: stageInspectorOpen ? '#fff0e6' : 'transparent', color: stageInspectorOpen ? '#e8793a' : '#8a817a', fontSize: 11.5, fontWeight: 700, fontFamily: 'inherit', cursor: 'pointer', whiteSpace: 'nowrap', flexShrink: 0 }}>
                          <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
                          Настройки
                        </button>
                        <KebabMenu items={[
                          { icon: Icon.plus, label: 'Добавить задачу', onClick: () => { addTask(ph.id, st.id); setCollapsed((c) => ({ ...c, [st.id]: false })); } },
                          { icon: Icon.copy, label: 'Дублировать этап', onClick: () => duplicateStage(ph.id, st.id) },
                          { icon: Icon.trash, label: 'Удалить этап', onClick: () => { if (confirm('Удалить этап «' + st.name + '»?')) deleteStage(ph.id, st.id); }, danger: true },
                        ]} />
                      </div>

                      {st.note && sOpen && (
                        <HVis view={view} field="notes">
                          <div style={{ fontSize: 12, color: '#8a817a', background: 'transparent', borderLeft: '2px solid var(--border-subtle, #e7e2d9)', padding: '2px 10px', margin: '8px 0 0 34px' }}>
                            {st.note}
                          </div>
                        </HVis>
                      )}

                      {/* TASKS */}
                      {sOpen && !HV().hiddenFor(view, 'tasks') && (
                        <div className={'hier-tasks' + (compact ? ' is-compact' : '')} style={{ opacity: HV().opacityFor(view, 'tasks') }}>
                          {st.tasks.length === 0 && (
                            <div style={{ fontSize: 13, color: '#a89e92', padding: '6px 4px' }}>Нет задач — добавьте задачу ниже.</div>
                          )}
                          {st.tasks.map((tk) => {
                            const tExp = expandedTasks[tk.id];
                            const tDragging = dragTask && dragTask.taskId === tk.id;
                            const tDrop = dropTask && dropTask.taskId === tk.id && dragTask && dragTask.taskId !== tk.id;
                            const taskSelected = isSel(tk.id);
                            return (
                              <React.Fragment key={tk.id}>
                              <div
                                onDragOver={(e) => { if (dragTask && dragTask.stageId === st.id && dragTask.taskId !== tk.id) { e.preventDefault(); setDropTask({ stageId: st.id, taskId: tk.id }); } }}
                                onDrop={(e) => { if (dragTask && dragTask.stageId === st.id) { e.preventDefault(); moveTask(ph.id, st.id, dragTask.taskId, tk.id); } setDragTask(null); setDropTask(null); }}
                                style={{
                                  opacity: tDragging ? 0.4 : 1,
                                  boxShadow: tDrop ? 'inset 0 2px 0 0 #e8793a' : 'none',
                                }}>
                                {/* TASK ROW */}
                                <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: compact ? '5px 6px' : '7px 6px', borderRadius: 9, flexWrap: 'wrap', background: taskSelected ? '#fff0e6' : 'transparent', boxShadow: taskSelected ? 'inset 0 0 0 1px #e8793a40' : 'none', cursor: 'pointer' }}
                                  id={window.catalogNodeAnchorId ? window.catalogNodeAnchorId(tk.id) : undefined}
                                  className="ctor-task-row"
                                  onClick={(e) => rowClick(e, { type: 'task', id: tk.id, phaseId: ph.id, stageId: st.id, taskId: tk.id })}>
                                  <span draggable
                                    onDragStart={(e) => { e.stopPropagation(); e.dataTransfer.effectAllowed = 'move'; setDragTask({ stageId: st.id, taskId: tk.id }); }}
                                    onDragEnd={() => { setDragTask(null); setDropTask(null); }}
                                    title="Перетащить" style={{ cursor: 'grab', color: '#d4d4d8', fontSize: 12, lineHeight: 1, userSelect: 'none' }}>⋮⋮</span>
                                  <button onClick={() => toggleTask(tk.id)} title={(tk.sub || []).length ? 'Подзадачи' : 'Добавить подзадачи'}
                                    style={{ ...ctorStyles.iconBtnSm, width: 20, height: 20, color: (tk.sub || []).length ? st.color : '#d4d4d8', transform: tExp ? 'rotate(90deg)' : 'none', transition: 'transform .15s' }}>
                                    {Icon.chevron}
                                  </button>
                                  <DraftDot on={ch.has(tk.id)} />
                                  <div style={{ flex: 1, minWidth: 160, cursor: 'pointer' }}
                                    onClick={() => pick({ type: 'task', id: tk.id, phaseId: ph.id, stageId: st.id, taskId: tk.id })}>
                                    <InlineInput value={tk.name} onCommit={(v) => updateTask(ph.id, st.id, tk.id, { name: v || 'Задача' })}
                                      onActivate={() => pick({ type: 'task', id: tk.id, phaseId: ph.id, stageId: st.id, taskId: tk.id })}
                                      style={{ fontSize: 14, fontWeight: 500, color: '#1a1714', width: '100%' }} />
                                    {showSlugs && (
                                      <InlineInput value={tk.slug} onCommit={(v) => updateTask(ph.id, st.id, tk.id, { slug: v })}
                                        style={{ fontSize: 10.5, fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace', color: '#bcb3a7', width: '100%' }} />
                                    )}
                                  </div>
                                  {(tk.sub || []).length > 0 && (
                                    <span style={{ fontSize: 11, color: '#a89e92', fontWeight: 500 }}>{tk.sub.length} подзад</span>
                                  )}
                                  <LevelPill level={tk.level} onCycle={() => updateTask(ph.id, st.id, tk.id, { level: nextLevel(tk.level) })} />
                                  <HVis view={view} field="duration" inline><NumField value={tk.days} onChange={(v) => updateTask(ph.id, st.id, tk.id, { days: v })} suffix="раб.дн" min={0} step={0.5} title="Базовый срок выполнения" /></HVis>
                                  <HVis view={view} field="pauses" inline><NumField value={tk.pause} onChange={(v) => updateTask(ph.id, st.id, tk.id, { pause: v })} suffix="простой" min={0} step={1} title="Технологический простой после задачи" accent={tk.pause > 0} quietZero /></HVis>
                                  <HVis view={view} field="executor" inline><RolePill roleId={tk.role} roles={roles} onChange={(r) => updateTask(ph.id, st.id, tk.id, { role: r })} /></HVis>
                                  <HVis view={view} field="cost" inline><MoneyChip value={hTaskClient(tk)} title="Цена задачи для клиента" /></HVis>
                                  <InclusionBadge inc={nodeInc(tk)} cond={tk.cond} dependsOn={tk.dependsOn} onCycle={() => cycleInc(ph.id, st.id, tk.id, tk.inc)} />
                                  {depBadges(tk)}
                                  <HVis view={view} field="notes" inline><NoteBadge n={nc[tk.id]} onClick={notes ? (e) => openNotePop(e, { nodeId: tk.id, nodeName: tk.name, nodePath: ph.name + ' › ' + st.name, nodeColor: st.color }) : undefined} /></HVis>
                                  <HVis view={view} field="materials" inline><BomBadge n={(bc[tk.id]||[]).length} onClick={(e) => { e.stopPropagation(); pick({ type: 'task', id: tk.id, phaseId: ph.id, stageId: st.id, taskId: tk.id }); }} /></HVis>
                                  {tk.checklistId && <HVis view={view} field="checklists" inline><span title="Прикреплён чек-лист" className="chip chip--data">{Icon.clipboard}</span></HVis>}
                                  {tk.subcontractorOn && <span title="Работу выполняет субподрядчик" className="chip chip--materials">{Icon.briefcase} субподряд</span>}
                                  {(tk.linkedOptions||[]).length > 0 && <span title={`Опции: ${(tk.linkedOptions||[]).length}`} className="chip chip--data">{Icon.sliders} {(tk.linkedOptions||[]).length}</span>}
                                  <KebabMenu color="#d4d4d8" items={[
                                    { icon: pencilIcon, label: 'Настройки', onClick: () => openInspector({ type: 'task', id: tk.id, phaseId: ph.id, stageId: st.id, taskId: tk.id }) },
                                    { icon: pencilIcon, label: 'Условие', onClick: () => openCondition({ type: 'task', id: tk.id, phaseId: ph.id, stageId: st.id, taskId: tk.id }) },
                                    { icon: Icon.plus, label: 'Добавить подзадачу', onClick: () => { addSub(ph.id, st.id, tk.id, tk.role); setExpandedTasks((c) => ({ ...c, [tk.id]: true })); } },
                                    { icon: Icon.copy, label: 'Дублировать задачу', onClick: () => duplicateTask(ph.id, st.id, tk.id) },
                                    { icon: Icon.trash, label: 'Удалить задачу', onClick: () => deleteTask(ph.id, st.id, tk.id), danger: true },
                                  ]} />
                                </div>

                                {/* SUBTASKS */}
                                {tExp && !HV().hiddenFor(view, 'subtasks') && (
                                  <div className="hier-subtasks" style={{ opacity: HV().opacityFor(view, 'subtasks') }}>
                                    {(tk.sub || []).map((su) => {
                                      const workSelected = isSel(su.id);
                                      return (
                                        <React.Fragment key={su.id}>
                                        <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '4px 6px', borderRadius: 7, background: workSelected ? '#fff0e6' : 'transparent', boxShadow: workSelected ? 'inset 0 0 0 1px #e8793a40' : 'none', cursor: 'pointer' }}
                                          id={window.catalogNodeAnchorId ? window.catalogNodeAnchorId(su.id) : undefined}
                                          onClick={(e) => rowClick(e, { type: 'work', id: su.id, phaseId: ph.id, stageId: st.id, taskId: tk.id, subId: su.id })}>
                                          <span style={{ width: 5, height: 5, borderRadius: '50%', background: st.color, opacity: 0.5, flexShrink: 0 }}></span>
                                          <DraftDot on={ch.has(su.id)} />
                                          <InlineInput value={su.name} onCommit={(v) => updateSub(ph.id, st.id, tk.id, su.id, { name: v || 'Подзадача' })}
                                            onActivate={() => pick({ type: 'work', id: su.id, phaseId: ph.id, stageId: st.id, taskId: tk.id, subId: su.id })}
                                            style={{ fontSize: 13, color: '#6b6259', flex: 1, minWidth: 0 }} />
                                          <button onClick={() => openCondition({ type: 'work', id: su.id, phaseId: ph.id, stageId: st.id, taskId: tk.id, subId: su.id })}
                                            title="Условие включения" style={condBtnStyle(inspectorOpen(su.id) && inspectorFocus === 'condition')}>
                                            Условие
                                          </button>
                                          <button onClick={() => openInspector({ type: 'work', id: su.id, phaseId: ph.id, stageId: st.id, taskId: tk.id, subId: su.id })}
                                            title="Настройки подзадачи" style={{ display: 'inline-flex', alignItems: 'center', gap: 4, padding: '2px 9px', borderRadius: 980, border: '1px solid ' + (inspectorOpen(su.id) ? '#e8793a' : '#e4ddd2'), background: inspectorOpen(su.id) ? '#fff0e6' : 'transparent', color: inspectorOpen(su.id) ? '#e8793a' : '#8a817a', fontSize: 11, fontWeight: 700, fontFamily: 'inherit', cursor: 'pointer', whiteSpace: 'nowrap', flexShrink: 0 }}>
                                            <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
                                            Настройки
                                          </button>
                                          <HVis view={view} field="executor" inline><RolePill roleId={su.role} roles={roles} size="sm" onChange={(r) => updateSub(ph.id, st.id, tk.id, su.id, { role: r })} /></HVis>
                                          <InclusionBadge inc={nodeInc(su)} cond={su.cond} dependsOn={su.dependsOn} onCycle={() => cycleSubInc(ph.id, st.id, tk.id, su.id, su.inc)} />
                                          {depBadges(su)}
                                          <HVis view={view} field="cost" inline><MoneyChip value={hNodeClient(su)} title="Цена подзадачи для клиента" /></HVis>
                                          <HVis view={view} field="notes" inline><NoteBadge n={nc[su.id]} onClick={notes ? (e) => openNotePop(e, { nodeId: su.id, nodeName: su.name, nodePath: ph.name + ' › ' + st.name + ' › ' + tk.name, nodeColor: st.color }) : undefined} /></HVis>
                                          <HVis view={view} field="materials" inline><BomBadge n={(bc[su.id]||[]).length} onClick={(e) => { e.stopPropagation(); pick({ type: 'work', id: su.id, phaseId: ph.id, stageId: st.id, taskId: tk.id, subId: su.id }); }} /></HVis>
                                          <button onClick={() => deleteSub(ph.id, st.id, tk.id, su.id)} style={ctorStyles.iconBtnSm} title="Удалить">{Icon.x}</button>
                                        </div>
                                        {window.NodeSkirt && !HV().hiddenFor(view, 'materials') && (
                                          <span style={{ opacity: HV().opacityFor(view, 'materials'), display: 'contents' }}>
                                            {React.createElement(window.NodeSkirt, { nodeId: su.id, notes, setNotes, bomLines: bc[su.id], setBindings, materialsLib: materialsLib || [], color: st.color })}
                                          </span>
                                        )}
                                      {workSelected && inspectorOpen(su.id) && inspectorNode && window.InspectorInline && React.createElement(window.InspectorInline, { node: inspectorNode, patch: inspectorPatch, roles, notes, setNotes, onTabSwitch, onClose: closeInspector, checklists: checklists||[], options: options||[], measures: measures||[], bindings, setBindings, materialsLib: materialsLib||[], catalog, focusSection: inspectorFocus })}
                                        </React.Fragment>
                                      );
                                    })}
                                    <button onClick={() => addSub(ph.id, st.id, tk.id, tk.role)} style={{ ...ctorStyles.dropdownItem, color: '#e8793a', width: 'auto', fontSize: 12.5, padding: '5px 6px' }}>
                                      {Icon.plus} Подзадача
                                    </button>
                                  </div>
                                )}
                              </div>
                              {window.NodeSkirt && !HV().hiddenFor(view, 'materials') && (
                                <span style={{ opacity: HV().opacityFor(view, 'materials'), display: 'contents' }}>
                                  {React.createElement(window.NodeSkirt, { nodeId: tk.id, notes, setNotes, bomLines: bc[tk.id], setBindings, materialsLib: materialsLib || [], color: st.color })}
                                </span>
                              )}
                              {taskSelected && inspectorOpen(tk.id) && inspectorNode && window.InspectorInline && React.createElement(window.InspectorInline, { node: inspectorNode, patch: inspectorPatch, roles, notes, setNotes, onTabSwitch, onClose: closeInspector, checklists: checklists||[], options: options||[], measures: measures||[], bindings, setBindings, materialsLib: materialsLib||[], catalog, focusSection: inspectorFocus })}
                              </React.Fragment>
                            );
                          })}
                          <button onClick={() => addTask(ph.id, st.id)} style={{ ...ctorStyles.dropdownItem, color: '#e8793a', width: 'auto', fontSize: 13, padding: '7px 6px', fontWeight: 600 }}>
                            {Icon.plus} Задача
                          </button>
                        </div>
                      )}
                    </div>
                    {window.NodeSkirt && !HV().hiddenFor(view, 'materials') && (
                      <span style={{ opacity: HV().opacityFor(view, 'materials'), display: 'contents' }}>
                        {React.createElement(window.NodeSkirt, { nodeId: st.id, notes, setNotes, bomLines: bc[st.id], setBindings, materialsLib: materialsLib || [], color: st.color })}
                      </span>
                    )}
                    {stageInspectorOpen && inspectorNode && window.InspectorInline && React.createElement(window.InspectorInline, { node: inspectorNode, patch: inspectorPatch, roles, notes, setNotes, onTabSwitch, onClose: closeInspector, checklists: checklists||[], options: options||[], measures: measures||[], bindings, setBindings, materialsLib: materialsLib||[], catalog, focusSection: inspectorFocus })}
                    </React.Fragment>
                  );
                })}
                <button type="button" onClick={() => addStage(ph.id)} className="hier-add-stage" style={ctorStyles.btnDashed}>
                  {Icon.plus} Добавить этап
                </button>
              </div>
            )}
          </section>
        );
      })}
      <button type="button" onClick={addPhase} className="hier-add-phase" style={ctorStyles.btnGhost}>
        {Icon.plus} Добавить фазу
      </button>
      {notePop && notes && setNotes && (
        <NotePopover
          anchorRect={notePop.anchorRect}
          nodeId={notePop.nodeId} nodeName={notePop.nodeName} nodePath={notePop.nodePath} nodeColor={notePop.nodeColor}
          notes={notes} setNotes={setNotes}
          onClose={() => setNotePop(null)} />
      )}
    </div>
  );
}

/* ---------- Level pill — требуемый уровень исполнителя 0–3 ---------- */
const LEVEL_META = {
  '': { label: 'любой', color: '#8a817a', bg: '#f0ece5' },
  elite: { label: 'Элита 0–1', color: '#7c3aed', bg: '#f3eafe' },
  pro: { label: 'Профи 2–3', color: '#e8793a', bg: '#fff0e6' },
};
function nextLevel(cur) {
  const order = ['', 'elite', 'pro'];
  return order[(order.indexOf(cur || '') + 1) % order.length];
}
function LevelPill({ level, onCycle }) {
  const m = LEVEL_META[level || ''] || LEVEL_META[''];
  const cls = !level ? 'chip chip--ghost' : level === 'elite' ? 'chip chip--lvl-elite' : 'chip chip--lvl-pro';
  return (
    <button onClick={onCycle} title="Требуемый уровень исполнителя" className={cls}>
      {Icon.star}
      {m.label}
    </button>
  );
}

const pencilIcon = <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.12 2.12 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>;

/* ---------- EmptyState — общий ---------- */
function EmptyState({ icon, title, text, action }) {
  return (
    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center', padding: '64px 24px', gap: 12 }}>
      <div style={{ width: 56, height: 56, borderRadius: 16, background: '#f0ece5', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#a89e92', marginBottom: 4 }}>
        <svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><polygon points="12 2 2 7 12 12 22 7 12 2"></polygon><polyline points="2 17 12 22 22 17"></polyline><polyline points="2 12 12 17 22 12"></polyline></svg>
      </div>
      <div style={{ fontSize: 17, fontWeight: 600, color: '#1a1714', letterSpacing: '-.015em' }}>{title}</div>
      <div style={{ fontSize: 14, color: '#8a817a', maxWidth: 420, lineHeight: 1.5 }}>{text}</div>
      {action && <div style={{ marginTop: 8 }}>{action}</div>}
    </div>
  );
}

/* ---------- ColorDot — мини-палитра цвета этапа ---------- */
const STAGE_COLORS = ['#ef4444', '#f97316', '#f59e0b', '#a855f7', '#8b5cf6', '#6366f1', '#3b82f6', '#0ea5e9', '#06b6d4', '#14b8a6', '#10b981', '#22c55e', '#78716c', '#a3a3a3', '#1a1714'];
function ColorDot({ color, onPick }) {
  const [open, setOpen] = hUseState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (!open) return;
    const onDown = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('mousedown', onDown);
    return () => document.removeEventListener('mousedown', onDown);
  }, [open]);
  return (
    <div ref={ref} style={{ position: 'relative', flexShrink: 0 }}>
      <button onClick={() => setOpen((o) => !o)} title="Цвет этапа" style={{ width: 14, height: 14, borderRadius: '50%', background: color, border: '1.5px solid #fff', boxShadow: '0 0 0 1px ' + color + '60', cursor: 'pointer', padding: 0 }}></button>
      {open && (
        <div style={{ ...ctorStyles.dropdown, left: 0, right: 'auto', minWidth: 0, display: 'grid', gridTemplateColumns: 'repeat(5, 22px)', gap: 6, padding: 10 }}>
          {STAGE_COLORS.map((c) => (
            <button key={c} onClick={() => { onPick(c); setOpen(false); }} style={{ width: 22, height: 22, borderRadius: '50%', background: c, border: c === color ? '2px solid #1a1714' : '2px solid #fff', boxShadow: '0 0 0 .5px rgba(0,0,0,.1)', cursor: 'pointer', padding: 0 }}></button>
          ))}
        </div>
      )}
    </div>
  );
}

Object.assign(window, { HierarchyPanel, EmptyState, ColorDot, LEVEL_META, pencilIcon, BomBadge });
