/* ctor-materials.jsx — «Конструктор материалов»: 8-я вкладка каталога.
   Зеркало конструктора проектов для материального слоя. 6 подвкладок:
   Подвиды · Типы полей · Библиотека · Связи с работами · Подборки · Импорт/экспорт */
const { useState: matUseState, useMemo: matUseMemo } = React;
const MDX = window.MaterialsData;

function mplural(n, one, few, many) {
  const m10 = n % 10,m100 = n % 100;
  if (m10 === 1 && m100 !== 11) return one;
  if (m10 >= 2 && m10 <= 4 && (m100 < 10 || m100 >= 20)) return few;
  return many;
}
function mmoney(n) {return (n || 0).toLocaleString('ru-RU') + ' ₽';}

const MAT_SUBTABS = [
{ v: 'subtypes', l: 'Подвиды' },
{ v: 'fieldtypes', l: 'Типы полей' },
{ v: 'library', l: 'Библиотека' },
{ v: 'bindings', l: 'Связи с работами' },
{ v: 'presets', l: 'Подборки' },
{ v: 'io', l: 'Импорт / экспорт' }];


function MaterialsConstructor(props) {
  const { library, setLibrary, fieldTypes, setFieldTypes, categories, setCategories,
    bindings, setBindings, matPresets, setMatPresets, catalog } = props;
  const [sub, setSub] = matUseState(() => localStorage.getItem('mat_subtab') || 'library');
  React.useEffect(() => {localStorage.setItem('mat_subtab', sub);}, [sub]);

  const bindTotal = matUseMemo(() => Object.values(bindings || {}).reduce((a, arr) => a + (arr ? arr.length : 0), 0), [bindings]);
  const counts = {
    subtypes: MDX.SUBTYPES.length, fieldtypes: fieldTypes.length, library: library.length,
    bindings: bindTotal, presets: matPresets.length, io: null
  };

  return (
    <div>
      {/* интро */}
      <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: 16, flexWrap: 'wrap', marginBottom: 16 }}>
        <div>
          <div style={ctorStyles.sectionLabel}>Конструктор материалов</div>
          <div style={{ fontSize: 14, color: '#8a817a', marginTop: 6, maxWidth: 640 }}>
            Мастер-каталог материалов: подвиды, схемы характеристик и библиотека позиций. Привязки «по умолчанию» задаются на узлах во вкладке «Иерархия» и попадают в смету на воронке.
          </div>
        </div>
      </div>

      {/* nested subtab bar */}
      <div style={{ ...ctorStyles.segmentedTrack, marginBottom: 22 }}>
        {MAT_SUBTABS.map((s) => {
          const on = sub === s.v;
          return (
            <button key={s.v} type="button" onClick={() => setSub(s.v)} style={ctorStyles.segmentedBtn(on)}>
              {s.l}
              {counts[s.v] != null && <span style={ctorStyles.segmentedCount(on)}>{counts[s.v]}</span>}
            </button>);

        })}
      </div>

      <div className="ctor-fade" key={sub}>
        {sub === 'subtypes' && <SubtypesPanel library={library} categories={categories} setCategories={setCategories} />}
        {sub === 'fieldtypes' && <FieldTypesPanel fieldTypes={fieldTypes} setFieldTypes={setFieldTypes} library={library} />}
        {sub === 'library' && <MaterialLibraryPanel library={library} setLibrary={setLibrary} fieldTypes={fieldTypes} categories={categories} bindings={bindings} />}
        {sub === 'bindings' && <BindingsPanel bindings={bindings} setBindings={setBindings} library={library} catalog={catalog} />}
        {sub === 'presets' && <MatPresetsPanel matPresets={matPresets} setMatPresets={setMatPresets} library={library} />}
        {sub === 'io' && <ImportExportPanel library={library} />}
      </div>
    </div>);

}

