/**
 * avatar.mjs — AI-Do v4.0 animated avatar component
 * Renders a complete SVG face with eyes, eyebrows, nose, mouth, hair, ears,
 * cheek blush, skin gradients. Includes blinking, gaze tracking, speaking
 * animation, idle breathing, and emotion expressions.
 */

import store from '../store.mjs';
import eventBus, { EVENTS } from '../event-bus.mjs';

// ── Config (Nova personality) ──
const SKIN_COLOR = '#f5d0c5';
const SKIN_HIGHLIGHT = '#fce4dc';
const SKIN_DARK = '#e0b8aa';
const EYE_COLOR = '#00d4aa';
const IRIS_COLOR = '#008866';
const IRIS_DARK = '#005540';
const LIP_COLOR = '#c9524a';
const LIP_DARK = '#a03830';
const LIP_HIGHLIGHT = '#e07068';
const EYEBROW_COLOR = '#2d1f1a';
const HAIR_COLOR = '#1a0f0a';
const HAIR_HIGHLIGHT = '#3d2820';
const SPEAK_SPEED = 0.018;

// ── Size presets ──
const SIZE_MAP = {
  sidebar: 64,
  welcome: 120,
  header: 40
};

// ── Module state ──
let instances = [];
let animFrameId = null;
let lastTime = 0;

// Shared blink state
let blinkState = 0; // 0=open, 1=closing/opening
let nextBlinkTime = Date.now() + 2000 + Math.random() * 3000;
let blinkStartTime = 0;
const BLINK_DURATION = 150;

// Shared gaze state
let gazeX = 0;
let gazeY = 0;
let targetGazeX = 0;
let targetGazeY = 0;
let lastMouseMove = Date.now();
let idleGazeTimer = 0;

// Speaking state
let isSpeaking = false;
let speakStartTime = 0;
let mouthOpenness = 0;
let targetMouthOpenness = 0;

// Breathing
let breathPhase = 0;

// Emotion
let currentEmotion = 'neutral';

// ── Helpers ──

function adjustColor(color, amount) {
  const num = parseInt(color.replace('#', ''), 16);
  const r = Math.min(255, Math.max(0, (num >> 16) + amount));
  const g = Math.min(255, Math.max(0, ((num >> 8) & 0x00ff) + amount));
  const b = Math.min(255, Math.max(0, (num & 0x0000ff) + amount));
  return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}

function uid() {
  return 'av-' + Math.random().toString(36).substr(2, 9);
}

// ── Mouse tracking (shared) ──

function onMouseMove(e) {
  lastMouseMove = Date.now();
  // Use first instance rect as reference; gaze is shared across all
  if (instances.length === 0) return;
  const firstRect = instances[0].container.getBoundingClientRect();
  const centerX = firstRect.left + firstRect.width / 2;
  const centerY = firstRect.top + firstRect.height / 2;
  const dx = e.clientX - centerX;
  const dy = e.clientY - centerY;
  const maxOffset = 6;
  const dist = Math.sqrt(dx * dx + dy * dy);
  const angle = Math.atan2(dy, dx);
  const clampedDist = Math.min(maxOffset, dist * 0.015);
  targetGazeX = Math.cos(angle) * clampedDist;
  targetGazeY = Math.sin(angle) * clampedDist;
}

// ── SVG builder ──

