// Procedural 3D mascot avatars — original cute stylized creatures
// Built with Three.js primitives. No external models required.

const AvatarFactory = (() => {

  // ---------- helpers ----------
  function softMat(color, opts = {}) {
    return new THREE.MeshStandardMaterial({
      color,
      roughness: opts.roughness ?? 0.55,
      metalness: opts.metalness ?? 0.05,
      ...opts.extra,
    });
  }

  // Build one avatar. Returns an object with .group (THREE.Group) + animation API.
  // variant: cobalt | rose | gold | jade | violet | amber
  const PALETTES = {
    cobalt:  { body: 0x2a3d5c, bodyDark: 0x1a2740, belly: 0xe8d6b8, cheek: 0x6fa8c9, accent: 0x4ba3c7, sash: 0x2d6a8c },
    rose:    { body: 0x3b2a4a, bodyDark: 0x271a35, belly: 0xf0d8d0, cheek: 0xff8da0, accent: 0xd968a6, sash: 0x9a3d7a },
    gold:    { body: 0x5c4524, bodyDark: 0x3c2c14, belly: 0xfae6c4, cheek: 0xf0c060, accent: 0xeec050, sash: 0x9c7028 },
    jade:    { body: 0x244c3c, bodyDark: 0x143028, belly: 0xe2eccc, cheek: 0x8cc890, accent: 0x60c890, sash: 0x408060 },
    violet:  { body: 0x382c5e, bodyDark: 0x241a40, belly: 0xe4dceb, cheek: 0xb59cf0, accent: 0x8a78e8, sash: 0x4c3a8a },
    amber:   { body: 0x5c2a18, bodyDark: 0x3c1a0e, belly: 0xf6dccc, cheek: 0xff9c70, accent: 0xff8c40, sash: 0x9c4820 },
  };
  function buildAvatar(variant = 'cobalt') {
    const palette = PALETTES[variant] || PALETTES.cobalt;

    const group = new THREE.Group();

    // ---------- body (rounded torso) ----------
    const bodyGeo = new THREE.SphereGeometry(0.85, 32, 32);
    bodyGeo.scale(1, 1.05, 0.95);
    const body = new THREE.Mesh(bodyGeo, softMat(palette.body, { roughness: 0.7 }));
    body.position.y = 0.85;
    body.castShadow = true;
    group.add(body);

    // belly patch
    const bellyGeo = new THREE.SphereGeometry(0.55, 24, 24);
    bellyGeo.scale(1, 1.1, 0.4);
    const belly = new THREE.Mesh(bellyGeo, softMat(palette.belly, { roughness: 0.85 }));
    belly.position.set(0, 0.75, 0.55);
    group.add(belly);

    // sash (decorative band)
    const sashGeo = new THREE.TorusGeometry(0.7, 0.08, 12, 48, Math.PI * 1.1);
    const sash = new THREE.Mesh(sashGeo, softMat(palette.sash, { roughness: 0.4, metalness: 0.2 }));
    sash.position.set(0, 0.95, 0);
    sash.rotation.x = Math.PI / 2;
    sash.rotation.y = variant === 'cobalt' ? -0.4 : 0.4;
    group.add(sash);

    // ---------- head ----------
    const headPivot = new THREE.Group();
    headPivot.position.y = 1.85;
    group.add(headPivot);

    const headGeo = new THREE.SphereGeometry(0.78, 40, 40);
    headGeo.scale(1.05, 1, 1);
    const head = new THREE.Mesh(headGeo, softMat(palette.body, { roughness: 0.7 }));
    head.castShadow = true;
    headPivot.add(head);

    // muzzle / snout area
    const muzzleGeo = new THREE.SphereGeometry(0.42, 24, 24);
    muzzleGeo.scale(1.1, 0.85, 0.7);
    const muzzle = new THREE.Mesh(muzzleGeo, softMat(palette.belly, { roughness: 0.85 }));
    muzzle.position.set(0, -0.12, 0.55);
    headPivot.add(muzzle);

    // nose
    const noseGeo = new THREE.SphereGeometry(0.11, 16, 16);
    noseGeo.scale(1.2, 0.8, 0.9);
    const nose = new THREE.Mesh(noseGeo, softMat(0x111111, { roughness: 0.3 }));
    nose.position.set(0, 0.05, 0.95);
    headPivot.add(nose);

    // ears
    function makeEar(side) {
      const earGroup = new THREE.Group();
      const outer = new THREE.Mesh(
        new THREE.SphereGeometry(0.22, 20, 20),
        softMat(palette.body, { roughness: 0.7 })
      );
      const inner = new THREE.Mesh(
        new THREE.SphereGeometry(0.14, 16, 16),
        softMat(palette.cheek, { roughness: 0.6 })
      );
      inner.position.z = 0.08;
      inner.scale.set(1, 1, 0.3);
      earGroup.add(outer, inner);
      earGroup.position.set(side * 0.55, 0.55, -0.05);
      earGroup.rotation.z = side * 0.1;
      earGroup.scale.set(1, 1, 0.7);
      return earGroup;
    }
    headPivot.add(makeEar(-1));
    headPivot.add(makeEar(1));

    // eyes
    function makeEye(side) {
      const eyeGroup = new THREE.Group();
      // white
      const white = new THREE.Mesh(
        new THREE.SphereGeometry(0.13, 24, 24),
        softMat(0xffffff, { roughness: 0.2 })
      );
      // iris
      const iris = new THREE.Mesh(
        new THREE.SphereGeometry(0.085, 20, 20),
        softMat(palette.accent, { roughness: 0.3, metalness: 0.1 })
      );
      iris.position.z = 0.08;
      iris.scale.set(1, 1, 0.4);
      // pupil
      const pupil = new THREE.Mesh(
        new THREE.SphereGeometry(0.05, 16, 16),
        softMat(0x0a0a14, { roughness: 0.2 })
      );
      pupil.position.z = 0.11;
      pupil.scale.set(1, 1, 0.3);
      // shine
      const shine = new THREE.Mesh(
        new THREE.SphereGeometry(0.018, 12, 12),
        softMat(0xffffff, { roughness: 0.1, extra: { emissive: 0xffffff, emissiveIntensity: 0.5 } })
      );
      shine.position.set(0.025, 0.025, 0.13);

      eyeGroup.add(white, iris, pupil, shine);
      eyeGroup.position.set(side * 0.27, 0.18, 0.62);
      eyeGroup.rotation.y = side * 0.1;
      return { group: eyeGroup, white, iris, pupil };
    }
    const eyeL = makeEye(-1);
    const eyeR = makeEye(1);
    headPivot.add(eyeL.group, eyeR.group);

    // eyelids (for blinking) — thin discs in front of eyes that scale to "close"
    function makeLid(eye, side) {
      const lid = new THREE.Mesh(
        new THREE.SphereGeometry(0.135, 20, 20, 0, Math.PI * 2, 0, Math.PI / 2),
        softMat(palette.body, { roughness: 0.7 })
      );
      lid.position.copy(eye.group.position);
      lid.position.z -= 0.005;
      lid.rotation.x = 0;
      lid.scale.y = 0.001; // closed = 1
      return lid;
    }
    const lidL = makeLid(eyeL, -1);
    const lidR = makeLid(eyeR, 1);
    headPivot.add(lidL, lidR);

    // cheeks (blush)
    function makeCheek(side) {
      const c = new THREE.Mesh(
        new THREE.SphereGeometry(0.09, 16, 16),
        softMat(palette.cheek, { roughness: 0.8, extra: { transparent: true, opacity: 0.7 } })
      );
      c.position.set(side * 0.42, -0.08, 0.55);
      c.scale.set(1, 0.6, 0.3);
      return c;
    }
    headPivot.add(makeCheek(-1));
    headPivot.add(makeCheek(1));

    // mouth (animatable jaw)
    const mouthPivot = new THREE.Group();
    mouthPivot.position.set(0, -0.18, 0.78);
    headPivot.add(mouthPivot);

    const mouthGeo = new THREE.SphereGeometry(0.11, 20, 20, 0, Math.PI * 2, 0, Math.PI / 2);
    mouthGeo.scale(1.3, 1, 0.5);
    const mouth = new THREE.Mesh(mouthGeo, softMat(0x2a0a14, { roughness: 0.4 }));
    mouth.rotation.x = Math.PI;
    mouthPivot.add(mouth);

    // tongue hint
    const tongueGeo = new THREE.SphereGeometry(0.07, 16, 16);
    tongueGeo.scale(1.2, 0.3, 0.6);
    const tongue = new THREE.Mesh(tongueGeo, softMat(0xc94a6a, { roughness: 0.5 }));
    tongue.position.y = -0.04;
    mouthPivot.add(tongue);

    // accessory: bow (rose) or cap-tuft (cobalt)
    if (variant === 'rose') {
      const bow = new THREE.Group();
      const knot = new THREE.Mesh(
        new THREE.SphereGeometry(0.09, 16, 16),
        softMat(0xff5c8a, { roughness: 0.4 })
      );
      const loopL = new THREE.Mesh(
        new THREE.TorusGeometry(0.13, 0.05, 8, 24),
        softMat(0xff5c8a, { roughness: 0.4 })
      );
      loopL.position.x = -0.16;
      loopL.scale.set(1, 1, 0.3);
      const loopR = loopL.clone();
      loopR.position.x = 0.16;
      bow.add(knot, loopL, loopR);
      bow.position.set(0.45, 0.7, 0.1);
      bow.rotation.z = -0.3;
      headPivot.add(bow);
    } else {
      // small leaf tuft
      const tuft = new THREE.Mesh(
        new THREE.ConeGeometry(0.08, 0.22, 6),
        softMat(0x6fc28a, { roughness: 0.6 })
      );
      tuft.position.set(0, 0.85, 0);
      tuft.rotation.z = 0.2;
      headPivot.add(tuft);
    }

    // ---------- arms ----------
    function makeArm(side) {
      const armGroup = new THREE.Group();
      const upper = new THREE.Mesh(
        new THREE.CapsuleGeometry(0.18, 0.45, 8, 16),
        softMat(palette.body, { roughness: 0.7 })
      );
      upper.castShadow = true;
      armGroup.add(upper);

      const handPivot = new THREE.Group();
      handPivot.position.y = -0.45;
      const hand = new THREE.Mesh(
        new THREE.SphereGeometry(0.2, 20, 20),
        softMat(palette.bodyDark, { roughness: 0.65 })
      );
      hand.scale.set(1, 0.9, 1);
      hand.castShadow = true;
      handPivot.add(hand);
      armGroup.add(handPivot);

      armGroup.position.set(side * 0.78, 1.35, 0.05);
      armGroup.rotation.z = side * 0.25;
      return { group: armGroup, hand: handPivot };
    }
    const armL = makeArm(-1);
    const armR = makeArm(1);
    group.add(armL.group, armR.group);

    // ---------- legs (small, hidden mostly) ----------
    function makeLeg(side) {
      const leg = new THREE.Mesh(
        new THREE.CapsuleGeometry(0.22, 0.25, 8, 16),
        softMat(palette.bodyDark, { roughness: 0.7 })
      );
      leg.position.set(side * 0.3, 0.18, 0);
      leg.castShadow = true;
      return leg;
    }
    group.add(makeLeg(-1));
    group.add(makeLeg(1));

    // ---------- animation API ----------
    const state = {
      mouthOpen: 0,        // 0..1
      blink: 0,            // 0..1 (1 = closed)
      lookAt: new THREE.Vector3(0, 1.85, 5),
      headTilt: 0,
      bodyBob: 0,
      armSway: 0,
      gesture: null,       // null | 'point' | 'wave' | 'listen'
      gestureT: 0,
      _t: Math.random() * 100,
      _nextBlink: 2 + Math.random() * 3,
      _baseArmZL: armL.group.rotation.z,
      _baseArmZR: armR.group.rotation.z,
    };

    function setLookAt(target) {
      state.lookAt.copy(target);
    }

    function setMouthOpen(v) {
      state.mouthOpen = Math.max(0, Math.min(1, v));
    }

    function triggerGesture(name) {
      state.gesture = name;
      state.gestureT = 0;
    }

    function update(dt) {
      state._t += dt;

      // breathing bob
      state.bodyBob = Math.sin(state._t * 1.2) * 0.025;
      body.position.y = 0.85 + state.bodyBob;
      headPivot.position.y = 1.85 + state.bodyBob * 0.6;

      // arm idle sway
      const sway = Math.sin(state._t * 1.4) * 0.04;
      armL.group.rotation.z = state._baseArmZL + sway;
      armR.group.rotation.z = state._baseArmZR - sway;

      // blink loop
      state._nextBlink -= dt;
      if (state._nextBlink <= 0) {
        state.blink = Math.min(1, state.blink + dt * 12);
        if (state.blink >= 1) {
          state._nextBlink = 0;
          // schedule reopen
          setTimeout(() => { state._nextBlink = -0.1; state.blink = 0.999; }, 80);
        }
      }
      if (state._nextBlink < 0 && state.blink > 0) {
        state.blink = Math.max(0, state.blink - dt * 14);
        if (state.blink === 0) {
          state._nextBlink = 2.5 + Math.random() * 4;
        }
      }
      lidL.scale.y = Math.max(0.001, state.blink);
      lidR.scale.y = Math.max(0.001, state.blink);

      // head look-at (clamped)
      const targetLocal = headPivot.worldToLocal(state.lookAt.clone());
      const yaw = Math.atan2(targetLocal.x, targetLocal.z);
      const pitch = Math.atan2(-targetLocal.y, Math.sqrt(targetLocal.x ** 2 + targetLocal.z ** 2));
      const clampedYaw = Math.max(-0.6, Math.min(0.6, yaw));
      const clampedPitch = Math.max(-0.3, Math.min(0.3, pitch));
      // smooth
      headPivot.rotation.y += (clampedYaw - headPivot.rotation.y) * Math.min(1, dt * 4);
      headPivot.rotation.x += (clampedPitch - headPivot.rotation.x) * Math.min(1, dt * 4);
      headPivot.rotation.z += (state.headTilt - headPivot.rotation.z) * Math.min(1, dt * 3);

      // mouth open (jaw)
      mouthPivot.scale.y = 0.4 + state.mouthOpen * 1.6;
      mouthPivot.scale.x = 1 - state.mouthOpen * 0.15;
      tongue.position.y = -0.04 + state.mouthOpen * 0.04;

      // gestures
      if (state.gesture) {
        state.gestureT += dt;
        const g = state.gestureT;
        if (state.gesture === 'point') {
          // raise opposite arm forward
          const armSide = variant === 'cobalt' ? armR : armL;
          const baseZ = variant === 'cobalt' ? state._baseArmZR : state._baseArmZL;
          const k = Math.min(1, g / 0.4) * (1 - Math.max(0, (g - 1.2) / 0.4));
          armSide.group.rotation.z = baseZ + (variant === 'cobalt' ? -1 : 1) * k * 0.6;
          armSide.group.rotation.x = -k * 1.0;
          if (g > 1.6) state.gesture = null;
        } else if (state.gesture === 'wave') {
          const armSide = variant === 'cobalt' ? armL : armR;
          const baseZ = variant === 'cobalt' ? state._baseArmZL : state._baseArmZR;
          const k = Math.min(1, g / 0.3);
          armSide.group.rotation.z = baseZ + (variant === 'cobalt' ? 1 : -1) * k * 1.5;
          armSide.group.rotation.x = -k * 0.6;
          armSide.hand.rotation.z = Math.sin(g * 12) * 0.4 * k;
          if (g > 1.4) { state.gesture = null; armSide.hand.rotation.z = 0; }
        } else if (state.gesture === 'listen') {
          // head tilt
          state.headTilt = Math.sin(g * 2) * 0.15;
          if (g > 1.5) { state.gesture = null; state.headTilt = 0; }
        }
      }
    }

    return {
      group,
      head: headPivot,
      setLookAt,
      setMouthOpen,
      triggerGesture,
      update,
      _state: state,
    };
  }

  return { buildAvatar };
})();

window.AvatarFactory = AvatarFactory;