/* ============ ПОДВИДЫ (фикс. верхний уровень + категории) ============ */
function SubtypesPanel({ library, categories, setCategories }) {
  const CAT_PALETTE = ['#e8793a', '#0891b2', '#8b5cf6', '#10b981', '#b45309', '#78716c', '#3b82f6', '#ef4444', '#f59e0b', '#16a34a', '#db2777', '#0d9488'];
  const [tintEdit, setTintEdit] = matUseState(null); // categoryId с открытой палитрой
  const addCat = (subtype) => setCategories((cs) => [...cs, { id: MDX.uid('cat'), name: 'Новая категория', subtype, color: '#78716c' }]);
  const renameCat = (id, name) => setCategories((cs) => cs.map((c) => c.id === id ? { ...c, name } : c));
  const setCatColor = (id, color) => { setCategories((cs) => cs.map((c) => c.id === id ? { ...c, color } : c)); setTintEdit(null); };
  const delCat = (id) => {
    const used = library.filter((m) => m.categoryId === id).length;
    if (used && !confirm('В категории ' + used + ' позиций. Удалить категорию? Позиции останутся без категории.')) return;
    setCategories((cs) => cs.filter((c) => c.id !== id));
  };
  return (
    <div className="mat-subtypes">
      {MDX.SUBTYPES.map((s) => {
          const cats = categories.filter((c) => c.subtype === s.id);
          const posCount = library.filter((m) => m.subtype === s.id).length;
          const km = MDX.kindMeta(s.kind);
          return (
            <section key={s.id} className="mat-subtype-section">
              <div className="mat-subtype-head">
                <span className="mat-subtype-icon" aria-hidden="true">{s.icon}</span>
                <span className="mat-subtype-name" title={s.hint}>{s.name}</span>
                <span className="mat-subtype-meta">{posCount} поз · {cats.length} кат</span>
                <span className="mat-subtype-kind">{km.label.toLowerCase()}</span>
              </div>
              <div className="mat-subtype-cats">
                {cats.map((c) => {
                  const cn = library.filter((m) => m.categoryId === c.id).length;
                  const tint = c.color || s.color;
                  return (
                    <div key={c.id} className="mat-cat-chip">
                      <button type="button" className="mat-cat-dot" onClick={() => setTintEdit(tintEdit === c.id ? null : c.id)} title="Цвет категории"
                        style={{ background: tint, boxShadow: '0 0 0 1px ' + tint + '55' }} aria-label="Выбрать цвет" />
                      <InlineInput value={c.name} onCommit={(v) => renameCat(c.id, v || 'Категория')} style={{ fontSize: 12, fontWeight: 500, color: 'var(--foreground)', flex: 1, minWidth: 72, maxWidth: 180 }} />
                      <span className="mat-cat-count">{cn}</span>
                      <button type="button" className="mat-cat-del" onClick={() => delCat(c.id)} title="Удалить категорию" aria-label="Удалить категорию">{Icon.x}</button>
                      {tintEdit === c.id && (
                        <div className="mat-cat-palette" role="listbox" aria-label="Палитра">
                          {CAT_PALETTE.map((col) =>
                            <button key={col} type="button" onClick={() => setCatColor(c.id, col)} title={col}
                              style={{ background: col, border: col === tint ? '2px solid #1a1714' : '1px solid #fff', boxShadow: '0 0 0 1px #ece5da' }} aria-label={col} />)}
                        </div>)}
                    </div>);
                })}
                <button type="button" className="mat-cat-add" onClick={() => addCat(s.id)} style={{ color: s.color, borderColor: s.color + '44' }}>
                  {Icon.plus} Категория
                </button>
              </div>
            </section>);
        })}
    </div>);

}

/* ============ ТИПЫ ПОЛЕЙ (схемы характеристик) ============ */
const FT_PALETTE = ['#e8793a', '#0891b2', '#8b5cf6', '#10b981', '#b45309', '#78716c', '#3b82f6', '#ef4444', '#f59e0b', '#16a34a'];
const FIELD_KINDS = [{ v: 'text', l: 'Текст' }, { v: 'number', l: 'Число' }, { v: 'select', l: 'Список' }, { v: 'dim', l: 'Габариты' }];