function buildSVG(id) {
  const noseBridge = adjustColor(SKIN_COLOR, -20);
  const nostrilColor = adjustColor(SKIN_DARK, -15);

  return `<svg class="avatar-svg" viewBox="0 0 200 240" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <radialGradient id="skinGrad-${id}" cx="0.35" cy="0.25" r="0.8">
      <stop offset="0%" stop-color="${SKIN_HIGHLIGHT}"/>
      <stop offset="50%" stop-color="${SKIN_COLOR}"/>
      <stop offset="100%" stop-color="${SKIN_DARK}"/>
    </radialGradient>
    <radialGradient id="eyeGrad-${id}" cx="0.35" cy="0.35" r="0.65">
      <stop offset="0%" stop-color="${EYE_COLOR}"/>
      <stop offset="70%" stop-color="${IRIS_COLOR}"/>
      <stop offset="100%" stop-color="${IRIS_DARK}"/>
    </radialGradient>
    <linearGradient id="upperLipGrad-${id}" x1="0" y1="0" x2="0" y2="1">
      <stop offset="0%" stop-color="${LIP_HIGHLIGHT}"/>
      <stop offset="100%" stop-color="${LIP_COLOR}"/>
    </linearGradient>
    <linearGradient id="lowerLipGrad-${id}" x1="0" y1="1" x2="0" y2="0">
      <stop offset="0%" stop-color="${LIP_HIGHLIGHT}"/>
      <stop offset="50%" stop-color="${LIP_COLOR}"/>
      <stop offset="100%" stop-color="${LIP_DARK}"/>
    </linearGradient>
  </defs>

  <g class="head-group">
    <!-- Ears (behind face) -->
    <ellipse cx="30" cy="125" rx="10" ry="16" fill="${SKIN_DARK}"/>
    <ellipse cx="170" cy="125" rx="10" ry="16" fill="${SKIN_DARK}"/>

    <!-- Face base -->
    <ellipse cx="100" cy="130" rx="70" ry="85" fill="url(#skinGrad-${id})"/>

    <!-- Face highlight -->
    <ellipse cx="75" cy="95" rx="18" ry="12" fill="rgba(255,255,255,0.12)"/>

    <!-- Eyes -->
    <g class="eyes">
      <!-- Left eye -->
      <g class="eye-left">
        <ellipse class="sclera" cx="70" cy="120" rx="16" ry="11" fill="#ffffff"/>
        <g class="iris-group-left">
          <circle class="iris" cx="70" cy="120" r="7" fill="url(#eyeGrad-${id})"/>
          <circle class="pupil" cx="70" cy="120" r="3" fill="#000"/>
          <circle class="eye-shine" cx="67" cy="117" r="2" fill="rgba(255,255,255,0.9)"/>
        </g>
        <path class="eyelid-left" d="M 54 109 Q 70 109 86 109 L 86 109 Q 70 109 54 109 Z" fill="${SKIN_COLOR}"/>
      </g>
      <!-- Right eye -->
      <g class="eye-right">
        <ellipse class="sclera" cx="130" cy="120" rx="16" ry="11" fill="#ffffff"/>
        <g class="iris-group-right">
          <circle class="iris" cx="130" cy="120" r="7" fill="url(#eyeGrad-${id})"/>
          <circle class="pupil" cx="130" cy="120" r="3" fill="#000"/>
          <circle class="eye-shine" cx="127" cy="117" r="2" fill="rgba(255,255,255,0.9)"/>
        </g>
        <path class="eyelid-right" d="M 114 109 Q 130 109 146 109 L 146 109 Q 130 109 114 109 Z" fill="${SKIN_COLOR}"/>
      </g>
    </g>

    <!-- Eyebrows -->
    <g class="eyebrows">
      <path class="eyebrow eyebrow-left"
            d="M 52 100 Q 62 94 78 98"
            stroke="${EYEBROW_COLOR}" stroke-width="4" stroke-linecap="round" fill="none"
            style="transform-origin: 78px 98px; transition: transform 0.3s ease;"/>
      <path class="eyebrow eyebrow-right"
            d="M 122 98 Q 138 94 148 100"
            stroke="${EYEBROW_COLOR}" stroke-width="4" stroke-linecap="round" fill="none"
            style="transform-origin: 122px 98px; transition: transform 0.3s ease;"/>
    </g>

    <!-- Nose -->
    <g class="nose">
      <path d="M 100 125 L 100 148" stroke="${noseBridge}" stroke-width="2" stroke-linecap="round" opacity="0.4"/>
      <ellipse cx="100" cy="152" rx="10" ry="6" fill="${SKIN_COLOR}"/>
      <ellipse cx="93" cy="154" rx="4" ry="2.5" fill="${nostrilColor}" opacity="0.35"/>
      <ellipse cx="107" cy="154" rx="4" ry="2.5" fill="${nostrilColor}" opacity="0.35"/>
    </g>

    <!-- Mouth group -->
    <g class="mouth-group">
      <path class="mouth-interior" d="M 80 175 Q 100 175 120 175 Q 100 175 80 175 Z" fill="#3a1a1a"/>
      <rect class="teeth-upper" x="85" y="175" width="30" height="0" rx="2" fill="#f5f5f0"/>
      <rect class="teeth-lower" x="87" y="175" width="26" height="0" rx="2" fill="#e8e8e3"/>
      <ellipse class="tongue" cx="100" cy="185" rx="12" ry="0" fill="#d47080"/>
      <path class="upper-lip" d="M 80 175 Q 90 169 100 172 Q 110 169 120 175 Q 100 178 80 175 Z"
            fill="url(#upperLipGrad-${id})" stroke="${LIP_DARK}" stroke-width="0.5"/>
      <path class="lower-lip" d="M 80 175 Q 100 184 120 175 Q 100 180 80 175 Z"
            fill="url(#lowerLipGrad-${id})"/>
      <ellipse class="lip-shine" cx="95" cy="172" rx="6" ry="2" fill="rgba(255,255,255,0.25)"/>
    </g>

    <!-- Cheek blush -->
    <ellipse cx="50" cy="145" rx="16" ry="10" fill="rgba(255,150,150,0.12)"/>
    <ellipse cx="150" cy="145" rx="16" ry="10" fill="rgba(255,150,150,0.12)"/>

    <!-- Hair (rendered LAST, on top) -->
    <g class="hair">
      <ellipse cx="100" cy="60" rx="75" ry="50" fill="${HAIR_COLOR}"/>
      <path d="M 30 70 Q 22 130 35 180" stroke="${HAIR_COLOR}" stroke-width="30" fill="none" stroke-linecap="round"/>
      <path d="M 170 70 Q 178 130 165 180" stroke="${HAIR_COLOR}" stroke-width="30" fill="none" stroke-linecap="round"/>
      <path d="M 45 75 Q 70 95 70 85 Q 85 100 95 80 Q 110 95 120 78 Q 140 90 155 75"
            stroke="${HAIR_COLOR}" stroke-width="18" fill="none" stroke-linecap="round"/>
      <path d="M 55 50 Q 75 65 70 80" stroke="${HAIR_HIGHLIGHT}" stroke-width="6" fill="none" opacity="0.4"/>
    </g>
  </g>
</svg>`;
}

