/* demand-trend-panel.jsx — 적재된 네이버 데이터랩 수요 시계열 화면 (P1 프론트)
 *
 *  ★엔진·API 만 만들고 화면을 안 만드는 실패를 반복하지 않는다. 적재층(cron) ·
 *    조회층(/api/demand) · 화면(이 파일)이 한 세트다.
 *
 *  화면이 반드시 같이 내는 것(하나라도 빠지면 미완):
 *   ① ratio 가 **창 내 최대=100 상대지수**라는 사실 — 절대 검색량이 아니다
 *   ② 지금 보고 있는 **창(window)** 이 무엇인지, 창이 섞였으면 그 경고
 *   ③ 마지막 구간은 **잠정값**(재호출 시 움직임을 실측) — 확정처럼 그리지 않는다
 *   ④ **관측 공백**(uncollected) — 0 과 "확인 못 함"을 다르게 그린다
 *   ⑤ tier · n
 */
(function () {
  var useState = React.useState, useEffect = React.useEffect, useCallback = React.useCallback;

  var MONO = 'var(--font-mono)';
  function Badge({ tone, children }) {
    var c = { amber: ['#3a2a12', '#f0b46a'], cyan: ['#11303a', '#5fc7e0'], plant: ['#13301f', '#5fcf94'],
              mute: ['#23262d', '#9aa0aa'], red: ['#3a1b1b', '#e08a8a'] }[tone || 'mute'];
    return <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4, padding: '2px 8px', borderRadius: 4,
      fontSize: 10.5, fontFamily: MONO, fontWeight: 600, background: c[0], color: c[1], whiteSpace: 'nowrap' }}>{children}</span>;
  }
  function Card({ children, style }) {
    return <div style={Object.assign({ background: 'var(--surface)', border: '1px solid var(--border)', borderRadius: 9, padding: '15px 17px' }, style)}>{children}</div>;
  }
  function H4({ children }) {
    return <h4 style={{ margin: '0 0 10px', fontSize: 11.5, letterSpacing: '0.05em', color: 'var(--ink-400)', textTransform: 'uppercase', fontFamily: MONO }}>{children}</h4>;
  }

  function DemandTrendPanel() {
    var [data, setData] = useState(null);
    var [loading, setLoading] = useState(false);
    var [err, setErr] = useState('');
    var [group, setGroup] = useState('');
    // ★단위(month/date)는 창이 달라 **다른 계열**이다. 키에 넣지 않으면 두 계열이 한 그래프에
    //   섞여 그려진다 — 축이 조용히 어긋나는 이 프로젝트의 대표 함정이다.
    var [unit, setUnit] = useState('');
    // ★결합 구간(P2)은 광고주 데이터가 섞여 **토큰이 있어야** 조회된다.
    //   토큰은 브라우저에만 두고(localStorage) 서버·로그에 남기지 않는다.
    var [token, setToken] = useState(function () {
      try { return localStorage.getItem('gfa-pm-demand-token') || ''; } catch (e) { return ''; }
    });
    var [fc, setFc] = useState(null);
    var [fcLoading, setFcLoading] = useState(false);
    var [fcErr, setFcErr] = useState('');

    var load = useCallback(function (groupArg, unitArg) {
      var g = groupArg === undefined ? group : groupArg;
      var u = unitArg === undefined ? unit : unitArg;
      setLoading(true); setErr('');
      var qs = [];
      if (g) qs.push('group=' + encodeURIComponent(g));
      if (u) qs.push('unit=' + encodeURIComponent(u));
      fetch('/api/demand' + (qs.length ? '?' + qs.join('&') : ''), { cache: 'no-store' })
        .then(function (r) { return r.json(); })
        .then(function (j) {
          if (!j || j.ok === false) { setErr((j && (j.error || j.verdict)) || '조회 실패'); setData(null); }
          else setData(j);
        })
        // 네트워크 실패를 "데이터 없음"으로 보이게 하지 않는다.
        .catch(function (e) { setErr('네트워크 실패: ' + e.message); setData(null); })
        .then(function () { setLoading(false); });
    }, [group, unit]);

    useEffect(function () { load(); }, []); // eslint-disable-line

    var loadForecast = useCallback(function (g) {
      if (!token) { setFcErr('토큰 없음 — 결합 구간은 내부 전용이다'); setFc(null); return; }
      setFcLoading(true); setFcErr('');
      fetch('/api/forecast?group=' + encodeURIComponent(g) + '&device=pc&days=28', {
        cache: 'no-store', headers: { Authorization: 'Bearer ' + token },
      })
        .then(function (r) { return r.json(); })
        .then(function (j) {
          if (!j || j.ok === false) { setFcErr((j && (j.error || j.verdict)) || '조회 실패'); setFc(null); }
          else setFc(j);
        })
        .catch(function (e) { setFcErr('네트워크 실패: ' + e.message); setFc(null); })
        .then(function () { setFcLoading(false); });
    }, [token]);

    var trend = (data && data.trend) || [];
    var insight = (data && data.insight) || [];
    var uncollected = (data && data.uncollected) || [];
    var meta = (data && data._meta) || {};

    // ★키워드·단위 목록은 **catalog**(DB 가 실제로 가진 것)에서만 만든다.
    //   지금 화면에 로드된 trend 로 만들면, 단위를 좁혀 받는 순간 다른 단위가 목록에서 사라진다.
    var catalog = (data && data.catalog) || [];
    var groups = [];
    catalog.forEach(function (c) { if (groups.indexOf(c.group_title) < 0) groups.push(c.group_title); });
    if (!groups.length) trend.forEach(function (t) { if (groups.indexOf(t.group_title) < 0) groups.push(t.group_title); });
    var shown = group || groups[0] || '';
    var unitsForShown = catalog.filter(function (c) { return c.group_title === shown; })
                               .map(function (c) { return c.time_unit; });
    var shownUnit = (unit && unitsForShown.indexOf(unit) >= 0) ? unit : (unitsForShown[0] || '');

    var series = trend.filter(function (t) {
      return t.group_title === shown && (!shownUnit || t.time_unit === shownUnit);
    });
    // ★일 단위는 400점까지 온다. 막대를 400개 그리면 읽을 수 없다 —
    //   최근 구간만 그리되 **몇 점을 접었는지 반드시 적는다**(조용한 절단 금지).
    var MAX_BARS = 90;
    var folded = Math.max(0, series.length - MAX_BARS);
    var bars = folded ? series.slice(-MAX_BARS) : series;
    var maxRatio = bars.reduce(function (m, t) { return Math.max(m, Number(t.ratio) || 0); }, 0) || 100;

    return (
      <div style={{ display: 'grid', gap: 14 }}>
        <Card>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap', marginBottom: 8 }}>
            <h3 style={{ margin: 0, fontSize: 15 }}>수요 시계열 — 네이버 데이터랩 적재분</h3>
            <Badge tone="cyan">tier {meta.tier || 'B'} · 관측</Badge>
            <Badge tone="mute">n={meta.n || 0}</Badge>
            <button onClick={load} disabled={loading}
              style={{ marginLeft: 'auto', background: 'transparent', border: '1px solid var(--border)', color: 'var(--ink-400)', borderRadius: 6, padding: '4px 10px', cursor: 'pointer', fontSize: 12 }}>
              {loading ? '조회 중…' : '새로고침'}
            </button>
          </div>

          {/* ① 무엇을 보고 있는지 — 숫자보다 먼저 */}
          <p style={{ margin: '0 0 10px', fontSize: 11.5, color: 'var(--ink-400)', lineHeight: 1.6 }}>
            {meta.note || 'ratio 는 창 내 최대=100 상대지수다 — 절대 검색량이 아니다.'}
          </p>

          {/* ② 창 */}
          {meta.windows && meta.windows.length > 0 && (
            <div style={{ fontSize: 11, fontFamily: MONO, color: 'var(--ink-400)', marginBottom: 8 }}>
              창 {meta.windows.join(' , ')}
            </div>
          )}
          {meta.truncated && (
            <div style={{ background: '#3a2a12', color: '#f0b46a', borderRadius: 6, padding: '8px 11px', fontSize: 12, marginBottom: 8 }}>
              조회 상한에 닿았다 — 받은 것이 전부가 아니다. 단위를 좁혀 다시 본다.
            </div>
          )}
          {meta.caution && (
            <div style={{ background: '#3a1b1b', color: '#e08a8a', borderRadius: 6, padding: '8px 11px', fontSize: 12, marginBottom: 8 }}>
              {meta.caution}
            </div>
          )}

          {err && (
            <div style={{ background: '#3a1b1b', color: '#e08a8a', borderRadius: 6, padding: '10px 12px', fontSize: 12 }}>
              {err}
            </div>
          )}

          {!err && !loading && trend.length === 0 && (
            <div style={{ background: '#2a233a', color: '#b79ae0', borderRadius: 6, padding: '10px 12px', fontSize: 12 }}>
              <b>아직 적재된 관측이 없다.</b> 0 이 아니라 아직 수집하지 않은 것이다 —
              <span style={{ fontFamily: MONO }}> /api/cron/demand-sync</span> 가 하루 1회 채운다.
            </div>
          )}

          {unitsForShown.length > 1 && (
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginTop: 8, alignItems: 'center' }}>
              <span style={{ fontSize: 10.5, fontFamily: MONO, color: 'var(--ink-400)' }}>단위</span>
              {unitsForShown.map(function (u) {
                var on = u === shownUnit;
                return (
                  <button key={u} onClick={function () { setUnit(u); load(shown, u); }}
                    title="단위마다 창이 달라 서로 다른 계열이다 — 이어붙이지 않는다"
                    style={{ background: on ? '#11303a' : 'transparent', color: on ? '#5fc7e0' : 'var(--ink-400)',
                             border: '1px solid var(--border)', borderRadius: 999, padding: '3px 11px', cursor: 'pointer', fontSize: 11.5 }}>
                    {u === 'date' ? '일' : u === 'week' ? '주' : '월'}
                  </button>
                );
              })}
            </div>
          )}

          {groups.length > 1 && (
            <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', marginTop: 6 }}>
              {groups.map(function (g) {
                var on = g === shown;
                return (
                  <button key={g} onClick={function () { setGroup(g); load(g, undefined); }}
                    style={{ background: on ? '#2a233a' : 'transparent', color: on ? '#b79ae0' : 'var(--ink-400)',
                             border: '1px solid var(--border)', borderRadius: 999, padding: '3px 11px', cursor: 'pointer', fontSize: 11.5 }}>
                    {g}
                  </button>
                );
              })}
            </div>
          )}
        </Card>

        {/* ③ 계열 — 마지막 구간은 잠정으로 구분해 그린다 */}
        {series.length > 0 && (
          <Card>
            <H4>
              {shown} · {shownUnit === 'date' ? '일별' : shownUnit === 'week' ? '주별' : '월별'} 상대 지수 ({series.length}점
              {folded ? ' 중 최근 ' + bars.length + '점' : ''})
            </H4>
            {folded > 0 && (
              <div style={{ fontSize: 11, color: 'var(--ink-400)', marginBottom: 6 }}>
                앞쪽 <b>{folded}점</b>은 그래프에서 접었다 — 데이터에는 그대로 있다(회귀는 전 구간을 쓴다).
              </div>
            )}
            <div style={{ display: 'flex', alignItems: 'flex-end', gap: 4, height: 150, padding: '4px 0 0' }}>
              {bars.map(function (t) {
                var h = Math.max(2, (Number(t.ratio) / maxRatio) * 130);
                var prov = t.is_window_last;
                return (
                  <div key={t.rk} title={t.period + ' · ' + t.ratio + (prov ? ' (잠정)' : '')}
                       style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4 }}>
                    <span style={{ fontSize: 9.5, fontFamily: MONO, color: prov ? '#f0b46a' : 'var(--ink-400)' }}>
                      {Number(t.ratio).toFixed(0)}
                    </span>
                    <div style={{
                      width: '100%', height: h, borderRadius: '3px 3px 0 0',
                      // 잠정값은 채우지 않고 빗금 — "확정값과 같은 막대"로 그리지 않는다
                      background: prov
                        ? 'repeating-linear-gradient(135deg,#f0b46a 0 3px,#3a2a12 3px 6px)'
                        : 'linear-gradient(180deg,#5fc7e0,#11303a)',
                    }} />
                    <span style={{ fontSize: 9, fontFamily: MONO, color: 'var(--ink-400)' }}>
                      {shownUnit === 'date' ? String(t.period).slice(5) : String(t.period).slice(2, 7)}
                    </span>
                  </div>
                );
              })}
            </div>
            <div style={{ marginTop: 10, fontSize: 11, color: 'var(--ink-400)', display: 'flex', gap: 14, flexWrap: 'wrap' }}>
              <span><i style={{ display: 'inline-block', width: 10, height: 10, borderRadius: 2, background: '#5fc7e0', marginRight: 5 }} />확정 구간</span>
              <span><i style={{ display: 'inline-block', width: 10, height: 10, borderRadius: 2, background: 'repeating-linear-gradient(135deg,#f0b46a 0 3px,#3a2a12 3px 6px)', marginRight: 5 }} />
                잠정 — {meta.provisional_note || '마지막 구간은 값이 움직인다'}</span>
            </div>
          </Card>
        )}

        {/* 쇼핑인사이트 — 실패도 그린다 */}
        {insight.length > 0 && (
          <Card>
            <H4>쇼핑인사이트 · 성별 · 연령 · 기기</H4>
            <div style={{ fontSize: 11, color: 'var(--ink-400)', marginBottom: 8 }}>
              상대 <b>클릭비율</b>이다 — 매출·전환·구매자 수가 아니다. 응답에 구간 라벨이 없어 캡처일로만 줄을 세운다.
            </div>
            <div style={{ display: 'grid', gap: 8 }}>
              {insight.slice(0, 8).map(function (r) {
                if (!r.ok) {
                  return (
                    <div key={r.rk} style={{ fontSize: 11.5, color: '#e08a8a', fontFamily: MONO }}>
                      {r.stat_dt} · cid {r.cid} — 미수집: {r.note || '사유 미기록'}
                    </div>
                  );
                }
                return (
                  <div key={r.rk} style={{ display: 'flex', gap: 12, flexWrap: 'wrap', alignItems: 'baseline', fontSize: 12 }}>
                    <span style={{ fontFamily: MONO, color: 'var(--ink-400)' }}>{r.stat_dt}</span>
                    <b>{r.seed_query || r.cid}</b>
                    <span>여 {r.female}% · 남 {r.male}%</span>
                    <span style={{ color: 'var(--ink-400)' }}>
                      10s {r.age_10s} · 20s {r.age_20s} · 30s {r.age_30s} · 40s {r.age_40s} · 50s {r.age_50s} · 60+ {r.age_60s_plus}
                    </span>
                    <span>모바일 {r.device_mobile}% / PC {r.device_pc}%</span>
                    <Badge tone="cyan">tier {r.tier || 'B'}</Badge>
                  </div>
                );
              })}
            </div>
          </Card>
        )}

        {/* ⑤ 결합 구간(P2) — 내부 전용. **절대값을 그리지 않는다.** */}
        <Card style={{ borderColor: fc ? '#11303a' : 'var(--border)' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
            <H4>예상 성과 구간 — 불확실성이 어디서 오나 (내부)</H4>
            <button onClick={function () { loadForecast(shown); }} disabled={fcLoading || !shown}
              style={{ marginLeft: 'auto', background: 'transparent', border: '1px solid var(--border)', color: 'var(--ink-400)', borderRadius: 6, padding: '3px 10px', cursor: 'pointer', fontSize: 11.5 }}>
              {fcLoading ? '산출 중…' : '산출'}
            </button>
          </div>

          {/* ★절대값을 안 그리는 이유를 화면에 둔다. 지우면 사람이 p50 을 성과 예측으로 읽는다. */}
          <p style={{ fontSize: 11, color: 'var(--ink-400)', margin: '0 0 10px', lineHeight: 1.6 }}>
            수요지수는 <b>창 내 최대=100 상대지수</b>라 곱의 <b>절대값에는 단위가 없다</b>.
            그래서 여기서는 <b>절대값을 그리지 않고</b> 상대 구간폭과 <b>무엇이 그 폭을 만들었는지</b>만 낸다.
          </p>

          <div style={{ display: 'flex', gap: 6, alignItems: 'center', marginBottom: 10 }}>
            <input type="password" value={token} placeholder="내부 토큰"
              onChange={function (e) {
                setToken(e.target.value);
                try { localStorage.setItem('gfa-pm-demand-token', e.target.value); } catch (err) {}
              }}
              style={{ flex: '0 0 200px', background: 'var(--surface-2)', border: '1px solid var(--border)', color: 'var(--ink-100)', borderRadius: 6, padding: '4px 9px', fontSize: 11.5 }} />
            <span style={{ fontSize: 10.5, color: 'var(--ink-400)' }}>광고주 수치가 섞여 공개 경로로 내보내지 않는다</span>
          </div>

          {fcErr && (
            <div style={{ background: '#3a2a12', color: '#f0b46a', borderRadius: 6, padding: '8px 11px', fontSize: 12 }}>{fcErr}</div>
          )}

          {fc && fc.forecast && fc.forecast.ok === false && (
            <div style={{ background: '#2a233a', color: '#b79ae0', borderRadius: 6, padding: '8px 11px', fontSize: 12 }}>
              <b>구간을 만들지 않았다.</b> {fc.forecast.caution}
            </div>
          )}

          {fc && fc.forecast && fc.forecast.ok && (
            <div style={{ display: 'grid', gap: 10 }}>
              {/* ★A2 판정(plan-adversary): width_drivers 를 caution 에 묻으면 부족하다.
                  숫자와 **같은 시각적 무게**로 "이 폭의 절반은 무엇이 만들었나"를 내야
                  이름을 바꿔도 사람이 "수요가 흔들린다"로 오독하지 않는다. */}
              <div style={{ display: 'flex', gap: 24, flexWrap: 'wrap', alignItems: 'baseline' }}>
                <div>
                  <div style={{ fontSize: 11, color: 'var(--ink-400)' }}>상대 구간폭</div>
                  <b className="mono" style={{ fontSize: 22 }}>{(fc.forecast.rel_width * 100).toFixed(0)}%</b>
                </div>
                {(fc.forecast.width_drivers || []).slice(0, 1).map(function (d) {
                  return (
                    <div key={d.key}>
                      <div style={{ fontSize: 11, color: 'var(--ink-400)' }}>이 폭을 만든 것</div>
                      <b className="mono" style={{ fontSize: 22, color: '#5fc7e0' }}>
                        {d.key} {Math.round(d.share_pct)}%
                      </b>
                    </div>
                  );
                })}
                {(fc.forecast.width_drivers || []).filter(function (d) { return d.key === 'demand_idx'; }).map(function (d) {
                  return (
                    <div key="dm">
                      <div style={{ fontSize: 11, color: 'var(--ink-400)' }}>그중 수요</div>
                      <b className="mono" style={{ fontSize: 22, color: '#9aa0aa' }}>{Math.round(d.share_pct)}%</b>
                    </div>
                  );
                })}
                <div style={{ display: 'flex', gap: 6, alignSelf: 'center' }}>
                  {fc.forecast.degenerate && <Badge tone="red">점질량 — 구간 아님</Badge>}
                  {fc.forecast.point_dims && fc.forecast.point_dims.length > 0 && (
                    <Badge tone="amber">σ 없는 축 {fc.forecast.point_dims.length}개</Badge>
                  )}
                  {Object.keys(fc.forecast.dims_meta || {}).some(function (k) {
                    var m = fc.forecast.dims_meta[k];
                    return m && m.judgment_deferred;
                  }) && <Badge tone="red">판단 보류 — 자동 완화 안 함</Badge>}
                </div>
              </div>

              <div>
                <div style={{ fontSize: 10.5, fontFamily: MONO, color: 'var(--ink-400)', marginBottom: 5 }}>
                  구간폭 기여 — 이 폭을 무엇이 만들었나
                </div>
                {(fc.forecast.width_drivers || []).map(function (d) {
                  var w = Math.max(0, Math.min(100, d.share_pct || 0));
                  return (
                    <div key={d.key} style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
                      <span style={{ flex: '0 0 90px', fontSize: 11.5, fontFamily: MONO }}>{d.key}</span>
                      <div style={{ flex: 1, height: 12, borderRadius: 6, background: 'var(--surface-2)', overflow: 'hidden' }}>
                        <div style={{ width: w + '%', height: '100%', background: 'linear-gradient(90deg,#5fc7e0,#11303a)' }} />
                      </div>
                      <span className="mono" style={{ flex: '0 0 46px', textAlign: 'right', fontSize: 11.5 }}>{w.toFixed(1)}%</span>
                    </div>
                  );
                })}
              </div>

              {fc.forecast._meta && fc.forecast._meta.caution && (
                <div style={{ background: '#3a2a12', color: '#f0b46a', borderRadius: 6, padding: '8px 11px', fontSize: 11.5 }}>
                  {fc.forecast._meta.caution}
                </div>
              )}
              <div style={{ fontSize: 10.5, color: 'var(--ink-400)' }}>
                {fc._meta && fc._meta.assumption ? fc._meta.assumption : ''}
                {fc.forecast._meta && fc.forecast._meta.assumption ? ' ' + fc.forecast._meta.assumption : ''}
              </div>
              {fc._meta && fc._meta.adversary_review && (
                <div style={{ background: '#3a1b1b', color: '#e08a8a', borderRadius: 6, padding: '8px 11px', fontSize: 11.5 }}>
                  ★{fc._meta.adversary_review}
                </div>
              )}
            </div>
          )}
        </Card>

        {/* ④ 관측 공백 — 숨기지 않는다 */}
        <Card style={{ borderColor: uncollected.length ? '#3a2a12' : 'var(--border)' }}>
          <H4>관측 공백</H4>
          {uncollected.length === 0 ? (
            <div style={{ fontSize: 12, color: 'var(--ink-400)' }}>
              기록된 미수집 없음. <b>단, 아직 수집이 한 번도 안 돌았으면 이 칸도 비어 있다</b> — 위 n 을 함께 본다.
            </div>
          ) : (
            <div style={{ display: 'grid', gap: 5 }}>
              {uncollected.slice(0, 12).map(function (u, i) {
                return (
                  <div key={i} style={{ fontSize: 11.5, fontFamily: MONO, color: '#f0b46a' }}>
                    {u.stat_dt} · {u.fn} · {u.target} — {u.reason}
                  </div>
                );
              })}
              {uncollected.length > 12 && (
                <div style={{ fontSize: 11, color: 'var(--ink-400)' }}>+{uncollected.length - 12}건 더 (접음)</div>
              )}
            </div>
          )}
        </Card>
      </div>
    );
  }

  window.DemandTrendPanel = DemandTrendPanel;
})();