function FieldTypesPanel({ fieldTypes, setFieldTypes, library }) {
  const [editId, setEditId] = matUseState(null);
  const editing = fieldTypes.find((f) => f.id === editId) || null;
  const usage = (id) => library.filter((m) => m.fieldTypeId === id).length;
  const update = (id, patch) => setFieldTypes((fs) => fs.map((f) => f.id === id ? { ...f, ...patch } : f));
  const add = () => {const f = { id: MDX.uid('ft'), name: 'Новый тип полей', color: '#e8793a', fields: [] };setFieldTypes((fs) => [...fs, f]);setEditId(f.id);};
  const remove = (id) => {if (usage(id) && !confirm('Тип используют ' + usage(id) + ' позиций. Удалить?')) return;setFieldTypes((fs) => fs.filter((f) => f.id !== id));if (editId === id) setEditId(null);};

  return (
    <div>
      <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', marginBottom: 16, gap: 16, flexWrap: 'wrap' }}>
        <div style={{ fontSize: 13.5, color: '#8a817a', maxWidth: 560 }}>
          Тип полей — переиспользуемая схема характеристик (как «Роли» у задач): набор атрибутов для позиции. Краска, Сплит-система, Плёнка…
        </div>
        <button onClick={add} style={ctorStyles.btnPrimary}>{Icon.plus} Тип полей</button>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: 12 }}>
        {fieldTypes.map((f) =>
        <div key={f.id} onClick={() => setEditId(f.id)} style={{ ...ctorStyles.card, padding: '16px 18px', cursor: 'pointer', borderLeft: '3px solid ' + f.color }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
              <span style={{ width: 34, height: 34, borderRadius: 10, background: f.color + '18', color: f.color, display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
                <svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="4" y1="9" x2="20" y2="9"></line><line x1="4" y1="15" x2="20" y2="15"></line><line x1="10" y1="3" x2="8" y2="21"></line><line x1="16" y1="3" x2="14" y2="21"></line></svg>
              </span>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 15, fontWeight: 700, color: '#1a1714', letterSpacing: '-.01em' }}>{f.name}</div>
                <div style={{ fontSize: 12, color: '#8a817a', marginTop: 1 }}>{f.fields.length} {mplural(f.fields.length, 'поле', 'поля', 'полей')} · {usage(f.id)} позиций</div>
              </div>
              <KebabMenu items={[
            { icon: pencilIcon, label: 'Редактировать', onClick: () => setEditId(f.id) },
            { icon: Icon.trash, label: 'Удалить', onClick: () => remove(f.id), danger: true }]
            } />
            </div>
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 5, marginTop: 12 }}>
              {f.fields.slice(0, 6).map((fl) =>
            <span key={fl.key} style={{ fontSize: 11, fontWeight: 500, color: '#6b6259', background: '#f0ece5', padding: '2px 9px', borderRadius: 980 }}>
                  {fl.label}{fl.unit ? ', ' + fl.unit : ''}
                </span>
            )}
              {f.fields.length === 0 && <span style={{ fontSize: 12, color: '#bcb3a7' }}>Поля не заданы</span>}
            </div>
          </div>
        )}
      </div>
      {editing && <FieldTypeDrawer ft={editing} onChange={(patch) => update(editing.id, patch)} onClose={() => setEditId(null)} />}
    </div>);

}

