/* client-sandbox-app.jsx — «Пройти сделку» (lk/client-sandbox.html) в устройстве кабинетов (DESIGN.md §6, как кабинет клиента):
   главная пробной сделки — чей сейчас ход и что сделать, путь сделки по этапам, участники этапа с фото, лента «что уже произошло».
   Интерфейс роли открывается окном на весь экран поверх главной (отступ 24, радиус 36 сверху), закрывается крестиком, Esc и кликом
   по затемнению. Внутри окна — настоящий интерфейс роли во фрейме, общая память песочницы (ClientSandbox.ROLES):
   менеджер — карточка сделки и смета в калькуляторе, замерщик — форма замера, прораб — смета с «Подтвердить мою часть»,
   клиент — его кабинет; до заявки — форма заявки с сайта (sandbox-lead.html).
   Два входа: раздел «Тестировщик» на «Личных кабинетах» (?tester=1) и «Глазами клиента» из Конструктора (?from=ctor: сразу окно
   кабинета клиента; «×» в шапке и Esc на главной закрывают всё окно).
   Этап вперёд доводит сделку шагами команды (ClientSandbox.goTo); назад — перезагрузка с ?at=<этап>, сделка заново с чистой памятью.
   Сданный замер и отправленная смета двигают сделку сами (ClientSandbox.advance, окно закрывается — на главной новый ход), остальное —
   «Сделать за …» (ClientSandbox.next). Этапы — DealPath (ветка без ДП) и дальше договор подряда и аванс (ClientSandbox.path):
   чей ход — ClientSandbox.turn (на договоре и авансе ход переходит между менеджером, клиентом и бухгалтером). Exports: — (рендер в #root) */
