import { useEffect, useRef, useState } from 'react';
import AnimatedAvatar from './AnimatedAvatar';

interface Props {
  isSpeaking: boolean;
  isListening: boolean;
  outfit: string;
  accessory: string | null;
  onTalkToggle: () => void;
}

type ExpressionName = 'Normal' | 'Smile' | 'Sad' | 'Angry' | 'Surprised' | 'Blushing';

const EXPRESSIONS: ExpressionName[] = ['Normal', 'Smile', 'Sad', 'Angry', 'Surprised', 'Blushing'];
const IDLE_EXPRESSIONS: ExpressionName[] = ['Normal', 'Smile', 'Blushing'];

const OUTFIT_TEXTURES: Record<string, string> = {
  'cozy-hoodie': '/live2d/models/kira/outfits/cozy-hoodie.png',
  'girly-dress': '/live2d/models/kira/outfits/girly-dress.png',
  'pajama-set': '/live2d/models/kira/outfits/pajama-set.png',
  'study-sweater': '/live2d/models/kira/outfits/study-sweater.png',
  'going-out': '/live2d/models/kira/outfits/going-out.png',
};

export default function KiraAvatar(props: Props) {
  const canvasRef = useRef<HTMLDivElement>(null);
  const appRef = useRef<any>(null);
  const modelRef = useRef<any>(null);
  const textureRef = useRef<any>(null);
  const lipSyncRef = useRef<number>(0);
  const idleExprRef = useRef<ReturnType<typeof setInterval> | null>(null);
  const [live2dReady, setLive2dReady] = useState(false);
  const [loadError, setLoadError] = useState(false);
  const [currentExpression, setCurrentExpression] = useState<ExpressionName>('Normal');

  // ── Initialize Live2D ──
  useEffect(() => {
    let mounted = true;
    const container = canvasRef.current;
    if (!container) return;

    (async () => {
      try {
        const resp = await fetch('/live2d/models/kira/kira.model3.json', { method: 'HEAD' });
        if (!resp.ok) {
          if (mounted) setLoadError(true);
          return;
        }

        await loadScript('/live2d/cubism/live2dcubismcore.min.js');
        const { Application, Ticker } = await import('pixi.js');
        const { Live2DModel } = await import('pixi-live2d-display/cubism4');

        // Register pixi Ticker so Live2DModel can drive animations
        // Cast needed due to pixi-live2d-display expecting older Ticker type
        (Live2DModel as any).registerTicker(Ticker as any);

        // Responsive sizing
        const size = Math.min(container.clientWidth || 260, 260);
        const app = new Application({
          width: size,
          height: size * 1.25,
          antialias: true,
          resolution: Math.min(window.devicePixelRatio || 1, 2),
          backgroundAlpha: 0,
        });
        appRef.current = app;
        if (!mounted) { app.destroy(true); return; }

        container.appendChild(app.view as HTMLCanvasElement);

        // Load model with default textures
        const model = await Live2DModel.from('/live2d/models/kira/kira.model3.json', {
          autoInteract: false,
        });
        modelRef.current = model;

        // Fit model in canvas
        const maxW = size * 0.78;
        const maxH = size * 1.1;
        const scale = Math.min(maxW / model.width, maxH / model.height);
        model.scale.set(scale);
        model.anchor.set(0.5, 0.5);
        model.position.set(app.screen.width / 2, app.screen.height / 2 + 6);

        app.stage.addChild(model as any);

        // Fix: prevent pixi v7 isInteractive TypeError with Live2D model
        (model as any).isInteractive = () => false;

        // Store reference to texture_02 for outfit swapping
        try {
          textureRef.current = {
            index: 2,
            original: (model.internalModel.coreModel as any).getTexture(2),
          };
        } catch { /* ignore */ }

        if (mounted) setLive2dReady(true);

        // Start idle animation
        try { model.motion('Idle'); } catch { /* no idle */ }
        try { model.expression('Normal'); } catch { /* no expressions */ }

        // Random idle expression changes
        idleExprRef.current = setInterval(() => {
          if (props.isSpeaking) return;
          const expr = IDLE_EXPRESSIONS[Math.floor(Math.random() * IDLE_EXPRESSIONS.length)];
          try { model.expression(expr); } catch { /* */ }
          setCurrentExpression(expr);
        }, 8000 + Math.random() * 7000);

      } catch (e) {
        console.warn('[Live2D]', e);
        if (mounted) setLoadError(true);
      }
    })();

    return () => {
      mounted = false;
      cancelAnimationFrame(lipSyncRef.current);
      clearInterval(idleExprRef.current ?? undefined);
      if (appRef.current) {
        appRef.current.destroy(true, { children: true });
        appRef.current = null;
      }
      modelRef.current = null;
      textureRef.current = null;
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // ── Lip sync from TTS ──
  useEffect(() => {
    const model = modelRef.current;
    if (!model || !live2dReady) return;

    cancelAnimationFrame(lipSyncRef.current);

    if (props.isSpeaking) {
      let phase = 0;
      const animate = () => {
        phase += 0.12;
        const openness = 0.25 + Math.sin(phase) * 0.35;
        try {
          model.internalModel.coreModel.setParameterValueByIndex(
            findParam(model, 'PARAM_MOUTH_OPEN_Y'),
            openness,
          );
        } catch { /* */ }
        lipSyncRef.current = requestAnimationFrame(animate);
      };
      animate();
    } else {
      try {
        model.internalModel.coreModel.setParameterValueByIndex(
          findParam(model, 'PARAM_MOUTH_OPEN_Y'),
          0,
        );
      } catch { /* */ }
    }

    return () => cancelAnimationFrame(lipSyncRef.current);
  }, [props.isSpeaking, live2dReady]);

  // ── Outfit texture swapping ──
  useEffect(() => {
    const model = modelRef.current;
    if (!model || !live2dReady) return;

    const outfitUrl = OUTFIT_TEXTURES[props.outfit];
    if (!outfitUrl) return;

    (async () => {
      try {
        const { Assets } = await import('pixi.js');
        const tex = await Assets.load(outfitUrl);
        // Swap texture slot 2 (clothing layer) in the model's texture array.
        // The render loop automatically binds WebGL textures from PixiJS textures.
        if ((model as any).textures) {
          (model as any).textures[2] = tex;
        } else {
          console.warn('[Outfit] cannot swap — no textures array');
        }
      } catch (e) {
        console.warn('[Outfit] texture swap failed:', e);
      }
    })();
  }, [props.outfit, live2dReady]);

  // ── Accessory toggle ──
  useEffect(() => {
    const model = modelRef.current;
    if (!model || !live2dReady) return;

    // The model has hair clips on texture_01 that could be toggled
    // For now, the accessory system works on the fallback avatar
  }, [props.accessory, live2dReady]);

  return (
    <div className="glass-card p-4 flex flex-col items-center" style={{ minHeight: 360 }}>
      {/* Live2D canvas */}
      <div
        ref={canvasRef}
        className={`relative w-full ${live2dReady ? 'block' : 'hidden'}`}
        style={{ maxWidth: 260, height: 325 }}
      />

      {/* SVG fallback */}
      {(!live2dReady || loadError) && (
        <div className={live2dReady ? 'hidden' : 'block'}>
          <AnimatedAvatar
            isSpeaking={props.isSpeaking}
            isListening={props.isListening}
            outfit={props.outfit}
            accessory={props.accessory}
            onTalkToggle={props.onTalkToggle}
          />
        </div>
      )}

      {/* Loading spinner */}
      {!live2dReady && !loadError && (
        <div className="flex flex-col items-center gap-2 py-10">
          <div className="w-8 h-8 border-3 border-kira-pink border-t-transparent rounded-full animate-spin" />
          <p className="text-[11px] text-kira-plum/40">loading Kira...</p>
        </div>
      )}

      {/* Expression indicator + Talk button */}
      {live2dReady && (
        <>
          <div className="mt-1 flex gap-1.5 flex-wrap justify-center">
            {EXPRESSIONS.map((expr) => (
              <button
                key={expr}
                onClick={() => {
                  try { modelRef.current?.expression(expr); setCurrentExpression(expr); } catch {}
                }}
                className={`text-[9px] px-2 py-0.5 rounded-full font-medium transition-all ${
                  currentExpression === expr
                    ? 'bg-kira-pink text-white'
                    : 'text-kira-plum/30 hover:text-kira-plum/60'
                }`}
              >
                {expr}
              </button>
            ))}
          </div>

          {/* Talk button — mic toggle */}
          <button
            onClick={props.onTalkToggle}
            className={`mt-2 flex items-center gap-2 px-5 py-2 rounded-full text-sm font-bold transition-all ${
              props.isListening
                ? 'bg-red-400 text-white shadow-lg scale-105 animate-listening-pulse'
                : 'bg-gradient-to-r from-kira-pink to-kira-lav text-white hover:shadow-lg hover:scale-105'
            }`}
          >
            <span className="text-base">{props.isListening ? '⏹️' : '🎤'}</span>
            {props.isListening ? 'Listening...' : 'Talk to Kira'}
          </button>
        </>
      )}

      {/* Status bar */}
      <div className="mt-2 flex items-center gap-3 text-xs text-kira-plum/40">
        <span className={`w-2 h-2 rounded-full ${props.isSpeaking ? 'bg-kira-pink animate-pulse' : props.isListening ? 'bg-red-400 animate-pulse' : 'bg-kira-mint'}`} />
        <span>
          {props.isSpeaking ? 'speaking...' : props.isListening ? 'listening...' : live2dReady ? 'here with you' : 'loading...'}
        </span>
      </div>

      <style>{`
        @keyframes listening-pulse {
          0%, 100% { box-shadow: 0 0 0 0 rgba(248, 113, 113, 0.4); }
          50% { box-shadow: 0 0 0 12px rgba(248, 113, 113, 0); }
        }
        .animate-listening-pulse {
          animation: listening-pulse 1.5s infinite;
        }
      `}</style>
    </div>
  );
}

// ── Helpers ──

function loadScript(src: string): Promise<void> {
  return new Promise((resolve, reject) => {
    if (document.querySelector(`script[src="${src}"]`)) { resolve(); return; }
    const s = document.createElement('script');
    s.src = src;
    s.onload = () => resolve();
    s.onerror = () => reject(new Error(`Failed ${src}`));
    document.head.appendChild(s);
  });
}

function findParam(model: any, name: string): number {
  try {
    return model.internalModel.coreModel.getParameterIds().indexOf(name);
  } catch { return 0; }
}