function FieldTypeDrawer({ ft, onChange, onClose }) {
  const setFields = (fn) => onChange({ fields: fn(ft.fields) });
  const addField = () => setFields((fs) => [...fs, { key: 'field_' + Math.random().toString(36).slice(2, 5), label: '', type: 'text', unit: '', options: [] }]);
  const patchField = (i, patch) => setFields((fs) => fs.map((f, idx) => idx === i ? { ...f, ...patch } : f));
  const delField = (i) => setFields((fs) => fs.filter((_, idx) => idx !== i));
  const move = (i, d) => setFields((fs) => { const n = [...fs]; const j = i + d; if (j < 0 || j >= n.length) return fs; [n[i], n[j]] = [n[j], n[i]]; return n; });

  return (
    <InspectorDrawer
      title="Тип полей"
      badges={<span style={{ width: 12, height: 12, borderRadius: 4, background: ft.color, display: 'inline-block' }} aria-hidden="true" />}
      onClose={onClose}
      width={560}
    >
      <div className="ins-tabpanel-grid">
        <InspectorSection title="Идентификация">
          <FText label="Название" value={ft.name} onChange={(v) => onChange({ name: v })} />
          <div>
            <div style={{ ...insStyles.label, marginBottom: 7 }}>Цвет метки</div>
            <div style={{ display: 'flex', flexWrap: 'wrap', gap: 7 }}>
              {FT_PALETTE.map((c) => (
                <button key={c} type="button" onClick={() => onChange({ color: c })} aria-label={'Цвет ' + c}
                  style={{ width: 26, height: 26, borderRadius: '50%', background: c, border: c === ft.color ? '2px solid var(--foreground)' : '2px solid var(--card)', boxShadow: '0 0 0 1px var(--border-subtle)', cursor: 'pointer', padding: 0 }} />
              ))}
            </div>
          </div>
        </InspectorSection>
      </div>

      <InspectorSection title={'Поля характеристик · ' + ft.fields.length} full>
        {ft.fields.map((f, i) => (
          <div key={i} className="ins-field-card">
            <div className="ins-field-card-toolbar">
              <div className="ins-field-card-toolbar-grow"><FText label="Подпись" value={f.label} onChange={(v) => patchField(i, { label: v })} placeholder="напр. Мощность" /></div>
              <div className="ins-field-card-toolbar-narrow"><FText label="Ед." value={f.unit} onChange={(v) => patchField(i, { unit: v })} placeholder="кВт" /></div>
              <div className="ins-field-card-actions">
                <button type="button" onClick={() => move(i, -1)} disabled={i === 0} style={{ ...ctorStyles.iconBtnSm, opacity: i === 0 ? .3 : 1 }} aria-label="Выше"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><polyline points="18 15 12 9 6 15"></polyline></svg></button>
                <button type="button" onClick={() => move(i, 1)} disabled={i === ft.fields.length - 1} style={{ ...ctorStyles.iconBtnSm, opacity: i === ft.fields.length - 1 ? .3 : 1 }} aria-label="Ниже"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><polyline points="6 9 12 15 18 9"></polyline></svg></button>
                <button type="button" onClick={() => delField(i)} style={{ ...ctorStyles.iconBtnSm, color: '#f04e62' }} aria-label="Удалить поле">{Icon.x}</button>
              </div>
            </div>
            <FSeg label="Тип" value={f.type} onChange={(v) => patchField(i, { type: v })} options={FIELD_KINDS.map((k) => ({ v: k.v, l: k.l }))} />
            {f.type === 'select' && (
              <OptionsListEditor label="Варианты" options={f.options} onChange={(opts) => patchField(i, { options: opts })} placeholder="Матовая" />
            )}
          </div>
        ))}
        <button type="button" onClick={addField} style={{ ...ctorStyles.btnDashed, alignSelf: 'flex-start' }}>{Icon.plus} Добавить поле</button>
      </InspectorSection>
    </InspectorDrawer>
  );
}

