// Komonichi — practitioner ranks. 10 levels from Novice to Divine.
// Computed purely from existing client state (tasks, focus log). The "kanji
// scene" on the right of the shell ripens as the practitioner climbs.
(function () {
  const { useKomonichi, sel, fmtMin } = window.KomonichiState;

  // ----- levels -----------------------------------------------------------
  // Each level is a target. Reaching its `req` thresholds means you ARE that
  // level. The first level is the floor — everyone starts here.
  //
  // Levels 1–3 measure effort: tasks completed + minutes attended.
  // Levels 4+ also measure consistency (streak days) and commitment — the
  // fraction of *dated* tasks completed on or before the day they were
  // scheduled for. `dated` is the minimum number of scheduled-and-completed
  // tasks the rate is computed over, so the commitment number is a real
  // sample rather than a single lucky on-time finish.
  const LEVELS = [
    { n: 1,  kanji: "初", name: "Novice",     romaji: "shoshin", req: { tasks: 0,    minutes: 0,     streak: 0,  onTime: 0,    dated: 0  } },
    { n: 2,  kanji: "学", name: "Apprentice", romaji: "manabi",  req: { tasks: 5,    minutes: 60,    streak: 0,  onTime: 0,    dated: 0  } },
    { n: 3,  kanji: "修", name: "Disciple",   romaji: "shū",     req: { tasks: 20,   minutes: 300,   streak: 0,  onTime: 0,    dated: 0  } },
    { n: 4,  kanji: "道", name: "Wayfarer",   romaji: "dō",      req: { tasks: 50,   minutes: 900,   streak: 3,  onTime: 0.60, dated: 10 } },
    { n: 5,  kanji: "行", name: "Adept",      romaji: "gyō",     req: { tasks: 100,  minutes: 1800,  streak: 7,  onTime: 0.70, dated: 25 } },
    { n: 6,  kanji: "達", name: "Master",     romaji: "tatsu",   req: { tasks: 200,  minutes: 3600,  streak: 14, onTime: 0.75, dated: 50 } },
    { n: 7,  kanji: "師", name: "Sage",       romaji: "shi",     req: { tasks: 400,  minutes: 7200,  streak: 21, onTime: 0.80, dated: 100 } },
    { n: 8,  kanji: "玄", name: "Profound",   romaji: "gen",     req: { tasks: 700,  minutes: 12000, streak: 30, onTime: 0.85, dated: 175 } },
    { n: 9,  kanji: "妙", name: "Sublime",    romaji: "myō",     req: { tasks: 1100, minutes: 18000, streak: 45, onTime: 0.90, dated: 275 } },
    { n: 10, kanji: "神", name: "Divine",     romaji: "shin",    req: { tasks: 1700, minutes: 27000, streak: 60, onTime: 0.95, dated: 425 } },
  ];

  // ----- metric helpers ---------------------------------------------------
  function computeMetrics(state) {
    const tasks = state.tasks || [];
    const done = tasks.filter(t => t.status === "done" && t.completedAt);
    const tasksDone = done.length;
    const totalFocusMin = sel.totalFocusMin();
    const streak = sel.streakDays();

    // Commitment: of *dated* done tasks (i.e. tasks that had a dueDate and
    // were completed), the fraction completed on or before that date.
    // Undated tasks are excluded — you can't be on-time for something that
    // was never scheduled. The denominator (`datedCount`) is reported too so
    // the bar can show "build more dated history" when the sample is thin.
    const dated = done.filter(t => t.dueDate);
    let onTime = 0;
    if (dated.length) {
      const k = (s) => new Date(s).toISOString().slice(0, 10);
      const ok = dated.filter(t => k(t.completedAt) <= t.dueDate).length;
      onTime = ok / dated.length;
    }
    return { tasksDone, totalFocusMin, streak, onTime, datedCount: dated.length };
  }

  // Per-criterion progress to a level (0..1 each). The "commitment" bucket
  // is the *minimum* of (rate / required rate) and (dated count / required
  // dated count) — both the rate and the sample size must be met. This
  // prevents accidental promotion off a single on-time completion, and the
  // bar visibly shows whichever side is lagging.
  function progressTo(metrics, req) {
    const safe = (n, d) => d > 0 ? Math.min(1, n / d) : 1;
    const ratePart  = req.onTime > 0 ? Math.min(1, metrics.onTime / req.onTime) : 1;
    const countPart = req.dated  > 0 ? Math.min(1, metrics.datedCount / req.dated) : 1;
    return {
      tasks:   safe(metrics.tasksDone,     req.tasks),
      minutes: safe(metrics.totalFocusMin, req.minutes),
      streak:  safe(metrics.streak,        req.streak),
      // Commitment combines two sub-gates. The display below shows the gating
      // side: rate when sample is sufficient, count when it isn't.
      onTime:  Math.min(ratePart, countPart),
      onTimeRate:  ratePart,
      onTimeCount: countPart,
    };
  }

  function meets(metrics, req) {
    const p = progressTo(metrics, req);
    return p.tasks >= 1 && p.minutes >= 1 && p.streak >= 1 && p.onTime >= 1;
  }

  // Current level: highest level whose requirements are met. Always ≥ 1.
  function currentLevel(metrics) {
    let cur = LEVELS[0];
    for (const lv of LEVELS) if (meets(metrics, lv.req)) cur = lv;
    return cur;
  }

  function useRank() {
    const [state] = useKomonichi();
    const metrics = React.useMemo(() => computeMetrics(state), [state]);
    const current = currentLevel(metrics);
    const nextIdx = LEVELS.findIndex(l => l.n === current.n) + 1;
    const next = nextIdx < LEVELS.length ? LEVELS[nextIdx] : null;
    return { metrics, current, next };
  }

  // ----- SVG scene --------------------------------------------------------
  // The kanji sits in front of a scene that gains layers as level rises.
  // Each layer is keyed to a level threshold; we render anything ≤ current.
  // The hues drift from sumi/moss → indigo → persimmon → gold.
  function paletteFor(level) {
    if (level >= 9) return { ink: "#2c2620", accent: "#b3924f", mist: "rgba(179,146,79,0.18)", deep: "rgba(179,146,79,0.32)", sky: "linear-gradient(180deg, #fbeac4 0%, #f3d49a 60%, #e6b97a 100%)" };
    if (level >= 7) return { ink: "#2c2620", accent: "#c46c4a", mist: "rgba(196,108,74,0.16)", deep: "rgba(196,108,74,0.30)", sky: "linear-gradient(180deg, #fbe1d2 0%, #f3c4ab 70%, #d99373 100%)" };
    if (level >= 5) return { ink: "#2c2620", accent: "#4d5a6e", mist: "rgba(77,90,110,0.16)", deep: "rgba(77,90,110,0.30)", sky: "linear-gradient(180deg, #e5e9f0 0%, #c8d0dc 70%, #98a3b6 100%)" };
    if (level >= 3) return { ink: "#2c2620", accent: "#7d8666", mist: "rgba(125,134,102,0.14)", deep: "rgba(125,134,102,0.26)", sky: "linear-gradient(180deg, #f0eedb 0%, #d8d6bd 70%, #a8a787 100%)" };
    return { ink: "#2c2620", accent: "#9a907f", mist: "rgba(154,144,127,0.10)", deep: "rgba(154,144,127,0.20)", sky: "linear-gradient(180deg, #faf6ec 0%, #ebe2cc 100%)" };
  }

  function KanjiScene({ level }) {
    const p = paletteFor(level);
    const n = level;
    return (
      <div className="rank-scene" style={{ background: p.sky }}>
        <svg viewBox="0 0 200 320" preserveAspectRatio="xMidYMid slice" width="100%" height="100%">
          <defs>
            <linearGradient id={`mist-${n}`} x1="0" x2="0" y1="0" y2="1">
              <stop offset="0%" stopColor={p.mist} stopOpacity="0"/>
              <stop offset="50%" stopColor={p.mist} stopOpacity="1"/>
              <stop offset="100%" stopColor={p.mist} stopOpacity="0"/>
            </linearGradient>
            <radialGradient id={`sun-${n}`} cx="50%" cy="50%" r="50%">
              <stop offset="0%" stopColor={p.accent} stopOpacity="0.85"/>
              <stop offset="60%" stopColor={p.accent} stopOpacity="0.20"/>
              <stop offset="100%" stopColor={p.accent} stopOpacity="0"/>
            </radialGradient>
          </defs>

          {/* L2+: a single faint horizon line */}
          {n >= 2 && <line x1="0" y1="230" x2="200" y2="230" stroke={p.deep} strokeWidth="0.6" opacity="0.5" />}

          {/* L3+: distant mountain silhouette */}
          {n >= 3 && (
            <path d="M0,240 L30,210 L55,225 L85,195 L115,220 L145,200 L175,225 L200,210 L200,260 L0,260 Z"
              fill={p.deep} opacity="0.55" />
          )}

          {/* L4+: mid mist band */}
          {n >= 4 && <rect x="0" y="190" width="200" height="60" fill={`url(#mist-${n})`} />}

          {/* L5+: closer ridge in front */}
          {n >= 5 && (
            <path d="M0,260 L25,245 L60,255 L95,235 L130,250 L165,238 L200,252 L200,300 L0,300 Z"
              fill={p.deep} opacity="0.78" />
          )}

          {/* L6+: a high sun/moon disc */}
          {n >= 6 && <circle cx="148" cy="68" r="22" fill={`url(#sun-${n})`} />}
          {n >= 6 && <circle cx="148" cy="68" r="14" fill={p.accent} opacity="0.55" />}

          {/* L7+: drifting clouds / cranes — two thin brush strokes */}
          {n >= 7 && (
            <g opacity="0.7">
              <path d="M22,90 Q40,82 60,90 T100,90" stroke={p.ink} strokeWidth="0.8" fill="none" opacity="0.45" />
              <path d="M30,108 Q44,102 58,108" stroke={p.ink} strokeWidth="0.7" fill="none" opacity="0.35" />
            </g>
          )}

          {/* L8+: a pine bough, lower-left corner */}
          {n >= 8 && (
            <g opacity="0.75">
              <path d="M-2,300 Q20,265 38,255 Q52,250 64,256" stroke={p.ink} strokeWidth="1.2" fill="none" />
              <path d="M16,278 q4,-8 12,-8" stroke={p.ink} strokeWidth="0.8" fill="none" />
              <path d="M30,266 q5,-6 12,-6" stroke={p.ink} strokeWidth="0.8" fill="none" />
              <path d="M46,260 q4,-4 10,-4" stroke={p.ink} strokeWidth="0.8" fill="none" />
            </g>
          )}

          {/* L9+: falling petals (sakura) */}
          {n >= 9 && (
            <g fill={p.accent} opacity="0.7">
              <circle cx="60"  cy="48"  r="1.6" />
              <circle cx="92"  cy="120" r="1.4" />
              <circle cx="38"  cy="160" r="1.2" />
              <circle cx="170" cy="142" r="1.5" />
              <circle cx="120" cy="200" r="1.3" />
            </g>
          )}

          {/* L10: gilded halo behind the kanji + extra peak glint */}
          {n >= 10 && (
            <g>
              <circle cx="100" cy="160" r="92" fill="none" stroke={p.accent} strokeWidth="0.6" opacity="0.55"
                strokeDasharray="2 4" />
              <circle cx="100" cy="160" r="72" fill="none" stroke={p.accent} strokeWidth="0.4" opacity="0.45" />
              <path d="M85,195 L92,188 L96,193 L102,184 L108,192 L115,196" stroke={p.accent} strokeWidth="0.8" fill="none" opacity="0.85" />
            </g>
          )}

          {/* faint vertical center seam from L4+ — the scroll fold */}
          {n >= 4 && <line x1="100" y1="20" x2="100" y2="300" stroke={p.deep} strokeWidth="0.3" opacity="0.35" />}
        </svg>
      </div>
    );
  }

  // ----- background painting ----------------------------------------------
  function RankPanel() {
    const { current } = useRank();
    const p = paletteFor(current.n);
    const ref = React.useRef(null);

    // The painting only "blooms forward" once the cursor leaves the main
    // content (the centered .shell column) and moves into the right margin by
    // a comfortable buffer — so it never wakes while you're working over a
    // card. Driven here rather than via :hover so the threshold can include
    // that buffer. We toggle the class on the node directly to avoid a React
    // re-render on every mouse move.
    React.useEffect(() => {
      const BUFFER = 64; // px past the content's right edge before it stirs
      let on = false;
      const set = (v) => {
        if (v === on) return;
        on = v;
        if (ref.current) ref.current.classList.toggle('active', v);
      };
      const onMove = (e) => {
        const shell = document.querySelector('.shell');
        const edge = shell ? shell.getBoundingClientRect().right : window.innerWidth;
        set(e.clientX > edge + BUFFER);
      };
      const onLeave = () => set(false);
      window.addEventListener('mousemove', onMove, { passive: true });
      document.addEventListener('mouseleave', onLeave);
      window.addEventListener('blur', onLeave);
      return () => {
        window.removeEventListener('mousemove', onMove);
        document.removeEventListener('mouseleave', onLeave);
        window.removeEventListener('blur', onLeave);
      };
    }, []);

    return (
      <div className="rank-painting" ref={ref} aria-hidden="true">
        <KanjiScene level={current.n} />
        <div className="rank-kanji-front" style={{ color: p.ink }}>
          <span className="rank-kanji">{current.kanji}</span>
          <div className="rank-name-overlay">
            <div className="label" style={{ color: p.accent, letterSpacing: '0.32em' }}>level {current.n} · {current.romaji}</div>
            <div className="rank-name">{current.name}</div>
          </div>
        </div>
      </div>
    );
  }

  // Compact ladder showing all 10 levels, current one highlighted.
  function RankLadder() {
    const { current } = useRank();
    return (
      <div className="card">
        <div className="cap">
          <div>
            <div className="label">the ten steps</div>
            <h3 className="title">the <span className="em">ladder</span></h3>
          </div>
          <span className="chip">{current.n} / {LEVELS.length}</span>
        </div>
        <div className="rank-ladder">
          {LEVELS.map(lv => {
            const isCur  = lv.n === current.n;
            const isPast = lv.n <  current.n;
            return (
              <div key={lv.n} className={'rank-ladder-row' + (isCur ? ' active' : '') + (isPast ? ' past' : '')}>
                <span className="rank-ladder-kanji">{lv.kanji}</span>
                <div className="rank-ladder-name">
                  <div className="rank-ladder-en">{lv.name}</div>
                  <div className="label">{lv.romaji}</div>
                </div>
                <span className="rank-ladder-num">{lv.n}</span>
              </div>
            );
          })}
        </div>
      </div>
    );
  }

  function RankCard() {
    const { metrics, current, next } = useRank();
    return (
      <div className="card">
        <div className="cap">
          <div>
            <div className="label">the practitioner's path</div>
            <h3 className="title"><span className="em">rank</span>, gently kept</h3>
          </div>
          <span className="chip" style={{ color: 'var(--persimmon)', borderColor: 'rgba(196,108,74,0.4)' }}>
            <span style={{ fontFamily: 'var(--serif)', fontSize: 14, marginRight: 6 }}>{current.kanji}</span>
            {current.name} · L{current.n}
          </span>
        </div>
        <RankProgress metrics={metrics} current={current} next={next} />
      </div>
    );
  }

  function Bar({ value, label, detail, tone }) {
    const pct = Math.round(value * 100);
    return (
      <div className="rank-bar-row">
        <div className="rank-bar-head">
          <span className="label">{label}</span>
          <span className="num" style={{ color: pct >= 100 ? 'var(--persimmon)' : 'var(--ink-faint)' }}>
            {detail}
          </span>
        </div>
        <div className="rank-bar-track">
          <div className="rank-bar-fill" style={{ width: pct + '%', background: `var(--${tone || 'persimmon'})` }} />
        </div>
      </div>
    );
  }

  function RankProgress({ metrics, current, next }) {
    if (!next) {
      return (
        <div className="rank-progress">
          <div className="label">the path opens</div>
          <h4 className="rank-progress-title">the way is yours</h4>
          <p className="haiku" style={{ fontSize: 14, marginTop: 8 }}>
            <span className="line">No further rank.</span>
            <span className="line">The practice itself</span>
            <span className="line">is the reward.</span>
          </p>
        </div>
      );
    }
    const prog = progressTo(metrics, next.req);
    const showRefined = next.req.streak > 0 || next.req.onTime > 0;

    // Commitment bar — show whichever sub-gate is lagging. If the sample is
    // too thin (datedCount < req.dated), we report the count rather than the
    // rate, so the practitioner sees what's actually blocking promotion.
    let commitDetail, commitValue;
    if (showRefined) {
      const sampleLow = next.req.dated > 0 && metrics.datedCount < next.req.dated;
      if (sampleLow) {
        commitDetail = `${metrics.datedCount} / ${next.req.dated} dated`;
        commitValue  = prog.onTimeCount;
      } else if (metrics.datedCount === 0) {
        commitDetail = 'no dated tasks yet';
        commitValue  = 0;
      } else {
        commitDetail = `${Math.round(metrics.onTime * 100)}% / ${Math.round(next.req.onTime * 100)}%`;
        commitValue  = prog.onTimeRate;
      }
    }

    return (
      <div className="rank-progress">
        <div className="label">toward</div>
        <h4 className="rank-progress-title">
          <span className="rank-progress-kanji">{next.kanji}</span> {next.name}
          <span className="rank-progress-romaji"> · {next.romaji}</span>
        </h4>
        <div className="rank-bars">
          <Bar value={prog.tasks}
               label="tasks kept"
               detail={`${metrics.tasksDone} / ${next.req.tasks}`}
               tone="persimmon" />
          <Bar value={prog.minutes}
               label="time attended"
               detail={`${fmtMin(metrics.totalFocusMin)} / ${fmtMin(next.req.minutes)}`}
               tone="moss" />
          {showRefined && (
            <Bar value={prog.streak}
                 label="streak"
                 detail={`${metrics.streak} / ${next.req.streak} d`}
                 tone="indigo" />
          )}
          {showRefined && (
            <Bar value={commitValue}
                 label="on the day"
                 detail={commitDetail}
                 tone="gold" />
          )}
        </div>
      </div>
    );
  }

  window.KomonichiRank = {
    LEVELS, useRank, RankPanel, RankCard, RankLadder,
  };
})();
