/* キャラ辞典 — Hub (floating field) + Detail overlay */

const { useRef, useEffect, useState, useCallback } = React;

/* ----- personality-driven motion parameters ----- */
const MOTION = {
  restless: { speed: 82, turn: 0.13, dart: 0.014, dartMul: 2.4, bobSpd: 7.5, bobAmp: 4, rotAmp: 5 },
  calm:     { speed: 24, turn: 0.020, dart: 0,    dartMul: 1,   bobSpd: 1.7, bobAmp: 8, rotAmp: 3 },
  chaotic:  { speed: 60, turn: 0.18,  dart: 0.010, dartMul: 2.8, bobSpd: 9.5, bobAmp: 5, rotAmp: 8, flip: 0.010 },
  drift:    { speed: 15, turn: 0.012, dart: 0,    dartMul: 1,   bobSpd: 1.2, bobAmp: 6, rotAmp: 2 }
};

/* ----- small link-icon set ----- */
function LinkIcon({ kind }) {
  const s = { width: 22, height: 22, display: 'block' };
  if (kind === 'youtube') return (
    <svg viewBox="0 0 24 24" style={s}><rect x="2" y="5" width="20" height="14" rx="4" fill="currentColor"/><path d="M10 9l5 3-5 3z" fill="#fff"/></svg>);
  if (kind === 'note') return (
    <svg viewBox="0 0 24 24" style={s}><circle cx="12" cy="12" r="10" fill="none" stroke="currentColor" strokeWidth="2"/><path d="M8.5 16V9.2l6 6.3V8" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/></svg>);
  if (kind === 'shop') return (
    <svg viewBox="0 0 24 24" style={s}><path d="M6 8h12l-1 11H7L6 8z" fill="none" stroke="currentColor" strokeWidth="2" strokeLinejoin="round"/><path d="M9 8a3 3 0 0 1 6 0" fill="none" stroke="currentColor" strokeWidth="2"/></svg>);
  return ( /* site */
    <svg viewBox="0 0 24 24" style={s}><circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" strokeWidth="2"/><path d="M3 12h18M12 3c2.5 2.5 2.5 15 0 18M12 3c-2.5 2.5-2.5 15 0 18" fill="none" stroke="currentColor" strokeWidth="2"/></svg>);
}

/* ----- droplet logo mark (brand しずく) ----- */
function Droplet({ size = 30, color = '#FFDD55' }) {
  return (
    <svg width={size} height={size} viewBox="0 0 32 32" aria-hidden="true">
      <path d="M16 2c4.2 6.6 9 10.4 9 16a9 9 0 1 1-18 0c0-5.6 4.8-9.4 9-16z" fill={color} stroke="rgba(0,0,0,.12)" strokeWidth="0.6"/>
      <ellipse cx="12.6" cy="18.5" rx="2.1" ry="3.1" fill="#fff" opacity=".55"/>
    </svg>
  );
}

/* ====================================================================== */
/*  HUB — characters drifting in a field                                  */
/* ====================================================================== */
function Hub({ chars, onSelect, tweaks }) {
  const fieldRef = useRef(null);
  const elRefs = useRef({});
  const plateRefs = useRef({});
  const agents = useRef({});
  const [hovered, setHovered] = useState(null);
  const hoveredRef = useRef(null);
  const motionRef = useRef(1);
  const rafRef = useRef(0);

  hoveredRef.current = hovered;
  motionRef.current = tweaks.motion;

  const visible = chars.filter(c => tweaks.showComingSoon || !c.comingSoon);

  // (re)initialise agents whenever the visible set changes or on mount
  const initAgents = useCallback(() => {
    const field = fieldRef.current;
    if (!field) return;
    const W = field.clientWidth, H = field.clientHeight;
    visible.forEach((c, i) => {
      const el = elRefs.current[c.id];
      if (!el) return;
      const w = el.offsetWidth, h = el.offsetHeight;
      const prev = agents.current[c.id];
      // spread starting positions on a loose grid so they don't pile up
      const cols = Math.ceil(Math.sqrt(visible.length));
      const gx = (i % cols + 0.5) / cols, gy = (Math.floor(i / cols) + 0.5) / Math.ceil(visible.length / cols);
      agents.current[c.id] = {
        x: prev ? Math.min(prev.x, W - w) : gx * (W - w) + (Math.random() - 0.5) * 60,
        y: prev ? Math.min(prev.y, H - h) : gy * (H - h) + (Math.random() - 0.5) * 60,
        heading: prev ? prev.heading : Math.random() * Math.PI * 2,
        spd: 1,
        t: Math.random() * 10,
        w, h, flip: Math.random() < 0.5 ? -1 : 1
      };
      // apply initial position immediately so chars are spread out
      // even before the first animation frame (e.g. while tab is hidden)
      const a = agents.current[c.id];
      el.style.transform = `translate(${a.x.toFixed(1)}px, ${a.y.toFixed(1)}px)`;
      const plate = plateRefs.current[c.id];
      if (plate) plate.style.transform = `translate(${(a.x + a.w / 2).toFixed(1)}px, ${a.y.toFixed(1)}px)`;
    });
  }, [visible.map(c => c.id).join(',')]);

  useEffect(() => {
    const id = setTimeout(initAgents, 30); // let images lay out
    window.addEventListener('resize', initAgents);
    return () => { clearTimeout(id); window.removeEventListener('resize', initAgents); };
  }, [initAgents]);

  // animation loop
  useEffect(() => {
    let last = performance.now();
    const loop = (now) => {
      let dt = (now - last) / 1000; last = now;
      if (dt > 0.05) dt = 0.05; // clamp tab-switch jumps
      const field = fieldRef.current;
      if (field) {
        const W = field.clientWidth, H = field.clientHeight;
        const intensity = motionRef.current;
        for (const c of visible) {
          const a = agents.current[c.id];
          const el = elRefs.current[c.id];
          if (!a || !el) continue;
          const p = MOTION[c.motion] || MOTION.drift;
          const isHover = hoveredRef.current === c.id;
          a.t += dt;
          if (!isHover && intensity > 0) {
            // wander heading
            a.heading += (Math.random() - 0.5) * p.turn;
            if (p.flip && Math.random() < p.flip) a.heading += (Math.random() - 0.5) * 2.4;
            // speed easing + occasional darts
            let target = 1;
            if (p.dart && Math.random() < p.dart) a.spd = p.dartMul;
            a.spd += (target - a.spd) * Math.min(1, dt * 2);
            const v = p.speed * a.spd * intensity;
            a.x += Math.cos(a.heading) * v * dt;
            a.y += Math.sin(a.heading) * v * dt;
            // bounce off field edges
            if (a.x < 0) { a.x = 0; a.heading = Math.PI - a.heading; a.flip *= -1; }
            if (a.x > W - a.w) { a.x = W - a.w; a.heading = Math.PI - a.heading; a.flip *= -1; }
            if (a.y < 0) { a.y = 0; a.heading = -a.heading; }
            if (a.y > H - a.h) { a.y = H - a.h; a.heading = -a.heading; }
          }
          const bob = Math.sin(a.t * p.bobSpd) * p.bobAmp;
          const rot = Math.sin(a.t * p.bobSpd * 0.6) * p.rotAmp;
          const hoverLift = isHover ? -6 : 0;
          const scale = isHover ? 1.08 : 1;
          el.style.transform =
            `translate(${a.x.toFixed(1)}px, ${(a.y + bob + hoverLift).toFixed(1)}px) rotate(${rot.toFixed(2)}deg) scale(${scale})`;
          // nameplate follows (anchored at char top-centre, no rotation)
          const plate = plateRefs.current[c.id];
          if (plate) plate.style.transform = `translate(${(a.x + a.w / 2).toFixed(1)}px, ${a.y.toFixed(1)}px)`;
        }
      }
      rafRef.current = requestAnimationFrame(loop);
    };
    rafRef.current = requestAnimationFrame(loop);
    return () => cancelAnimationFrame(rafRef.current);
  }, [visible.map(c => c.id).join(',')]);

  const hoverStyle = tweaks.hoverStyle;

  return (
    <div className="field" ref={fieldRef}>
      {/* spotlight dimmer */}
      {hoverStyle === 'spotlight' && hovered &&
        <div className="field-dim" />}

      {visible.map((c) => {
        const isHover = hovered === c.id;
        const glow = isHover && hoverStyle === 'glow';
        return (
          <React.Fragment key={c.id}>
            <button
              className={'char' + (isHover ? ' is-hover hl-' + hoverStyle : '') + (c.comingSoon ? ' is-cs' : '')}
              ref={el => (elRefs.current[c.id] = el)}
              style={{
                '--cc': c.color,
                width: Math.round((c.natW / c.natH) * (c.comingSoon ? 92 : 132)) + 'px',
                zIndex: isHover ? 50 : (c.comingSoon ? 5 : 10),
                filter: glow ? `drop-shadow(0 0 0 ${c.color}) drop-shadow(0 0 14px ${c.color})` : 'none'
              }}
              onMouseEnter={() => setHovered(c.id)}
              onMouseLeave={() => setHovered(h => (h === c.id ? null : h))}
              onFocus={() => setHovered(c.id)}
              onBlur={() => setHovered(h => (h === c.id ? null : h))}
              onClick={() => { if (c.comingSoon) setHovered(c.id); else onSelect(c.id); }}
              aria-label={c.comingSoon ? '近日登場のキャラクター' : c.name + ' のしょうかいを見る'}
            >
              {isHover && hoverStyle === 'ring' && <span className="ring" />}
              {c.img
                ? <img src={c.img} alt="" draggable="false" style={{ width: '100%', display: 'block' }} />
                : <span className="silhouette" style={{ color: c.color }}>?</span>}
            </button>

            {/* nameplate */}
            <div
              className={'plate' + (isHover && !c.comingSoon ? ' show' : '')}
              ref={el => (plateRefs.current[c.id] = el)}
              style={{ zIndex: 60 }}
              aria-hidden="true"
            >
              {!c.comingSoon && (
                <span className="plate-pill" style={{ '--cc': c.color }}>
                  <span className="plate-dot" />
                  <span className="plate-name">{c.name}</span>
                  <span className="plate-role">{c.role}</span>
                </span>
              )}
              {c.comingSoon && (
                <span className={'plate-pill cs-pill' + (isHover ? ' show-cs' : '')}>
                  <span className="plate-name">近日登場</span>
                  <span className="plate-role">Coming soon</span>
                </span>
              )}
            </div>
          </React.Fragment>
        );
      })}
    </div>
  );
}

/* ====================================================================== */
/*  DETAIL — full-screen character profile                                */
/* ====================================================================== */
function Detail({ char, layout, onClose }) {
  const [closing, setClosing] = useState(false);
  const handleClose = () => { setClosing(true); setTimeout(onClose, 260); };
  useEffect(() => {
    const onKey = e => { if (e.key === 'Escape') handleClose(); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, []);

  const animClass = 'pose pose-' + char.motion;

  const figure = (
    <div className="detail-figure" style={{ '--cc': char.color }}>
      <div className="stage-glow" />
      <div className={animClass}>
        {char.img
          ? <img src={char.img} alt={char.name} draggable="false" />
          : <div className="detail-silhouette" style={{ color: char.color }}>
              <span>?</span>
            </div>}
      </div>
      <div className="stage-floor" />
      {char.instrument &&
        <div className="instrument-chip">♪ {char.instrument}</div>}
    </div>
  );

  const info = (
    <div className="detail-info">
      <div className="eyebrow">
        <span className="eyebrow-en" style={{ color: char.color }}>{char.en}</span>
        <span className="eyebrow-rd">{char.reading}</span>
      </div>
      <h2 className="detail-name">{char.name}</h2>
      <div className="detail-role">{char.role}</div>

      <p className="detail-lead">{char.lead}</p>
      <p className="detail-body">{char.body}</p>

      <div className="trait-row">
        {(char.personality || []).map(t => <span className="trait" key={t} style={{ '--cc': char.color }}>{t}</span>)}
      </div>
      <div className="tag-row">
        {(char.tags || []).map(t => <span className="tag" key={t}>#{t}</span>)}
      </div>

      {char.note && <div className="detail-note">{char.note}</div>}

      <div className="link-block">
        <div className="link-head">RELATED LINKS</div>
        <div className="link-grid">
          {(char.links || []).map((l, i) => (
            <a className="link-card" key={i} href={l.href}
               target={l.href.startsWith('http') ? '_blank' : undefined}
               rel="noopener noreferrer" style={{ '--cc': char.color }}>
              <span className="link-ic" style={{ color: char.color }}><LinkIcon kind={l.kind} /></span>
              <span className="link-tx">
                <span className="link-lb">{l.label}</span>
                <span className="link-sb">{l.sub}</span>
              </span>
              <span className="link-arrow">→</span>
            </a>
          ))}
        </div>
      </div>
    </div>
  );

  return (
    <div className={'detail-overlay' + (closing ? ' closing' : '')}
         onClick={handleClose}>
      <div className={'detail-card layout-' + layout}
           style={{ '--cc': char.color }}
           onClick={e => e.stopPropagation()}>
        <button className="detail-close" onClick={handleClose} aria-label="とじる">✕</button>
        {layout === 'magazine'
          ? <><div className="mag-band" style={{ background: char.color }} />{figure}{info}</>
          : <>{figure}{info}</>}
      </div>
    </div>
  );
}

Object.assign(window, { Hub, Detail, Droplet, LinkIcon });