/* ============ СВЯЗИ С РАБОТАМИ (двусторонний просмотр BOM) ============ */
function BindingsPanel({ bindings, setBindings, library, catalog }) {
  const [mode, setMode] = matUseState('byWork');
  const nodeMap = matUseMemo(() => MDX.nodeLabelMap(catalog), [catalog]);
  const removeBinding = (nodeId, bid) => setBindings((all) => ({ ...all, [nodeId]: (all[nodeId] || []).filter((b) => b.id !== bid) }));

  const nodesWithBom = Object.keys(bindings || {}).filter((id) => (bindings[id] || []).length);
  const materialsUsed = library.filter((m) => MDX.usageOf(bindings, m.id).length);

  return (
    <div className="bom-panel">
      <header className="bom-toolbar">
        <p className="bom-lead">Привязки по умолчанию. Добавляются в инспекторе узла: Иерархия → Материалы по умолчанию.</p>
        <div className="bom-mode" role="tablist" aria-label="Вид карты привязок">
          {[{ v: 'byWork', l: 'По работам' }, { v: 'byMaterial', l: 'По материалам' }].map((o) => (
            <button
              key={o.v}
              type="button"
              role="tab"
              aria-selected={mode === o.v}
              className={'bom-mode-btn' + (mode === o.v ? ' on' : '')}
              onClick={() => setMode(o.v)}
            >
              {o.l}
            </button>
          ))}
        </div>
      </header>

      <div className="bom-grid">
        {mode === 'byWork' && nodesWithBom.length === 0 && <BomEmpty />}
        {mode === 'byMaterial' && materialsUsed.length === 0 && <BomEmpty />}

        {mode === 'byWork' && nodesWithBom.map((nodeId) => {
          const meta = nodeMap[nodeId] || { label: nodeId, trail: '', color: '#8a817a' };
          const rows = bindings[nodeId];
          return (
            <article key={nodeId} className="bom-card">
              <header className="bom-card-head">
                <div style={{ minWidth: 0 }}>
                  <h3 className="bom-card-title">{meta.label}</h3>
                  {meta.trail && <p className="bom-card-trail">{meta.trail}</p>}
                </div>
                <span className="bom-card-count">{rows.length}</span>
              </header>
              <ul className="bom-rows">
                {rows.map((b) => {
                  const m = MDX.materialById(library, b.materialId);
                  const formula = b.qtyFormula === 'fixed' ? 'фикс.' : b.qtyFormula;
                  return (
                    <li key={b.id} className="bom-row">
                      <span className={'bom-row-name' + (m ? '' : ' is-missing')}>{m ? m.name : 'Позиция удалена'}</span>
                      <span className="bom-row-meta">{b.defaultQty} {b.unit} · {formula}</span>
                      <button type="button" className="bom-row-del" onClick={() => removeBinding(nodeId, b.id)} title="Убрать привязку" aria-label="Убрать привязку">{Icon.x}</button>
                    </li>
                  );
                })}
              </ul>
            </article>
          );
        })}

        {mode === 'byMaterial' && materialsUsed.map((m) => {
          const usage = MDX.usageOf(bindings, m.id);
          return (
            <article key={m.id} className="bom-card">
              <header className="bom-card-head">
                <div style={{ minWidth: 0 }}>
                  <h3 className="bom-card-title">{m.name}</h3>
                  {m.sku && <p className="bom-card-trail">{m.sku}</p>}
                </div>
                <span className="bom-card-count">{usage.length}</span>
              </header>
              <ul className="bom-ref-list">
                {usage.map((u, i) => {
                  const meta = nodeMap[u.nodeId] || { label: u.nodeId };
                  return (
                    <li key={i} className="bom-ref">
                      <span className="bom-ref-node">{meta.label}</span>
                      <span className="bom-ref-qty">{u.binding.defaultQty} {u.binding.unit}</span>
                    </li>
                  );
                })}
              </ul>
            </article>
          );
        })}
      </div>
    </div>
  );
}

function BomEmpty() {
  return (
    <div className="bom-empty">
      <p className="bom-empty-title">Привязок пока нет</p>
      <p className="bom-empty-hint">Откройте узел во вкладке «Иерархия» и добавьте материалы в блоке «Материалы по умолчанию».</p>
    </div>
  );
}

/* ============ ПОДБОРКИ / ПРЕСЕТЫ ============ */
const PRESET_KIND_L = { rough: 'Черн.', finish: 'Чист.', mixed: 'Микс' };