// ── Element caching ──

function cacheEls(container) {
  const svg = container.querySelector('.avatar-svg');
  if (!svg) return null;
  return {
    headGroup: svg.querySelector('.head-group'),
    eyebrowLeft: svg.querySelector('.eyebrow-left'),
    eyebrowRight: svg.querySelector('.eyebrow-right'),
    eyelidLeft: svg.querySelector('.eyelid-left'),
    eyelidRight: svg.querySelector('.eyelid-right'),
    irisGroupLeft: svg.querySelector('.iris-group-left'),
    irisGroupRight: svg.querySelector('.iris-group-right'),
    mouthInterior: svg.querySelector('.mouth-interior'),
    upperLip: svg.querySelector('.upper-lip'),
    lowerLip: svg.querySelector('.lower-lip'),
    teethUpper: svg.querySelector('.teeth-upper'),
    teethLower: svg.querySelector('.teeth-lower'),
    tongue: svg.querySelector('.tongue'),
    lipShine: svg.querySelector('.lip-shine')
  };
}

// ── Animation update (shared, runs once) ──

function updateAll(dt) {
  const now = Date.now();

  // ----- Blink -----
  if (blinkState === 0 && now >= nextBlinkTime) {
    blinkState = 1;
    blinkStartTime = now;
  }

  let blinkAmount = 0;
  if (blinkState === 1) {
    const progress = (now - blinkStartTime) / BLINK_DURATION;
    if (progress >= 1) {
      blinkState = 0;
      nextBlinkTime = now + 2000 + Math.random() * 3000;
      blinkAmount = 0;
    } else {
      blinkAmount = progress < 0.5 ? progress * 2 : (1 - progress) * 2;
    }
  }

  // ----- Gaze -----
  const timeSinceMouseMove = now - lastMouseMove;
  if (timeSinceMouseMove > 2000) {
    idleGazeTimer += dt;
    if (idleGazeTimer > 1500 + Math.random() * 1500) {
      idleGazeTimer = 0;
      targetGazeX = (Math.random() - 0.5) * 8;
      targetGazeY = (Math.random() - 0.5) * 5;
    }
  }
  gazeX += (targetGazeX - gazeX) * 0.05;
  gazeY += (targetGazeY - gazeY) * 0.05;

  // ----- Speaking mouth -----
  if (isSpeaking) {
    const elapsed = performance.now() - speakStartTime;
    const wave = Math.abs(Math.sin(elapsed * SPEAK_SPEED));
    targetMouthOpenness = 0.15 + wave * 0.65;
  }
  mouthOpenness += (targetMouthOpenness - mouthOpenness) * 0.25;

  // ----- Breathing -----
  breathPhase += dt * 0.001;
  const breathOffset = Math.sin(breathPhase * (2 * Math.PI / 3)) * 1.5;

  // ----- Apply to all instances -----
  for (const inst of instances) {
    const els = inst.els;
    if (!els) continue;

    // Eyelids
    applyBlink(els, blinkAmount);

    // Gaze
    if (els.irisGroupLeft) {
      els.irisGroupLeft.style.transform = `translate(${gazeX}px, ${gazeY}px)`;
    }
    if (els.irisGroupRight) {
      els.irisGroupRight.style.transform = `translate(${gazeX}px, ${gazeY}px)`;
    }

    // Mouth
    applyMouth(els, mouthOpenness);

    // Breathing
    if (els.headGroup) {
      els.headGroup.style.transform = `translateY(${breathOffset}px)`;
    }
  }
}

