/* ctor-measures.jsx — Tab «Шаблоны замеров» (measure_field sections: fields · mini_survey · room_group) · KSH-350 */
const { useState: msUseState, useEffect: msUseEffect, useRef: msUseRef } = React;
const msCreatePortal = ReactDOM.createPortal;

const MS_SECTION_KINDS = [
  { v: 'fields', l: 'Общие поля', icon: '📋', hint: 'Плоские поля формы' },
  { v: 'mini_survey', l: 'Мини-опросник', icon: '💬', hint: 'Блок вопросов до помещений' },
  { v: 'room_group', l: 'Группа помещений', icon: '🏠', hint: 'Повторяемые комнаты · фото и размеры' },
];

const MS_FIELD_TYPES = [
  { v: 'composite', l: 'Набор значений', icon: '⊞' },
  { v: 'text', l: 'Текст', icon: 'T' },
  { v: 'number', l: 'Число', icon: '#' },
  { v: 'textarea', l: 'Текст (многостр.)', icon: '¶' },
  { v: 'checkbox', l: 'Флаг', icon: '☑' },
  { v: 'select', l: 'Список', icon: '▾' },
  { v: 'chips', l: 'Chips', icon: '◫' },
  { v: 'media', l: 'Фото / видео', icon: '📷' },
];

const MS_SUB_FIELD_TYPES = MS_FIELD_TYPES.filter((t) => t.v !== 'composite');
const MS_COMPOSITE_MAX = 10;

function msSubUid() {
  return 'sf-' + Math.random().toString(36).slice(2, 7);
}

function msDefaultCompositeFields() {
  return [
    { id: msSubUid(), type: 'number', label: 'Ширина', placeholder: '0', unit: 'м', step: 0.01 },
    { id: msSubUid(), type: 'number', label: 'Высота', placeholder: '0', unit: 'м', step: 0.01 },
  ];
}

function msEnsureComposite(field) {
  if (field.type !== 'composite') return field;
  return {
    layout: 'row',
    fields: msDefaultCompositeFields(),
    ...field,
    fields: (field.fields && field.fields.length) ? field.fields : msDefaultCompositeFields(),
  };
}

const msStyles = {
  card: (active) => ({ padding: '10px 12px', cursor: 'pointer', borderRadius: 10, marginBottom: 2, background: active ? '#fff0e6' : '#fff', border: active ? '.5px solid #e8793a40' : '.5px solid transparent', transition: 'all .1s' }),
  sectionCard: { padding: '10px 12px', background: '#fafafa', borderRadius: 10, border: '.5px solid #ece5da' },
  sectionHead: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap', marginBottom: 8 },
  kindBadge: (kind) => {
    const meta = MS_SECTION_KINDS.find((k) => k.v === kind) || {};
    const colors = { fields: '#5aad6e', mini_survey: '#8b5cf6', room_group: '#e8793a' };
    const c = colors[kind] || '#8a817a';
    return { display: 'inline-flex', alignItems: 'center', gap: 6, padding: '5px 10px', borderRadius: 980, fontSize: 12, fontWeight: 700, color: c, background: c + '18', border: '.5px solid ' + c + '33', flexShrink: 0 };
  },
};

function loadMeasures() {
  const M = window.MeasureFieldTemplateMock;
  if (!M) return [];
  return M.listSchemas();
}

function saveMeasures(schemas) {
  const M = window.MeasureFieldTemplateMock;
  if (!M) return;
  const obj = {};
  schemas.forEach((s) => { obj[s.id] = M.normalizeSchema(s); });
  M.saveSchemasObject(obj);
}

function editableSections(schema) {
  return (schema.sections || []).filter((s) => s.kind !== 'readonly_header');
}

function sectionKindMeta(kind) {
  return MS_SECTION_KINDS.find((k) => k.v === kind) || { l: kind, icon: '·' };
}