function MatPresetsPanel({ matPresets, setMatPresets, library }) {
  const [editId, setEditId] = matUseState(null);
  const editing = matPresets.find((p) => p.id === editId) || null;
  const update = (id, patch) => setMatPresets((ps) => ps.map((p) => p.id === id ? { ...p, ...patch } : p));
  const add = () => {const p = { id: MDX.uid('mp'), name: 'Новая подборка', desc: '', kind: 'mixed', scope: 'Объект', items: [], requiresClientApproval: false };setMatPresets((ps) => [...ps, p]);setEditId(p.id);};
  const remove = (id) => {if (confirm('Удалить подборку?')) {setMatPresets((ps) => ps.filter((p) => p.id !== id));if (editId === id) setEditId(null);}};
  const dup = (id) => {const s = matPresets.find((p) => p.id === id);if (!s) return;const c = MDX.clone(s);c.id = MDX.uid('mp');c.name = s.name + ' (копия)';setMatPresets((ps) => [...ps, c]);};
  const stopCardMenu = (e) => e.stopPropagation();

  const sumClient = (p) => p.items.reduce((a, id) => {const m = MDX.materialById(library, id);return a + (m ? m.clientUnitPrice || m.unitPrice || 0 : 0);}, 0);

  return (
    <div className="mps-panel">
      <header className="mps-toolbar">
        <p className="mps-lead">Готовые комплекты материалов для добавления в смету одним действием.</p>
        <button type="button" className="mps-add-btn" onClick={add}>{Icon.plus} Подборка</button>
      </header>

      <div className="mps-grid">
        {matPresets.length === 0 && <PresetsEmpty onAdd={add} />}
        {matPresets.map((p) => {
          const kindLabel = PRESET_KIND_L[p.kind] || p.kind;
          const metaParts = [p.scope, kindLabel];
          if (p.requiresClientApproval) metaParts.push('согласование');
          return (
            <article
              key={p.id}
              className="mps-card"
              role="button"
              tabIndex={0}
              onClick={() => setEditId(p.id)}
              onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); setEditId(p.id); } }}
            >
              <div className="mps-card-top">
                <h3 className="mps-card-title">{p.name}</h3>
                <div onClick={stopCardMenu} onKeyDown={stopCardMenu}>
                  <KebabMenu items={[
                    { icon: pencilIcon, label: 'Редактировать', onClick: () => setEditId(p.id) },
                    { icon: Icon.copy, label: 'Дублировать', onClick: () => dup(p.id) },
                    { icon: Icon.trash, label: 'Удалить', onClick: () => remove(p.id), danger: true },
                  ]} />
                </div>
              </div>
              {p.desc && <p className="mps-card-desc">{p.desc}</p>}
              <div className="mps-card-meta">{metaParts.join(' · ')}</div>
              <div className="mps-card-stats">{p.items.length} поз. · ≈ {mmoney(sumClient(p))}</div>
            </article>
          );
        })}
      </div>

      {editing && <MatPresetDrawer preset={editing} library={library} onChange={(patch) => update(editing.id, patch)} onClose={() => setEditId(null)} />}
    </div>
  );
}

function PresetsEmpty({ onAdd }) {
  return (
    <div className="mps-empty">
      <p className="mps-empty-title">Подборок пока нет</p>
      <p className="mps-empty-hint">Соберите комплект материалов, который менеджер сможет добавить в смету целиком.</p>
      <button type="button" className="mps-add-btn" onClick={onAdd}>{Icon.plus} Подборка</button>
    </div>
  );
}