function applyBlink(els, amount) {
  const closeY = 11 * amount;

  if (els.eyelidLeft) {
    const leftPath = `M 54 ${109 + closeY} Q 70 ${109 - 7 + closeY * 1.5} 86 ${109 + closeY} L 86 97 Q 70 97 54 97 Z`;
    els.eyelidLeft.setAttribute('d', leftPath);
  }
  if (els.eyelidRight) {
    const rightPath = `M 114 ${109 + closeY} Q 130 ${109 - 7 + closeY * 1.5} 146 ${109 + closeY} L 146 97 Q 130 97 114 97 Z`;
    els.eyelidRight.setAttribute('d', rightPath);
  }
}

function applyMouth(els, openness) {
  const cy = 175;
  const width = 20;
  const openY = openness * 18;

  if (els.mouthInterior) {
    const p = `M ${100 - width} ${cy} Q 100 ${cy + openness * 3} ${100 + width} ${cy} Q 100 ${cy + openY} ${100 - width} ${cy} Z`;
    els.mouthInterior.setAttribute('d', p);
  }

  if (els.upperLip) {
    const lipShift = openness * 2;
    const p = `M ${100 - width} ${cy} Q ${100 - width / 2} ${cy - 6 - lipShift} 100 ${cy - 3 - lipShift} Q ${100 + width / 2} ${cy - 6 - lipShift} ${100 + width} ${cy} Q 100 ${cy + openness * 2} ${100 - width} ${cy} Z`;
    els.upperLip.setAttribute('d', p);
  }

  if (els.lowerLip) {
    const lowerDrop = openY;
    const p = `M ${100 - width} ${cy} Q 100 ${cy + lowerDrop + 9} ${100 + width} ${cy} Q 100 ${cy + lowerDrop + 3} ${100 - width} ${cy} Z`;
    els.lowerLip.setAttribute('d', p);
  }

  if (els.teethUpper) {
    const h = Math.max(0, openness * 8);
    els.teethUpper.setAttribute('height', h);
    els.teethUpper.setAttribute('y', cy + 1);
  }
  if (els.teethLower) {
    const h = Math.max(0, openness * 6);
    els.teethLower.setAttribute('height', h);
    els.teethLower.setAttribute('y', cy + openY - h + 3);
  }

  if (els.tongue) {
    const tongueRy = Math.max(0, openness * 7);
    els.tongue.setAttribute('ry', tongueRy);
    els.tongue.setAttribute('cy', cy + openY - 5);
  }

  if (els.lipShine) {
    const lipShift = openness * 2;
    els.lipShine.setAttribute('cy', cy - 3 - lipShift);
  }
}