function MsPreviewField({ field, inline }) {
  const inputOnly = inline || field.type === 'checkbox';
  const MediaPreview = window.MsMediaFieldPreview;
  return (
    <div style={{ marginBottom: inline ? 0 : 8 }}>
      {!inputOnly && (
        <div style={{ fontSize: 13, fontWeight: 500, color: '#1a1714', marginBottom: 5 }}>
          {field.label}
          {field.required && <span style={{ fontSize: 10, fontWeight: 700, color: '#f04e62', marginLeft: 6 }}>*</span>}
        </div>
      )}
      {field.type === 'text' && (
        <input disabled placeholder={field.placeholder || 'Введите значение…'} style={{ width: '100%', padding: '8px 11px', borderRadius: 9, border: '.5px solid #e4ddd2', background: '#fff', fontFamily: 'inherit', fontSize: 13 }} />
      )}
      {field.type === 'textarea' && (
        <textarea disabled rows={2} placeholder={field.placeholder || ''} style={{ width: '100%', padding: '8px 11px', borderRadius: 9, border: '.5px solid #e4ddd2', background: '#fff', fontFamily: 'inherit', fontSize: 13, resize: 'none' }} />
      )}
      {field.type === 'number' && (
        <input type="number" disabled placeholder="0" style={{ width: '50%', padding: '8px 11px', borderRadius: 9, border: '.5px solid #e4ddd2', background: '#fff', fontFamily: 'inherit', fontSize: 13 }} />
      )}
      {field.type === 'checkbox' && (
        <label style={{ display: 'flex', alignItems: 'center', gap: 8, cursor: 'default' }}>
          <input type="checkbox" disabled style={{ width: 16, height: 16 }} />
          <span style={{ fontSize: 13, color: '#6b6259' }}>{field.label}</span>
        </label>
      )}
      {field.type === 'select' && (
        <select disabled style={{ width: '100%', padding: '8px 11px', borderRadius: 9, border: '.5px solid #e4ddd2', background: '#fff', fontFamily: 'inherit', fontSize: 13 }}>
          <option>— выберите —</option>
          {(field.options || []).map((o, i) => <option key={i}>{o}</option>)}
        </select>
      )}
      {field.type === 'chips' && (
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
          {(field.options || ['Вариант']).map((o, i) => (
            <span key={i} style={{ padding: '5px 10px', borderRadius: 980, border: '.5px solid #ece5da', background: '#fff', fontSize: 12 }}>{o}</span>
          ))}
        </div>
      )}
      {field.type === 'media' && MediaPreview && (
        <MediaPreview field={field} />
      )}
      {field.type === 'composite' && (
        <div style={{
          display: 'grid',
          gridTemplateColumns: (field.layout || 'row') === 'row' ? 'repeat(auto-fit, minmax(96px, 1fr))' : '1fr',
          gap: 8,
          padding: '10px 12px',
          borderRadius: 10,
          background: '#fff',
          border: '.5px solid #ece5da',
        }}>
          {(field.fields || []).map((sub) => (
            <div key={sub.id}>
              <div style={{ fontSize: 11, fontWeight: 600, color: '#8a817a', marginBottom: 4 }}>
                {sub.label || 'Подполе'}{sub.unit ? ' · ' + sub.unit : ''}
              </div>
              <MsPreviewField field={sub} inline />
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

function MsCompositeSubEditor({ sub, idx, total, onPatch, onDelete, onMove }) {
  const MediaConfig = window.MsMediaFieldConfigPanel;
  return (
    <div style={{
      display: 'grid',
      gridTemplateColumns: 'minmax(108px, 0.9fr) minmax(120px, 1.2fr) minmax(72px, 0.6fr) auto',
      gap: 8,
      alignItems: 'start',
      padding: '10px 12px',
      background: '#fff',
      borderRadius: 10,
      border: '.5px solid #ece5da',
    }}>
      <select value={sub.type} onChange={(e) => onPatch(sub.id, { type: e.target.value })}
        style={{ ...insStyles.field, padding: '7px 9px', fontSize: 12.5 }}>
        {MS_SUB_FIELD_TYPES.map((t) => <option key={t.v} value={t.v}>{t.icon} {t.l}</option>)}
      </select>
      <input value={sub.label || ''} onChange={(e) => onPatch(sub.id, { label: e.target.value })} placeholder="Подпись"
        style={{ ...insStyles.field, fontSize: 12.5 }} />
      <input value={sub.unit || ''} onChange={(e) => onPatch(sub.id, { unit: e.target.value })} placeholder="ед."
        style={{ ...insStyles.field, fontSize: 12.5 }} title="Единица: м, м², шт." />
      <div style={{ display: 'flex', gap: 3, paddingTop: 4 }}>
        <button type="button" onClick={() => onMove(sub.id, -1)} disabled={idx === 0} style={{ ...ctorStyles.iconBtnSm, opacity: idx === 0 ? .3 : 1 }} aria-label="Выше">
          <svg width="11" height="11" 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={() => onMove(sub.id, 1)} disabled={idx === total - 1} style={{ ...ctorStyles.iconBtnSm, opacity: idx === total - 1 ? .3 : 1 }} aria-label="Ниже">
          <svg width="11" height="11" 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={() => onDelete(sub.id)} style={{ ...ctorStyles.iconBtnSm, color: '#f04e62' }} aria-label="Удалить подполе">{Icon.x}</button>
      </div>
      {(sub.type === 'select' || sub.type === 'chips') && (
        <OptionsListEditor label="Варианты" options={sub.options} onChange={(opts) => onPatch(sub.id, { options: opts })}
          placeholder="Вариант" compact style={{ gridColumn: '1 / -1' }} />
      )}
      {(sub.type === 'text' || sub.type === 'number') && (
        <input value={sub.placeholder || ''} onChange={(e) => onPatch(sub.id, { placeholder: e.target.value })} placeholder="Placeholder подполя"
          style={{ ...insStyles.field, gridColumn: '1 / -1', fontSize: 12 }} />
      )}
      {sub.type === 'media' && MediaConfig && (
        <div style={{ gridColumn: '1 / -1' }}>
          <MediaConfig field={sub} onPatch={onPatch} />
        </div>
      )}
    </div>
  );
}

function MsCompositeEditor({ field, onPatch }) {
  const comp = msEnsureComposite(field);
  const subs = comp.fields || [];
  const setSubs = (fn) => onPatch(comp.id, { fields: fn(subs) });
  const patchSub = (id, p) => setSubs((list) => list.map((s) => (s.id === id ? { ...s, ...p } : s)));
  const delSub = (id) => setSubs((list) => list.filter((s) => s.id !== id));
  const moveSub = (id, dir) => setSubs((list) => {
    const idx = list.findIndex((s) => s.id === id);
    if (idx < 0) return list;
    const next = [...list];
    const swap = idx + dir;
    if (swap < 0 || swap >= next.length) return list;
    [next[idx], next[swap]] = [next[swap], next[idx]];
    return next;
  });
  const addSub = () => {
    if (subs.length >= MS_COMPOSITE_MAX) return;
    setSubs((list) => [...list, { id: msSubUid(), type: 'number', label: '', unit: '', placeholder: '' }]);
  };

  return (
    <div className="ms-composite-editor">
      <div className="ms-composite-editor-head">
        <div>
          <div className="ms-composite-editor-title">Подполя</div>
          <div className="ms-composite-editor-sub">До {MS_COMPOSITE_MAX} значений</div>
        </div>
        <div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
          <span style={{ fontSize: 12, color: '#8a817a', fontWeight: 600 }}>Раскладка</span>
          <div style={{ display: 'flex', gap: 2, padding: 3, background: '#ece5da', borderRadius: 9 }}>
            {[{ v: 'row', l: 'В строку' }, { v: 'stack', l: 'Столбцом' }].map((o) => (
              <button key={o.v} type="button" onClick={() => onPatch(comp.id, { layout: o.v })}
                style={{
                  padding: '5px 10px', borderRadius: 7, border: 'none', cursor: 'pointer', fontFamily: 'inherit',
                  fontSize: 12, fontWeight: 600,
                  background: (comp.layout || 'row') === o.v ? '#fff' : 'transparent',
                  color: (comp.layout || 'row') === o.v ? '#1a1714' : '#8a817a',
                  boxShadow: (comp.layout || 'row') === o.v ? '0 1px 2px rgba(26,23,20,.08)' : 'none',
                }}>
                {o.l}
              </button>
            ))}
          </div>
        </div>
      </div>
      <div className="ms-composite-sub-list">
        {subs.map((sub, idx) => (
          <MsCompositeSubEditor key={sub.id} sub={sub} idx={idx} total={subs.length}
            onPatch={patchSub} onDelete={delSub} onMove={moveSub} />
        ))}
      </div>
      <button type="button" onClick={addSub} disabled={subs.length >= MS_COMPOSITE_MAX}
        className="ms-section-add-field" style={{ marginTop: 6, opacity: subs.length >= MS_COMPOSITE_MAX ? .45 : 1 }}>
        {Icon.plus} Подполе ({subs.length}/{MS_COMPOSITE_MAX})
      </button>
    </div>
  );
}

function MsPreviewSection({ section }) {
  if (section.kind === 'readonly_header') {
    return (
      <div className="ms-preview-block ms-preview-readonly">
        <div className="ms-preview-block-title">{section.title || 'Объект'}</div>
        {(section.fields || []).map((f) => (
          <div key={f.id} style={{ display: 'flex', justifyContent: 'space-between', gap: 8, fontSize: 12.5, marginBottom: 4 }}>
            <span style={{ color: '#8a817a' }}>{f.label}</span>
            <span style={{ color: '#1a1714', fontWeight: 600 }}>из профиля</span>
          </div>
        ))}
      </div>
    );
  }
  if (section.kind === 'room_group') {
    const label = (section.labelTemplate || 'Помещение {n}').replace('{n}', '1');
    return (
      <div className="ms-preview-block">
        <div className="ms-preview-block-title">{section.title || 'Помещения'}</div>
        <div className="ms-preview-room">
          <div className="ms-preview-room-label">{label}</div>
          {(section.fields || []).map((f) => <MsPreviewField key={f.id} field={f} />)}
        </div>
        <div className="ms-preview-room-add">{section.addLabel || '+ Добавить помещение'}</div>
      </div>
    );
  }
  return (
    <div className="ms-preview-block">
      <div className="ms-preview-block-title">{section.title || sectionKindMeta(section.kind).l}</div>
      {(section.fields || []).map((f) => <MsPreviewField key={f.id} field={f} />)}
    </div>
  );
}

function MsFieldEditor({ field, idx, total, onPatch, onDelete, onMove }) {
  const msDefaultMediaField = window.msDefaultMediaField;
  const MsMediaFieldConfigPanel = window.MsMediaFieldConfigPanel;
  const setType = (nextType) => {
    if (nextType === 'composite') {
      onPatch(field.id, msEnsureComposite({ ...field, type: 'composite' }));
      return;
    }
    if (nextType === 'media' && msDefaultMediaField) {
      onPatch(field.id, msDefaultMediaField({ ...field, type: 'media', fields: undefined, layout: undefined }));
      return;
    }
    onPatch(field.id, { type: nextType, fields: undefined, layout: undefined });
  };
  const hasOptions = field.type === 'select' || field.type === 'chips';

  return (
    <div className="ms-field-editor">
      <div className="ms-field-editor-row">
        <select className="ms-field-editor-type" value={field.type} onChange={(e) => setType(e.target.value)} aria-label="Тип поля">
          {MS_FIELD_TYPES.map((t) => <option key={t.v} value={t.v}>{t.icon} {t.l}</option>)}
        </select>
        <input className="ms-field-editor-label" value={field.label} onChange={(e) => onPatch(field.id, { label: e.target.value })} placeholder="Название поля" />
        <label className="ms-field-editor-flag is-req" title="Обязательное поле">
          <input type="checkbox" checked={!!field.required} onChange={(e) => onPatch(field.id, { required: e.target.checked })} style={{ accentColor: '#f04e62' }} />
          <span>*</span>
        </label>
        <div className="ms-field-editor-actions">
          <button type="button" onClick={() => onMove(field.id, -1)} disabled={idx === 0} style={{ ...ctorStyles.iconBtnSm, opacity: idx === 0 ? .3 : 1 }} aria-label="Выше">
            <svg width="11" height="11" 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={() => onMove(field.id, 1)} disabled={idx === total - 1} style={{ ...ctorStyles.iconBtnSm, opacity: idx === total - 1 ? .3 : 1 }} aria-label="Ниже">
            <svg width="11" height="11" 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={() => onDelete(field.id)} style={{ ...ctorStyles.iconBtnSm, color: '#f04e62' }} aria-label="Удалить">{Icon.x}</button>
        </div>
      </div>
      {hasOptions && (
        <OptionsListEditor label="Варианты" options={field.options} onChange={(opts) => onPatch(field.id, { options: opts })}
          placeholder="Вариант" compact className="ms-field-editor-extra" />
      )}
      {(field.type === 'text' || field.type === 'number') && (
        <input className="ms-field-editor-extra" value={field.placeholder || ''} onChange={(e) => onPatch(field.id, { placeholder: e.target.value })} placeholder="Placeholder (необязательно)" />
      )}
      {field.type === 'media' && MsMediaFieldConfigPanel && (
        <MsMediaFieldConfigPanel field={field} onPatch={onPatch} />
      )}
      {field.type === 'composite' && (
        <MsCompositeEditor field={field} onPatch={onPatch} />
      )}
    </div>
  );
}

function MsSectionEditor({ section, secIdx, totalSecs, onChange, onDelete, onMove }) {
  const uid = () => 'f-' + Math.random().toString(36).slice(2, 7);
  const meta = sectionKindMeta(section.kind);
  const setFields = (fn) => onChange({ ...section, fields: fn(section.fields || []) });
  const patchField = (id, p) => setFields((fields) => fields.map((f) => (f.id === id ? { ...f, ...p } : f)));
  const delField = (id) => setFields((fields) => fields.filter((f) => f.id !== id));
  const moveField = (id, dir) => setFields((fields) => {
    const idx = fields.findIndex((f) => f.id === id);
    if (idx < 0) return fields;
    const next = [...fields];
    const swap = idx + dir;
    if (swap < 0 || swap >= next.length) return fields;
    [next[idx], next[swap]] = [next[swap], next[idx]];
    return next;
  });
  const addField = () => setFields((fields) => [...fields, { id: uid(), type: 'text', label: '', required: false }]);

  return (
    <div className="ms-section-block">
      <div className="ms-section-head">
        <span style={msStyles.kindBadge(section.kind)}>{meta.icon} {meta.l}</span>
        <input value={section.title || ''} onChange={(e) => onChange({ ...section, title: e.target.value })} placeholder="Заголовок секции"
          style={{ ...insStyles.field, flex: 1, minWidth: 140, fontWeight: 600 }} />
        <div style={{ display: 'flex', gap: 4, marginLeft: 'auto' }}>
          <button onClick={() => onMove(secIdx, -1)} disabled={secIdx === 0} style={{ ...ctorStyles.iconBtnSm, opacity: secIdx === 0 ? .3 : 1 }}>
            <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><polyline points="18 15 12 9 6 15"></polyline></svg>
          </button>
          <button onClick={() => onMove(secIdx, 1)} disabled={secIdx === totalSecs - 1} style={{ ...ctorStyles.iconBtnSm, opacity: secIdx === totalSecs - 1 ? .3 : 1 }}>
            <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5"><polyline points="6 9 12 15 18 9"></polyline></svg>
          </button>
          <button onClick={onDelete} style={{ ...ctorStyles.iconBtnSm, color: '#f04e62' }} title="Удалить секцию">{Icon.x}</button>
        </div>
      </div>
      {section.kind === 'room_group' && (
        <div className="ms-room-group-meta">
          <label style={{ fontSize: 12.5, color: '#6b6259' }}>
            Подпись элемента
            <input value={section.labelTemplate || ''} onChange={(e) => onChange({ ...section, labelTemplate: e.target.value })}
              placeholder="Помещение {n}"
              style={{ ...insStyles.field, marginTop: 4, fontSize: 12.5 }} />
          </label>
          <label style={{ fontSize: 12.5, color: '#6b6259' }}>
            Кнопка добавления
            <input value={section.addLabel || ''} onChange={(e) => onChange({ ...section, addLabel: e.target.value })}
              placeholder="+ Добавить помещение"
              style={{ ...insStyles.field, marginTop: 4, fontSize: 12.5 }} />
          </label>
          <label style={{ fontSize: 12.5, color: '#6b6259' }}>
            Мин. помещений
            <input type="number" min={1} max={20} value={section.minItems == null ? 1 : section.minItems}
              onChange={(e) => onChange({ ...section, minItems: +e.target.value })}
              style={{ ...insStyles.field, marginTop: 4, width: 72, fontSize: 12.5 }} />
          </label>
        </div>
      )}
      <div className="ms-field-list">
        {(section.fields || []).map((f, idx) => (
          <MsFieldEditor key={f.id} field={f} idx={idx} total={(section.fields || []).length}
            onPatch={patchField} onDelete={delField} onMove={moveField} />
        ))}
      </div>
      <button type="button" onClick={addField} className="ms-section-add-field">{Icon.plus} Поле</button>
    </div>
  );
}

function MeasureSchemaEditor({ schema, onChange }) {
  const M = window.MeasureFieldTemplateMock;
  const sections = editableSections(schema);

  const setSections = (nextEditable) => {
    const header = (schema.sections || []).find((s) => s.kind === 'readonly_header') || (M ? M.COMMON_HEADER : { kind: 'readonly_header', title: 'Объект', fields: [] });
    onChange({ ...schema, sections: [header, ...nextEditable] });
  };

  const patchSection = (idx, patch) => setSections(sections.map((s, i) => (i === idx ? { ...s, ...patch } : s)));
  const deleteSection = (idx) => setSections(sections.filter((_, i) => i !== idx));
  const moveSection = (idx, dir) => {
    const next = [...sections];
    const swap = idx + dir;
    if (swap < 0 || swap >= next.length) return;
    [next[idx], next[swap]] = [next[swap], next[idx]];
    setSections(next);
  };

  const addSection = (kind) => {
    const M2 = window.MeasureFieldTemplateMock;
    let block;
    if (kind === 'room_group') block = M2 ? M2.blankRoomGroup() : { kind: 'room_group', id: 'rooms-' + Math.random().toString(36).slice(2, 5), title: 'Помещения', fields: [] };
    else if (kind === 'mini_survey') block = M2 ? M2.blankMiniSurvey() : { kind: 'mini_survey', title: 'Мини-опросник', fields: [] };
    else block = M2 ? M2.blankFieldsSection('Общие поля') : { kind: 'fields', title: 'Общие поля', fields: [] };
    setSections([...sections, block]);
  };

  return (
    <div className="ms-schema-editor">
      <div className="ms-schema-main">
        <div className="ms-schema-toolbar">
          <span className="ms-schema-chip" title="readonly_header из профиля сделки">Шапка объекта · из сделки</span>
          <div className="ms-schema-add-row">
            {MS_SECTION_KINDS.map((k) => (
              <button key={k.v} type="button" onClick={() => addSection(k.v)} className="ms-schema-add-btn" title={k.hint}>
                {Icon.plus} {k.l}
              </button>
            ))}
          </div>
        </div>
        <div className="ms-section-list">
          {sections.map((sec, idx) => (
            <MsSectionEditor key={(sec.id || sec.kind) + '-' + idx} section={sec} secIdx={idx} totalSecs={sections.length}
              onChange={(patch) => patchSection(idx, patch)}
              onDelete={() => deleteSection(idx)}
              onMove={moveSection} />
          ))}
        </div>
      </div>
      <aside className="ms-schema-preview" aria-label="Превью анкеты">
        <div className="ms-schema-preview-label">Превью</div>
        <div className="ms-schema-preview-body">
          {(schema.sections || []).map((sec, i) => <MsPreviewSection key={i} section={sec} />)}
          <button type="button" disabled className="ms-schema-preview-submit">Отправить замер</button>
        </div>
      </aside>
    </div>
  );
}

function MeasuresTemplatesPanel({ measures, setMeasures, activeId, setActiveId }) {
  const M = window.MeasureFieldTemplateMock;
  const [panelMode, setPanelMode] = msUseState('structure');
  const active = measures.find((m) => m.id === activeId) || null;

  const refresh = () => {
    const next = loadMeasures();
    setMeasures(next);
    return next;
  };

  const update = (updated) => {
    if (M) M.upsertSchema(updated);
    const next = refresh();
    if (!next.find((m) => m.id === updated.id)) setActiveId(next[0]?.id || null);
  };

  const add = () => {
    const nm = M ? M.createSchema() : { id: 'tpl-measure-new', kind: 'measure_field', title: 'Новый шаблон', sections: [] };
    refresh();
    setActiveId(nm.id);
  };

  const del = (id) => {
    if (!confirm('Удалить шаблон замера?')) return;
    if (M) M.deleteSchema(id);
    const next = refresh();
    if (activeId === id) setActiveId(next[0]?.id || null);
  };

  const dup = (id) => {
    if (!M) return;
    const copy = M.duplicateSchema(id);
    refresh();
    setActiveId(copy.id);
  };

  const sectionCount = (schema) => editableSections(schema).length;
  const fieldCount = (schema) => (M ? M.sectionFieldCount(schema) : editableSections(schema).reduce((s, sec) => s + (sec.fields || []).length, 0));

  return (
    <div className="ms-templates-layout">
      <div className="dp-briefs-sidebar is-measures">
        <section className="dp-sidebar-block is-briefs">
          <div className="dp-sidebar-block-head">
            <span className="dp-sidebar-block-title">Шаблоны</span>
            <button type="button" onClick={add} className="dp-sidebar-icon-btn is-create" title="Создать шаблон" aria-label="Создать шаблон">{Icon.plus}</button>
          </div>
          <div className="dp-sidebar-block-scroll" role="list" aria-label="Шаблоны замера">
            {measures.map((m) => {
              const isActive = m.id === activeId;
              return (
                <div
                  key={m.id}
                  onClick={() => setActiveId(m.id)}
                  className={'dp-sidebar-list-item' + (isActive ? ' is-active' : '')}
                  role="listitem"
                >
                  <div className="dp-sidebar-list-item-main">
                    <div className="dp-sidebar-list-item-title">{m.title || m.id}</div>
                    <div className="dp-sidebar-list-item-meta">
                      {sectionCount(m)} сек. · {fieldCount(m)} полей
                    </div>
                  </div>
                  <div className="dp-sidebar-list-item-actions">
                    <button type="button" onClick={(e) => { e.stopPropagation(); dup(m.id); }} title="Дублировать" className="dp-sidebar-list-icon-btn">{Icon.copy}</button>
                    <button type="button" onClick={(e) => { e.stopPropagation(); del(m.id); }} title="Удалить" className="dp-sidebar-list-icon-btn is-muted">{Icon.x}</button>
                  </div>
                </div>
              );
            })}
            {!measures.length && (
              <button type="button" onClick={add} className="dp-sidebar-create-row" aria-label="Создать шаблон">
                {Icon.plus}
              </button>
            )}
          </div>
        </section>
      </div>
      <div className="ms-templates-main">
        {active
          ? (
            <div className="ms-template-workspace">
              <div className="ms-template-workspace-head">
                <input
                  className="ms-template-title-input"
                  value={active.title || ''}
                  onChange={(e) => update({ ...active, title: e.target.value })}
                  placeholder="Название шаблона"
                  aria-label="Название шаблона"
                />
                <div className="ms-hub-bar ms-template-mode-bar-inline" role="tablist" aria-label="Режим редактора">
                  <button type="button" role="tab" aria-selected={panelMode === 'structure'}
                    className={'ms-hub-tab' + (panelMode === 'structure' ? ' on' : '')}
                    onClick={() => setPanelMode('structure')}>
                    Структура
                  </button>
                  <button type="button" role="tab" aria-selected={panelMode === 'geometry'}
                    className={'ms-hub-tab' + (panelMode === 'geometry' ? ' on' : '')}
                    onClick={() => setPanelMode('geometry')}>
                    Геометрия
                  </button>
                </div>
              </div>
              <div className="ms-template-workspace-body">
                {panelMode === 'structure'
                  ? <MeasureSchemaEditor schema={active} onChange={update} />
                  : (window.MeasurementsWizard
                    ? React.createElement(window.MeasurementsWizard, { embedded: true })
                    : <div className="ms-template-empty">Загрузите measurements-app.jsx для геометрии шаблона.</div>)}
              </div>
            </div>
          )
          : (
            <div className="ms-template-empty ms-template-empty--pick">
              <div className="ms-template-empty-title">Выберите шаблон</div>
              <p>Секции анкеты и шаги геометрии настраиваются для выбранного шаблона.</p>
            </div>
          )}
      </div>
    </div>
  );
}

function initMeasuresPreviewOpen() {
  try {
    const s = new URLSearchParams(window.location.search).get('sub');
    if (s === 'field' || s === 'preview') return true;
  } catch (e) { /* ignore */ }
  return false;
}

function syncMeasuresSubUrl(sub) {
  try {
    const u = new URL(window.location.href);
    u.searchParams.set('tab', 'measures');
    u.searchParams.set('sub', sub);
    window.history.replaceState({}, '', u);
  } catch (e) { /* ignore */ }
}

function MeasuresFieldPreviewDialog({ measures, activeTemplateId, onSelectTemplate, open, onClose }) {
  const dlgRef = msUseRef(null);
  const titleId = 'ms-field-preview-title';
  const active = measures.find((m) => m.id === activeTemplateId) || measures[0] || null;
  const schemaId = active?.id || 'tpl-measure-res-2';
  const iframeSrc = 'lk/field-measure.html?deal=D-404&previewSchema=' + encodeURIComponent(schemaId);

  msUseEffect(() => {
    const dlg = dlgRef.current;
    if (!dlg) return;
    if (open) {
      if (!dlg.open) dlg.showModal();
    } else if (dlg.open) {
      dlg.close();
    }
  }, [open]);

  if (typeof document === 'undefined') return null;

  return msCreatePortal(
    <dialog
      ref={dlgRef}
      className="ms-preview-dialog"
      aria-labelledby={titleId}
      onCancel={(e) => { e.preventDefault(); onClose(); }}
      onClose={onClose}
      onClick={(e) => { if (e.target === dlgRef.current) onClose(); }}
    >
      <div className="ms-preview-dialog-panel" onClick={(e) => e.stopPropagation()}>
        <header className="ms-preview-dialog-head">
          <div className="ms-preview-dialog-head-text">
            <h2 id={titleId}>Превью анкет</h2>
            <p>Как замерщик увидит шаблон на объекте (FormEngine).</p>
          </div>
          <div className="ms-preview-dialog-actions">
            <select
              value={schemaId}
              onChange={(e) => onSelectTemplate(e.target.value)}
              className="ms-preview-dialog-select"
              aria-label="Шаблон для превью"
            >
              {measures.map((m) => <option key={m.id} value={m.id}>{m.title || m.id}</option>)}
            </select>
            <button type="button" className="ms-preview-dialog-close" onClick={onClose} aria-label="Закрыть">
              {Icon.x}
            </button>
          </div>
        </header>
        <div className="ms-preview-frame ms-preview-dialog-frame">
          <iframe title="Превью анкеты замера" src={iframeSrc} className="ms-preview-dialog-iframe" />
        </div>
        <footer className="ms-preview-dialog-foot">
          <a href={iframeSrc} target="_blank" rel="noopener noreferrer">Открыть превью в отдельной вкладке</a>
          <span className="ms-preview-dialog-foot-note">Геометрия и расчёт объёмов — вкладка «Геометрия» в редакторе шаблона.</span>
        </footer>
      </div>
    </dialog>,
    document.body
  );
}

function MeasuresHub({ measures, setMeasures }) {
  const [previewOpen, setPreviewOpen] = msUseState(initMeasuresPreviewOpen);
  const [activeId, setActiveId] = msUseState(measures[0]?.id || null);

  const openPreview = () => {
    setPreviewOpen(true);
    syncMeasuresSubUrl('preview');
  };

  const closePreview = () => {
    setPreviewOpen(false);
    syncMeasuresSubUrl('templates');
  };

  return (
    <div>
      <div className="ms-hub-toolbar">
        <div className="ms-hub-section-title">Шаблоны форм</div>
        <button
          type="button"
          className="ms-hub-preview-btn"
          aria-expanded={previewOpen}
          aria-haspopup="dialog"
          title="FormEngine · как анкета выглядит на объекте"
          onClick={openPreview}
        >
          Превью анкет
        </button>
      </div>
      <MeasuresTemplatesPanel
        measures={measures}
        setMeasures={setMeasures}
        activeId={activeId}
        setActiveId={setActiveId}
      />
      <MeasuresFieldPreviewDialog
        measures={measures}
        activeTemplateId={activeId}
        onSelectTemplate={setActiveId}
        open={previewOpen}
        onClose={closePreview}
      />
    </div>
  );
}

Object.assign(window, { MeasuresPanel: MeasuresHub, MeasuresHub, MeasuresTemplatesPanel, loadMeasures, saveMeasures });