function MatPresetDrawer({ preset, library, onChange, onClose }) {
  const p = preset;
  const [q, setQ] = matUseState('');
  const inSet = new Set(p.items);
  const toggle = (id) => onChange({ items: inSet.has(id) ? p.items.filter((x) => x !== id) : [...p.items, id] });
  const pickList = library.filter((m) => !q || (m.name + ' ' + m.sku).toLowerCase().includes(q.toLowerCase()));
  const chosen = p.items.map((id) => MDX.materialById(library, id)).filter(Boolean);
  const sumBuy = chosen.reduce((a, m) => a + (m.unitPrice || 0), 0);
  const sumClient = chosen.reduce((a, m) => a + (m.clientUnitPrice || m.unitPrice || 0), 0);

  return (
    <InspectorDrawer title="Подборка материалов" onClose={onClose} width={600}>
      <div className="ins-tabpanel-grid">
        <InspectorSection title="Описание">
          <FText label="Название" value={p.name} onChange={(v) => onChange({ name: v })} />
          <FText label="Описание" value={p.desc} onChange={(v) => onChange({ desc: v })} multiline placeholder="Когда применять подборку" />
        </InspectorSection>
        <InspectorSection title="Область и класс">
          <FText label="Область" value={p.scope} onChange={(v) => onChange({ scope: v })} placeholder="Санузел / Комната / Объект" />
          <FSeg label="Класс" value={p.kind} onChange={(v) => onChange({ kind: v })} options={[{ v: 'rough', l: 'Черн.' }, { v: 'finish', l: 'Чист.' }, { v: 'mixed', l: 'Микс' }]} />
          <FToggle label="Требует согласования клиентом" value={p.requiresClientApproval} onChange={(v) => onChange({ requiresClientApproval: v })} />
        </InspectorSection>
      </div>

      <InspectorSection title={'Состав · ' + chosen.length} full lead={'Закуп ' + mmoney(sumBuy) + ' · клиенту ' + mmoney(sumClient)}>
        <div className="ins-search-inline">
          <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
          <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Поиск позиции для добавления" aria-label="Поиск позиции" />
        </div>
        <div className="ins-picker-list">
          {pickList.map((m) => {
            const on = inSet.has(m.id);
            return (
              <label key={m.id} className={'ins-picker-row' + (on ? ' on' : '')} onClick={() => toggle(m.id)}>
                {window.Check && React.createElement(window.Check, { on, onClick: () => toggle(m.id) })}
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 13, fontWeight: 600, color: 'var(--foreground)', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{m.name}</div>
                  <div style={{ fontSize: 11.5, color: 'var(--text-secondary)' }}>{(MDX.subtypeById(m.subtype) || {}).name} · {mmoney(m.clientUnitPrice || m.unitPrice)} / {m.unit}</div>
                </div>
                <MaterialKindChip kind={m.materialKind} small />
              </label>
            );
          })}
          {pickList.length === 0 && <InspectorEmptyState title="Ничего не найдено" hint="Измените запрос или добавьте позиции в библиотеку." />}
        </div>
      </InspectorSection>
    </InspectorDrawer>
  );
}

/* ============ ИМПОРТ / ЭКСПОРТ ============ */
const IMPORT_TEMPLATES = [
{ id: 'A', name: 'Библиотека позиций', file: 'material-library-template.csv', cols: 'material_kind, name, sku, unit, unit_price, consumption_rate, …' },
{ id: 'B', name: 'BOM одного узла', file: 'catalog-bom-node-template.csv', cols: 'catalog_node_slug, material_kind, default_qty, qty_formula, …' },
{ id: 'C', name: 'Bulk BOM', file: 'catalog-bom-bulk-template.csv', cols: 'node_slug, material_slug, qty, formula, required, …' }];

const PREVIEW_ROWS = [
{ status: 'ok', name: 'Грунт бетоноконтакт', detail: 'sku PR-BK · 220 ₽/л · расход 0.3 л/м²' },
{ status: 'ok', name: 'Шпатлёвка финишная Knauf', detail: 'sku KN-FIN · меш 25 кг · 540 ₽' },
{ status: 'warning', name: 'Краска фасадная', detail: 'категория не найдена → «Отделочные краски» по умолчанию' },
{ status: 'warning', name: 'Тёплый пол кабель', detail: 'тип полей не указан → импорт без характеристик' },
{ status: 'error', name: '(пустая строка 14)', detail: 'нет name и sku — строка будет пропущена' }];

const ST_META = {
  ok: { label: 'ok' },
  warning: { label: 'warn' },
  error: { label: 'err' }
};

const EXPORT_ITEMS = [
  { l: 'Вся библиотека позиций', s: null, f: 'library.csv', countKey: 'library' },
  { l: 'BOM узла каталога', s: 'один узел', f: 'bom-node.csv' },
  { l: 'BOM ветки каталога', s: 'фаза или этап', f: 'bom-branch.csv' },
  { l: 'Спецификация для клиента', s: 'без закупочных цен', f: 'client-spec.csv' },
  { l: 'Заявка поставщику', s: 'по поставщикам', f: 'supplier-order.csv' },
];