function applyEmotion(emotion) {
  const expressions = {
    neutral: { leftY: 0, rightY: 0, leftRot: 0, rightRot: 0 },
    happy: { leftY: -4, rightY: -4, leftRot: -5, rightRot: 5 },
    thinking: { leftY: -6, rightY: 2, leftRot: 8, rightRot: -3 }
  };

  const expr = expressions[emotion] || expressions.neutral;

  for (const inst of instances) {
    const els = inst.els;
    if (!els) continue;
    if (els.eyebrowLeft) {
      els.eyebrowLeft.style.transform = `translateY(${expr.leftY}px) rotate(${expr.leftRot}deg)`;
    }
    if (els.eyebrowRight) {
      els.eyebrowRight.style.transform = `translateY(${expr.rightY}px) rotate(${expr.rightRot}deg)`;
    }
  }
}

// ── Animation loop ──

function animationLoop(time) {
  const dt = time - (lastTime || time);
  lastTime = time;

  updateAll(dt);

  animFrameId = requestAnimationFrame(animationLoop);
}

function ensureAnimLoop() {
  if (animFrameId === null) {
    document.addEventListener('mousemove', onMouseMove);
    animFrameId = requestAnimationFrame(animationLoop);
  }
}

function maybeStopAnimLoop() {
  if (instances.length === 0 && animFrameId !== null) {
    cancelAnimationFrame(animFrameId);
    animFrameId = null;
    lastTime = 0;
    document.removeEventListener('mousemove', onMouseMove);
  }
}

// ── Public API ──

/**
 * Create an avatar DOM element.
 * @param {'sidebar'|'welcome'|'header'} size
 * @returns {HTMLElement}
 */
export function createAvatar(size = 'sidebar') {
  const id = uid();
  const px = SIZE_MAP[size] || SIZE_MAP.sidebar;

  const wrapper = document.createElement('div');
  wrapper.className = `avatar-component avatar-component--${size}`;
  wrapper.innerHTML = buildSVG(id);

  const els = cacheEls(wrapper);

  const inst = { container: wrapper, els, size };
  instances.push(inst);

  ensureAnimLoop();

  // Apply current emotion
  if (currentEmotion !== 'neutral') {
    applyEmotion(currentEmotion);
  }

  // Cleanup when removed from DOM
  const observer = new MutationObserver(() => {
    if (!document.body.contains(wrapper)) {
      observer.disconnect();
      const idx = instances.indexOf(inst);
      if (idx !== -1) instances.splice(idx, 1);
      maybeStopAnimLoop();
    }
  });
  // Observe parent (once attached)
  requestAnimationFrame(() => {
    if (wrapper.parentNode) {
      observer.observe(wrapper.parentNode, { childList: true });
    }
  });

  return wrapper;
}

/**
 * Start mouth speaking animation.
 */
export function startSpeaking() {
  if (isSpeaking) return;
  isSpeaking = true;
  speakStartTime = performance.now();
}

/**
 * Stop mouth speaking animation.
 */
export function stopSpeaking() {
  isSpeaking = false;
  targetMouthOpenness = 0;
}

/**
 * Set eyebrow emotion.
 * @param {'neutral'|'happy'|'thinking'} emotion
 */
export function setEmotion(emotion) {
  currentEmotion = emotion;
  applyEmotion(emotion);
}

/**
 * Initialize — subscribe to store for voice status and chat events.
 * Call once during app bootstrap.
 */
export function init() {
  // Voice status: auto start/stop speaking
  store.subscribe(
    (s) => s.voice.status,
    (status) => {
      if (status === 'speaking') {
        startSpeaking();
      } else if (isSpeaking) {
        stopSpeaking();
      }
    }
  );

  // Agent reply events — trigger brief speaking animation
  eventBus.on(EVENTS.CHAT_MESSAGE, (data) => {
    if (data && data.role !== 'user') {
      startSpeaking();
      // Stop after a duration proportional to content length
      const text = data.content || '';
      const duration = Math.max(1000, Math.min(text.length * 40, 8000));
      setTimeout(() => {
        // Only stop if still speaking from this trigger
        if (isSpeaking) stopSpeaking();
      }, duration);
    }
  });

  // Streaming: speak while streaming
  store.subscribe(
    (s) => s.chat.streaming,
    (streaming) => {
      if (streaming) {
        startSpeaking();
      } else {
        stopSpeaking();
      }
    }
  );
}
