import { useEffect, useRef } from 'react';
import { useFrame, useLoader, useThree } from '@react-three/fiber';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { VRMLoaderPlugin, VRMUtils } from '@pixiv/three-vrm';
import { createVRMAnimationClip, VRMAnimationLoaderPlugin } from '@pixiv/three-vrm-animation';
import { AnimationMixer, Vector3, Quaternion } from 'three';
import * as THREE from 'three';

import { keys, adminMode } from './controls/KeyInput';

const BASE_URL = import.meta.env.BASE_URL;

export const VRMA_FILES = [
  { name: 'idle',       label: '待機',     file: 'idle.vrma',       loop: true },
  { name: 'fly_idle',   label: '飛行待機', file: 'fly_idle.vrma',   loop: true },
  { name: 'fly',        label: '飛行',     file: 'fly.vrma',        loop: true },
  { name: 'fly_stop',   label: '飛行停止', file: 'fly_stop.vrma',   loop: false },
  { name: 'fly_dodge',  label: '回避',     file: 'fly_dodge.vrma',  loop: false },
  { name: 'jump',       label: 'ジャンプ', file: 'jump.vrma',       loop: false },
  { name: 'attack',     label: '攻撃',     file: 'attack.vrma',     loop: false },
  { name: 'damage',     label: 'ダメージ', file: 'damage.vrma',     loop: false },
  { name: 'skill',      label: 'スキル',   file: 'skill_end.vrma',  loop: false },
  { name: 'skill_loop', label: 'スキル継続', file: 'skill_loop.vrma', loop: true },
  { name: 'skill_end',  label: 'スキル終了', file: 'skill_end.vrma',  loop: false },
];
const VRMA_URLS = VRMA_FILES.map(v => `${BASE_URL}animation/${v.file}`);

const TRIM_DURATION_LOOP = 3.4;      // ループアニメーションの長さ
const TRIM_DURATION_ONCE = 2.0;      // 技(oneshot)アニメーションの長さ

function trimClip(clip, duration, addLoopPose) {
  const fade = 0.3;
  const trimEnd = duration + (addLoopPose ? fade : 0);
  clip.tracks.forEach(track => {
    const times = track.times;
    const valStride = track.values.length / times.length;
    let cutIndex = times.length;
    for (let j = 0; j < times.length; j++) {
      if (times[j] > trimEnd) { cutIndex = j; break; }
    }
    const newTimes = Array.from(times.slice(0, cutIndex));
    const newValues = Array.from(track.values.slice(0, cutIndex * valStride));
    if (addLoopPose) {
      newTimes.push(duration);
      for (let v = 0; v < valStride; v++) {
        newValues.push(track.values[v]);
      }
    }
    track.times = new Float32Array(newTimes);
    track.values = new Float32Array(newValues);
  });
  clip.duration = duration;
  return clip;
}

export default function VrmCharacter({ selectedAnimation: animState, vrmModel = 'ai.vrm' }) {
  const selectedAnimation = animState?.name || animState;
  const mixerRef = useRef(null);
  const vrmRef = useRef(null);
  const actionsRef = useRef({});
  const currentActionRef = useRef(null);

  const vrmUrl = `${BASE_URL}model/${vrmModel}`;
  const gltf = useLoader(GLTFLoader, vrmUrl, (loader) => {
    loader.register((parser) => new VRMLoaderPlugin(parser));
  });

  const vrmaGltfs = useLoader(GLTFLoader, VRMA_URLS, (loader) => {
    loader.register((parser) => new VRMAnimationLoaderPlugin(parser));
  });

  useEffect(() => {
    const vrm = gltf.userData.vrm;
    vrmRef.current = vrm;
    VRMUtils.removeUnnecessaryJoints(vrm.scene);
    // SpringBone: centerをRotatingStage(親group)に設定し、回転に追従させる
    const sbm = vrm.springBoneManager;
    if (sbm) {
      const stageGroup = vrm.scene.parent;
      sbm.joints?.forEach(joint => {
        if (stageGroup) joint.center = stageGroup;
      });
    }
    vrm.humanoid.resetPose();
    vrm.scene.rotation.y = Math.PI;

    const mixer = new AnimationMixer(vrm.scene);
    mixerRef.current = mixer;

    const actions = {};
    vrmaGltfs.forEach((vrmaGltf, i) => {
      const anim = vrmaGltf.userData.vrmAnimations?.[0];
      if (anim) {
        const clip = createVRMAnimationClip(anim, vrm);
        const meta = VRMA_FILES[i];
        if (meta.loop) {
          actions[meta.name] = mixer.clipAction(trimClip(clip, TRIM_DURATION_LOOP, true));
        } else {
          const action = mixer.clipAction(trimClip(clip, TRIM_DURATION_ONCE, false));
          action.setLoop(THREE.LoopOnce);
          action.clampWhenFinished = true;
          actions[meta.name] = action;
        }
      }
    });
    actionsRef.current = actions;

    const defaultAnim = 'fly_idle';
    if (actions[defaultAnim]) {
      actions[defaultAnim].setLoop(THREE.LoopRepeat).play();
      currentActionRef.current = defaultAnim;
    }

    return () => {
      mixer.stopAllAction();
    };
  }, [gltf, vrmaGltfs]);

  useEffect(() => {
    if (!selectedAnimation || !actionsRef.current[selectedAnimation]) return;
    const actions = actionsRef.current;
    const meta = VRMA_FILES.find(v => v.name === selectedAnimation);

    const current = currentActionRef.current;
    if (current && actions[current]) {
      actions[current].fadeOut(0.3);
    }
    const action = actions[selectedAnimation].reset().fadeIn(0.3).play();
    if (meta?.loop) {
      action.setLoop(THREE.LoopRepeat);
    }
    currentActionRef.current = selectedAnimation;
  }, [animState]);

  useFrame((_, delta) => {
    mixerRef.current?.update(delta);
    vrmRef.current?.update(delta);

    // oneshot animation finished -> return to fly_idle
    const current = currentActionRef.current;
    const actions = actionsRef.current;
    const meta = VRMA_FILES.find(v => v.name === current);
    if (meta && !meta.loop && actions[current]) {
      const action = actions[current];
      if (!action.isRunning() || action.time >= action.getClip().duration - 0.1) {
        action.fadeOut(0.3);
        actions.fly_idle?.reset().setLoop(THREE.LoopRepeat).fadeIn(0.3).play();
        currentActionRef.current = 'fly_idle';
      }
    }

    // admin WASD movement -> fly / fly_idle switching
    if (adminMode && actions.fly && actions.fly_idle) {
      const isMoving = keys.w;
      if (isMoving && current !== 'fly') {
        if (actions[current]) actions[current].stop();
        actions.fly.reset().setLoop(THREE.LoopRepeat).fadeIn(0.3).play();
        currentActionRef.current = 'fly';
      } else if (!isMoving && current === 'fly') {
        actions.fly.stop();
        actions.fly_idle.reset().setLoop(THREE.LoopRepeat).fadeIn(0.3).play();
        currentActionRef.current = 'fly_idle';
      }
    }
  });

  return <primitive object={gltf.scene} />;
}