function ImportExportPanel({ library }) {
  const [tpl, setTpl] = matUseState('A');
  const [staged, setStaged] = matUseState(false);
  const [toast, setToast] = matUseState(null);
  const counts = PREVIEW_ROWS.reduce((a, r) => { a[r.status] = (a[r.status] || 0) + 1; return a; }, {});
  const fire = (text) => { setToast(text); setTimeout(() => setToast(null), 2600); };
  const activeTpl = IMPORT_TEMPLATES.find((t) => t.id === tpl) || IMPORT_TEMPLATES[0];
  const summary = ['ok', 'warning', 'error'].map((s) => (counts[s] ? counts[s] + ' ' + ST_META[s].label : null)).filter(Boolean).join(' · ');

  return (
    <div className="mio-layout">
      <section className="mio-col" aria-labelledby="mio-import-heading">
        <h2 id="mio-import-heading" className="mio-heading">Импорт</h2>
        <p className="mio-lead">CSV или XLSX: предпросмотр, затем добавление в черновик.</p>

        <div className="mio-tpl-list" role="radiogroup" aria-label="Шаблон импорта">
          {IMPORT_TEMPLATES.map((t) => {
            const on = tpl === t.id;
            return (
              <div key={t.id} className={'mio-tpl' + (on ? ' on' : '')}>
                <button
                  type="button"
                  role="radio"
                  aria-checked={on}
                  className="mio-tpl-select"
                  onClick={() => { setTpl(t.id); setStaged(false); }}
                >
                  <span className="mio-tpl-name">{t.name}</span>
                  <span className="mio-tpl-cols">{t.cols}</span>
                </button>
                <button
                  type="button"
                  className="mio-tpl-dl"
                  title={'Скачать ' + t.file}
                  aria-label={'Скачать ' + t.file}
                  onClick={() => fire('Шаблон ' + t.file + ' скачан')}
                >
                  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
                </button>
              </div>
            );
          })}
        </div>

        <button type="button" className="mio-drop" onClick={() => setStaged(true)}>
          <span className="mio-drop-label">Выбрать файл</span>
          <span className="mio-drop-file">{activeTpl.file}</span>
        </button>

        {staged && (
          <div className="mio-preview ctor-fade">
            {summary && <p className="mio-summary">{summary}</p>}
            <div className="mio-preview-list">
              {PREVIEW_ROWS.map((r, i) => (
                <div key={i} className={'mio-preview-row' + (r.status === 'error' ? ' is-error' : '')}>
                  <span className={'mio-st ' + r.status}>{ST_META[r.status].label}</span>
                  <span className="mio-preview-name">{r.name}</span>
                  <span className="mio-preview-detail">{r.detail}</span>
                </div>
              ))}
            </div>
            <button
              type="button"
              className="mio-apply"
              disabled={!counts.ok}
              onClick={() => { setStaged(false); fire((counts.ok || 0) + ' позиций добавлено в черновик'); }}
            >
              Применить {counts.ok || 0} в черновик
            </button>
          </div>
        )}
      </section>

      <section className="mio-col" aria-labelledby="mio-export-heading">
        <h2 id="mio-export-heading" className="mio-heading">Экспорт</h2>
        <p className="mio-lead">Выгрузка библиотеки и спецификаций в CSV или XLSX.</p>
        <div className="mio-export-list">
          {EXPORT_ITEMS.map((e, i) => (
            <button key={i} type="button" className="mio-export-row" onClick={() => fire(e.f + ' сформирован')}>
              <span className="mio-export-label">{e.l}</span>
              <span className="mio-export-sub">
                {e.countKey === 'library' ? library.length + ' позиций' : e.s}
              </span>
            </button>
          ))}
        </div>
      </section>

      {toast && <div className="mio-toast ctor-fade" role="status">{toast}</div>}
    </div>
  );
}

Object.assign(window, { MaterialsConstructor });