(function () {
  const { useState, useEffect } = React;
  const SB = window.ClientSandbox;
  const q = window.location.search;
  const embedded = window.parent !== window && /[?&]from=ctor\b/.test(q);
  const tester = /[?&]tester=1\b/.test(q);
  const close = () => window.parent.postMessage({ type: 'client-sandbox:close' }, '*');
  // первый заход: этап из адреса (?at=), иначе заявка. Тестировщик без ?at= начинает с пустой формы заявки — сделки ещё нет
  let bootErr = '';
  const atM = /[?&]at=([\w-]+)/.exec(q);
  if (!SB.state() && (!tester || atM)) {
    const at = atM && SB.FLOW.some((f) => f.id === atM[1]) ? atM[1] : 'lead';
    try { SB.goTo(at); } catch (e) { bootErr = 'Пробная сделка не собралась: ' + e.message; }
  }

  const OPEN = { client: 'Открыть кабинет клиента', manager: 'Открыть карточку сделки', measurer: 'Открыть форму замера', foreman: 'Открыть смету прораба', accountant: 'Открыть кабинет бухгалтера' };
  const MONTHS = ['января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря'];
  const today = () => { const d = new Date(); return ['Воскресенье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота'][d.getDay()] + ', ' + d.getDate() + ' ' + MONTHS[d.getMonth()]; };
  const hhmm = (iso) => { const d = new Date(iso); return isNaN(d) ? '' : String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0'); };
  const ini = (n) => String(n || '').split(/\s+/).filter(Boolean).slice(0, 2).map((w) => w[0]).join('').toUpperCase();
  const Arrow = () => <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M7 17 17 7M8 7h9v9" /></svg>;
  const Check = () => <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="m5 12 5 5 9-10" /></svg>;

  function Face({ p, size }) {
    return p.photo
      ? <img className="sb-face" style={{ width: size, height: size }} src={'demo/' + p.photo + '.jpg'} alt="" />
      : <span className="sb-face sb-face-i" style={{ width: size, height: size }} aria-hidden="true">{ini(p.name)}</span>;
  }

  function App() {
    const [, tick] = useState(0);
    const [phone, setPhone] = useState(false);
    const [frameKey, setFrameKey] = useState(0);
    const [err, setErr] = useState(bootErr);
    const [win, setWin] = useState(embedded ? 'client' : null); // открытое окно роли; 'lead' — форма заявки
    const path = SB.path();
    useEffect(() => {
      let t = null;
      window.__clientSandboxPing = () => {
        clearTimeout(t);
        t = setTimeout(() => {
          // роль сделала ход в своём интерфейсе (замер сдан, смета отправлена) — сделка идёт дальше, на главной новый ход
          let moved = null;
          try { moved = SB.advance(); } catch (e) { setErr('Не получилось двинуть сделку: ' + e.message); }
          if (moved) { setWin(embedded ? 'client' : null); setFrameKey((x) => x + 1); }
          tick((x) => x + 1);
        }, 250);
      };
      // форма заявки (sandbox-lead.html): клиент отправил заявку — сделка начинается с его данными
      window.__clientSandboxLead = (lead) => {
        try { SB.start({ lead }); setErr(''); } catch (e) { setErr('Заявка не создалась: ' + e.message); return false; }
        setTimeout(() => { setWin(null); tick((x) => x + 1); }, 1400);
        return true;
      };
      return () => { window.__clientSandboxPing = null; window.__clientSandboxLead = null; };
    }, []);
    useEffect(() => {
      const k = (e) => { if (e.key !== 'Escape') return; if (win) setWin(null); else if (embedded) close(); };
      document.addEventListener('keydown', k);
      return () => document.removeEventListener('keydown', k);
    }, [win]);

    const s = SB.state();
    const at = Math.max(0, path.findIndex((x) => x.id === (s ? s.stage : 'lead')));
    const stageId = path[at].id;
    const flow = SB.FLOW.find((f) => f.id === stageId) || {};
    const roles = s ? SB.rolesAt(stageId) : [];
    const it = s && window.loadSiteIntake ? window.loadSiteIntake(SB.DEAL) || {} : {};
    const person = (r) => ({ ...SB.ROLES[r], role: r, name: r === 'client' ? it.clientName || SB.ROLES.client.name : SB.ROLES[r].name });
    const now = s ? SB.situation() : null;
    const who = s ? SB.turn(stageId) : null; // чей ход: на договоре и авансе зависит от шага документа; null — путь пройден
    const turn = who ? person(who) : null;
    const reload = () => { setFrameKey((x) => x + 1); tick((x) => x + 1); };
    const goStage = (i) => {
      if (!s || i === at) return;
      if (i < at) {
        // назад — заново с чистой памятью: ответы и решения с прошлого прохода не тянутся
        const u = new URL(window.location.href);
        u.searchParams.set('at', path[i].id);
        window.location.replace(u.toString());
        return;
      }
      try { SB.goTo(path[i].id); setErr(''); } catch (e) { setErr('Этап не открылся: ' + e.message); }
      setWin(null); reload();
    };
    const step = () => { try { SB.next(); setErr(''); } catch (e) { setErr('Не получилось: ' + e.message); } setWin(null); reload(); };
    const restart = () => { const u = new URL(window.location.href); u.searchParams.delete('at'); window.location.replace(u.toString()); };
    const log = s && s.log ? s.log.slice().reverse() : [];

    const lab = s ? today() + ' · ' + [it.clientName, it.address].filter(Boolean).join(' · ') : '';
    const hero = !s
      ? { lab: today() + ' · пробная сделка', h: 'Начните с заявки', t: 'Вы — клиент на сайте. Заполните заявку, как это делает настоящий клиент: с неё начинается сделка, дальше ход переходит к менеджеру.', btn: 'Заполнить заявку', go: 'lead' }
      : !who
        ? { lab, h: 'Сделка готова к запуску', t: now.text, btn: OPEN.client, go: 'client' }
        : { lab, h: 'Сейчас ход ' + SB.ROLES[who].of, t: SB.hint(who, stageId) || now.text, btn: who === 'manager' && SB.view('manager', s) === SB.MANAGER_DEAL ? 'Открыть сделку' : OPEN[who], go: who };

    const winRole = win && win !== 'lead' ? person(win) : null;
    const winSrc = win === 'lead' ? SB.LEAD_FORM : win ? SB.view(win, s) : null;
    const winHint = winRole ? SB.hint(win, stageId) || (now && now.text) : 'Вы — клиент на сайте. Пробная заявка никуда не уходит.';

    return (
      <div className="sb">
        <header className="sb-head">
          <span className="sb-logo">ОБИТЕЛЬ</span>
          <span className="sb-sect">{tester ? 'Тестировщик' : 'Глазами клиента'}</span>
          <div className="sb-me">
            <span className="sb-pill" title="Пробная сделка живёт только в этом окне: настоящие сделки и настройки не меняются">Пробная сделка · не сохраняется</span>
            {s ? <button type="button" className="sb-btn-l" onClick={restart}>Начать заново</button> : null}
            {embedded ? <button type="button" className="sb-x" aria-label="Закрыть" onClick={close}>×</button> : null}
          </div>
        </header>

        <section className="sb-hero">
          <div className="sb-hero-l">
            <div className="sb-lab">{hero.lab}</div>
            <h1 className="sb-display">{hero.h}</h1>
            <p className="sb-lede" role="status">{hero.t}</p>
            {err ? <p className="sb-err">{err}</p> : null}
          </div>
          <div className="sb-hero-r">
            {turn ? <div className="sb-turnp"><Face p={turn} size={44} /><span><span className="sb-n">{turn.name}</span><span className="sb-lab">{turn.label}</span></span></div> : null}
            <button type="button" className="sb-btn-k" onClick={() => setWin(hero.go)}>{hero.btn}<Arrow /></button>
            {s && who && flow.done ? <button type="button" className="sb-link" onClick={step} title={flow.next}>{'Сделать за ' + SB.ROLES[who].of}</button> : null}
          </div>
        </section>

        <div className="sb-cols">
          <section className="sb-card">
            <div className="sb-card-h"><h2 className="sb-h2">Путь сделки</h2><span className="sb-lab">Без дизайн-проекта · этап {at + 1} из {path.length}</span></div>
            <ol className="sb-path">{path.map((p, i) => {
              const f = SB.FLOW.find((x) => x.id === p.id) || {};
              const st = !s ? (i === 0 ? 'now' : 'next') : i < at ? 'done' : i === at ? (who ? 'now' : 'done') : 'next';
              const pw = i === at && turn ? turn : f.who ? person(f.who) : null;
              return (
                <li key={p.id} className={'is-' + st}>
                  <button type="button" onClick={() => goStage(i)} disabled={!s || i === at} title={st === 'done' && i !== at ? 'Пройти заново с этого этапа' : st === 'next' ? 'Перейти: шаги до него команда сделает сама' : undefined}>
                    <span className="sb-dot">{st === 'done' ? <Check /> : i + 1}</span>
                    <span className="sb-pt">{p.title}</span>
                    {pw ? <span className="sb-pw"><Face p={pw} size={22} />{pw.label}</span> : <span />}
                    <span className="sb-ps">{st === 'done' ? 'сделано' : st === 'now' ? <span className="sb-now-pill">сейчас</span> : 'дальше'}</span>
                  </button>
                </li>);
            })}</ol>
          </section>

          <div className="sb-side">
            <section className="sb-card">
              <div className="sb-card-h"><h2 className="sb-h2">Участники этапа</h2><span className="sb-lab">Откройте интерфейс любой роли</span></div>
              {s ? <ul className="sb-people">{roles.map((r) => { const pp = person(r); return (
                <li key={r}>
                  <button type="button" className="sb-person" onClick={() => setWin(r)}>
                    <Face p={pp} size={48} />
                    <span className="sb-ptx"><span className="sb-n">{pp.name}</span><span className="sb-lab">{pp.label} · {SB.view(r, s).title.toLowerCase()}</span></span>
                    {r === who ? <span className="sb-turn">ход</span> : null}
                    <span className="sb-arr"><Arrow /></span>
                  </button>
                </li>); })}</ul>
                : <p className="sb-empty">Участники появятся, когда клиент оставит заявку.</p>}
            </section>
            <section className="sb-card">
              <div className="sb-card-h"><h2 className="sb-h2">Что уже произошло</h2></div>
              {log.length ? <ul className="sb-log">{log.map((l, i) => (
                <li key={i}><span className="sb-lab sb-lt">{hhmm(l.at)}</span><span>{l.text}</span></li>))}</ul>
                : <p className="sb-empty">Пока ничего: сделки ещё нет.</p>}
            </section>
          </div>
        </div>

        {win ? (
          <div className="sb-scrim" onClick={(e) => { if (e.target === e.currentTarget) setWin(null); }}>
            <div className="sb-win" role="dialog" aria-modal="true" aria-label={winSrc.title}>
              <div className="sb-win-bar">
                {winRole ? <Face p={winRole} size={40} /> : null}
                <div className="sb-win-t">
                  <div className="sb-n">{winRole ? winRole.name + ' · ' + winRole.label : 'Заявка с сайта'}</div>
                  <div className="sb-lab">{winHint}</div>
                </div>
                <div className="sb-seg" role="group" aria-label="Экран">
                  <button type="button" aria-pressed={!phone} onClick={() => setPhone(false)}>Компьютер</button>
                  <button type="button" aria-pressed={phone} onClick={() => setPhone(true)}>Телефон</button>
                </div>
                <button type="button" className="sb-x" aria-label="Закрыть окно" onClick={() => setWin(null)}>×</button>
              </div>
              <div className={'sb-stage-frame' + (phone ? ' is-phone' : '')}>
                <iframe key={win + ':' + frameKey + ':' + (s ? 1 : 0)} className="sb-frame" title={winSrc.title} src={winSrc.src} />
              </div>
            </div>
          </div>) : null}
      </div>);
  }

  ReactDOM.createRoot(document.getElementById('root')).render(<App />);
})();
