/* estimate-rooms-view.jsx — смета клиента «По комнатам» (KSH-502, спека 2026-11-01-client-estimate-by-rooms §7): переключатель вида,
   чипы комнат, обзор карточками с «Общим по объекту» и вид комнаты с этапами-аккордеонами. Чистое представление: данные — RoomsView (ClientEstimate.view().byRooms до запуска,
   EstimateRooms.forProject(project) после), деньги уже замаскированы моделью (просрочка — sum null, сумм в разметке нет).
   Решения клиента (согласовать / отказаться, у строк и у комнаты) приходят снаружи: окно «Смета» (client-v2-prestart.jsx) отдаёт
   renderLine / onRoomReject и сам решает через ClientEstimate.decide по ключу line.decisionKey (комната — room.decisionKey).
   Этапы и «Материалы» — свёрнутые аккордеоны (кнопка с aria-expanded), тела всегда в разметке (класс, не условный рендер): печать раскрывает всё и печатает все комнаты. Стили — .cv-er-* в client-v2.css (общие с КП: .cv-kp-*).
   Зависит только от React; EstimateRooms читается в момент вызова (название группы наружных комнат).
   Exports on window: EstimateViewToggle, EstimateRoomsView (+ .LineDecision, .RejectForm) */
(function () {
  'use strict';
  const DEC_L = { approved: 'согласовано', rejected: 'отказ' };
  const plainRub = (n) => (Math.round(n) || 0).toLocaleString('ru-RU') + ' ₽';
  const qtyR = (q) => (Math.round((+q || 0) * 100) / 100).toLocaleString('ru-RU');
  const outdoorName = () => (window.EstimateRooms && window.EstimateRooms.OUTDOOR_GROUP) || 'Балконы и террасы';

  /* «Вся смета» / «По комнатам»: value — 'all' | 'rooms'. Не печатается */
  function EstimateViewToggle({ value, onChange }) {
    const b = (v, label) => (
      <button type="button" className={value === v ? 'on' : ''} aria-pressed={value === v} onClick={() => onChange(v)}>{label}</button>);
    return <div className="cv-er-tg cv-kp-noprint" role="group" aria-label="Вид сметы">{b('all', 'Вся смета')}{b('rooms', 'По комнатам')}</div>;
  }

  /* форма отказа: показывается только у строки (комнаты), чей ключ совпал с draft.id; btn — { disabled, title } режима «только чтение» */
  function RejectForm({ id, draft, setDraft, onSave, btn }) {
    if (!id || !draft || draft.id !== id) return null;
    const b = btn || {};
    return (
      <div className="cv-kp-rej cv-kp-noprint">
        <textarea className="cv-ta" rows={2} aria-label="Что не подходит" placeholder="Напишите, что не подходит" value={draft.text} onChange={(e) => setDraft({ id, text: e.target.value })} />
        <button type="button" className="btn-k" disabled={!!b.disabled || !draft.text.trim()} title={b.title} onClick={() => onSave(id, draft.text)}>Сохранить отказ</button>
        <button type="button" className="btn-l" onClick={() => setDraft(null)}>Отмена</button>
      </div>);
  }

  /* решения по строке вида: своё (decision по decisionKey) и по работе везде (workDecision, есть у строк работ комнат и «Общего»).
     Кнопки — только когда canDecide и у строки есть ключ; решения, уже принятые, видны всегда (и в печати) */
  function LineDecision({ line, canDecide, btn, draft, setDraft, onApprove, onReject, onClear }) {
    const d = line.decision, w = line.workDecision, key = line.decisionKey;
    const b = btn || {};
    const can = !!(canDecide && key);
    const pill = (x, text) => <span className={x.state === 'approved' ? 'pill-k' : 'pill-d'}>{text}</span>;
    if (!d && !w && !can && !(draft && key && draft.id === key)) return null;
    return (
      <React.Fragment>
        <div className="cv-er-dec">
          {d ? pill(d, DEC_L[d.state]) : null}
          {w ? pill(w, 'по работе везде: ' + DEC_L[w.state]) : null}
          {can && !d ? <button type="button" className="btn-l cv-kp-noprint" {...b} onClick={() => onApprove(key)}>Согласовать</button> : null}
          {can && !d ? <button type="button" className="btn-l cv-kp-noprint" {...b} onClick={() => setDraft({ id: key, text: '' })}>Отказаться</button> : null}
          {can && d ? <button type="button" className="btn-l cv-kp-noprint" {...b} onClick={() => onClear(key)}>Снять решение</button> : null}
        </div>
        {d && d.comment ? <div className="lab cv-kp-cm">{d.comment}</div> : null}
        {w && w.comment ? <div className="lab cv-kp-cm">По работе везде: {w.comment}</div> : null}
        <RejectForm id={key} draft={draft} setDraft={setDraft} onSave={onReject} btn={b} />
      </React.Fragment>);
  }

  /* работы по этапам: порядок первого появления; строки без этапа (старые данные) — «Прочие работы» */
  function byStage(lines) {
    const order = [], map = new Map();
    lines.forEach((l) => {
      const id = l.stageId || '_';
      if (!map.has(id)) { const g = { id, name: l.stageName || 'Прочие работы', lines: [] }; map.set(id, g); order.push(g); }
      map.get(id).lines.push(l);
    });
    return order;
  }
  /* сумма списка; хоть одна null (просрочка предложения) — null, денег в разметке нет */
  const tot = (xs) => (xs.some((x) => x.sum == null) ? null : xs.reduce((a, x) => a + x.sum, 0));

  /* data — RoomsView; rub — формат денег кабинета; renderLine(line, ctx) — решения под строкой (ctx.room — комната, null в «Общем»);
     onRoomReject(room) — «Отказаться от комнаты», onRoomClear(room) — снять отказ, roomNote(room) — форма отказа под шапкой;
     btn — { disabled, title } для кнопок комнаты (режим «только чтение»); hint — подсказка про решения (по умолчанию есть при renderLine);
     initial — комната, открытая сразу ('all' — обзор). Выбор комнаты и раскрытые этапы — состояние вида.
     Разметка: чипы → обзор (карточки) → все комнаты → «Общее по объекту»; неактивное прячет класс cv-er-off (не условный рендер), печать раскрывает всё */
  function EstimateRoomsView({ data, renderLine, onRoomReject, onRoomClear, roomNote, rub, btn, hint, initial }) {
    const [selRaw, setSel] = React.useState(initial || 'all');
    const [open, setOpen] = React.useState({});
    if (!data) return null;
    const sel = data.rooms.some((r) => r.id === selRaw) ? selRaw : 'all';
    const money = rub || plainRub;
    const showHint = hint == null ? !!renderLine : !!hint;
    const sum = (x) => (x == null ? null : <span className="num sum">{money(x)}</span>);
    const off = (on) => (on ? '' : ' cv-er-off');
    const toggle = (k) => setOpen((o) => ({ ...o, [k]: !o[k] }));
    const anyShare = data.rooms.some((r) => r.lines.some((l) => l.mode === 'share'));

    const lineEl = (l, room) => (
      <div key={l.key || l.id} className="cv-kp-line cv-er-line">
        <div className="row">
          <div className="t"><span>{l.name}{l.mode === 'share' ? <span className="cv-er-sh" role="img" aria-label="работа разделена по площади"> ◔</span> : null}</span></div>
          {l.mode !== 'share' && l.qty != null ? <span className="lab cv-er-q">{qtyR(l.qty)}{l.unit ? ' ' + l.unit : ''}</span> : null}
          {sum(l.sum)}
        </div>
        {renderLine ? renderLine(l, { room }) : null}
      </div>);
    const matEl = (m) => (
      <div key={m.id} className="cv-kp-line cv-er-line cv-er-mat">
        <div className="row">
          <div className="t"><span>{m.name}</span></div>
          <span className="lab cv-er-q">{qtyR(m.qty)}{m.unit ? ' ' + m.unit : ''}</span>
          {sum(m.sum)}
        </div>
      </div>);
    /* свёрнутый раздел: заголовок — кнопка (aria-expanded), тело всегда в разметке */
    const acc = (key, title, total, body) => {
      const on = !!open[key];
      return (
        <div key={key} className={'cv-er-acc' + (on ? ' on' : '')}>
          <button type="button" className="cv-er-ah" aria-expanded={on} aria-controls={'cv-er-b-' + key} onClick={() => toggle(key)}>
            <span className="cv-er-ac" aria-hidden="true">▸</span><span className="cv-er-an">{title}</span>{sum(total)}
          </button>
          <div className="cv-er-ab" id={'cv-er-b-' + key}>{body}</div>
        </div>);
    };
    const stagesEl = (pre, lines, room) => byStage(lines).map((g) => acc(pre + g.id, g.name, tot(g.lines), g.lines.map((l) => lineEl(l, room))));
    const matsEl = (pre, list) => (list.length ? acc(pre + 'mat', 'Материалы', tot(list), list.map(matEl)) : null);

    const chip = (id, label) => (
      <button key={id} type="button" className={sel === id ? 'on' : ''} aria-pressed={sel === id} onClick={() => setSel(id)}>{label}</button>);

    const card = (r) => {
      const p = r.sum == null || !data.total ? null : r.sum / data.total * 100;
      const top = r.sum == null ? [] : byStage(r.lines).map((g) => ({ name: g.name, sum: tot(g.lines) })).sort((a, b) => b.sum - a.sum).slice(0, 3).map((g) => g.name);
      return (
        <button key={r.id} type="button" className="cv-er-cd" onClick={() => setSel(r.id)}>
          <span className="cv-er-cn">{r.name}</span>
          <span className="cv-er-cm">{r.area > 0 ? <span className="lab">{qtyR(r.area)} м²</span> : <span />}{sum(r.sum)}</span>
          {p == null ? null : <span className="cv-er-bar" aria-hidden="true"><i style={{ width: Math.max(2, Math.min(100, p)) + '%' }} /></span>}
          {p == null ? null : <span className="lab cv-er-ct">{p < 1 ? '<1' : Math.round(p)}% сметы{top.length ? ' · ' + top.join(', ') : ''}</span>}
        </button>);
    };
    const grid = [];
    let prev = null, cur = null;
    data.rooms.forEach((r) => {
      const grp = r.outdoor ? outdoorName() : r.floorLabel;
      if (grp && grp !== prev) grid.push(<h3 key={'g:' + grp} className="cv-er-grp">{grp}</h3>);
      if (grp !== prev || !cur) { cur = []; grid.push(<div key={'w:' + grid.length} className="cv-er-grid">{cur}</div>); }
      prev = grp || prev;
      cur.push(card(r));
    });

    const roomEl = (r) => {
      const d = r.decision;
      return (
        <section key={r.id} className={'card cv-er-room' + off(sel === r.id)} aria-labelledby={'cv-er-h-' + r.id}>
          <header className="cv-er-hd">
            <div className="cv-er-nm">
              <h3 className="cv-kp-h3" id={'cv-er-h-' + r.id}>{r.name}</h3>
              {r.area > 0 ? <div className="lab">{qtyR(r.area)} м²</div> : null}
            </div>
            <div className="cv-er-rs">
              {d ? <span className={d.state === 'approved' ? 'pill-k' : 'pill-d'}>{d.state === 'rejected' ? 'отказ от комнаты' : DEC_L[d.state]}</span> : null}
              {sum(r.sum)}
            </div>
          </header>
          {d && d.comment ? <div className="lab cv-kp-cm">{d.comment}</div> : null}
          {onRoomReject && r.decisionKey && !d ? <div className="cv-er-ra cv-kp-noprint"><button type="button" className="btn-l" {...(btn || {})} onClick={() => onRoomReject(r)}>Отказаться от комнаты</button></div> : null}
          {onRoomClear && r.decisionKey && d ? <div className="cv-er-ra cv-kp-noprint"><button type="button" className="btn-l" {...(btn || {})} onClick={() => onRoomClear(r)}>Снять отказ</button></div> : null}
          {roomNote ? roomNote(r) : null}
          <div className="cv-er-body">
            {stagesEl(r.id + ':', r.lines, r)}
            {matsEl(r.id + ':', r.materials)}
          </div>
        </section>);
    };

    const c = data.common;
    const ov = sel === 'all';
    return (
      <div className="cv-er">
        <h2 className="cv-er-print">Смета по комнатам</h2>
        <div className="cv-er-chips cv-kp-noprint" role="group" aria-label="Комнаты">{chip('all', 'Все комнаты')}{data.rooms.map((r) => chip(r.id, r.name))}</div>
        {showHint ? <div className="lab cv-er-hint cv-kp-noprint">Решение по работе действует во всей смете</div> : null}
        <div className={'cv-er-ov' + off(ov)}>{grid}</div>
        {data.rooms.map(roomEl)}
        <section key="common" className={'card cv-er-room cv-er-common' + off(ov)} aria-labelledby="cv-er-h-common">
          <header className="cv-er-hd">
            <div className="cv-er-nm"><h3 className="cv-kp-h3" id="cv-er-h-common">{c.name}</h3></div>
            <div className="cv-er-rs">{sum(c.sum)}</div>
          </header>
          <div className="cv-er-body">
            {stagesEl('c:', c.lines, null)}
            {matsEl('c:', c.materials)}
            {c.extras.map((x) => (
              <div key={x.id} className="cv-kp-line cv-er-line cv-er-ex">
                <div className="row"><div className="t"><span>{x.name}</span></div>{sum(x.sum)}</div>
              </div>))}
            <div className="cv-er-tot"><span>Итого по смете</span>{data.total == null ? <span className="num">—</span> : <span className="num">{money(data.total)}</span>}</div>
          </div>
        </section>
        {anyShare ? <div className={'lab cv-er-fn' + off(!ov)}>◔ — работа разделена по площади комнаты</div> : null}
      </div>);
  }
  EstimateRoomsView.LineDecision = LineDecision;
  EstimateRoomsView.RejectForm = RejectForm;

  window.EstimateViewToggle = EstimateViewToggle;
  window.EstimateRoomsView = EstimateRoomsView;
})();
