import { useEffect, useState } from 'react';
import { View, Text, Platform } from 'react-native';
import { Asset } from 'expo-asset';
import Rive, { Fit, Alignment, RNRiveErrorType, type RNRiveError } from 'rive-react-native';

// Android: Rive akzeptiert NUR raw-resource oder url, kein file:// uri.
// Asset liegt als android/app/src/main/res/raw/lyra_avatar.riv (gebundelt
// via plugins/with-rive-asset-android.js). resourceName = filename ohne
// extension, lowercase + underscores (Android raw-resource convention).
const ANDROID_RIVE_RESOURCE = 'lyra_avatar';

// Modul-Level: nur EINMAL die Asset registrieren + URI cachen.
// In Production-Builds wird die .riv ins App-Bundle gebakt (Asset.localUri
// zeigt sofort auf das Bundle-File). In Dev-Builds wird sie beim ersten Mal
// von Metro gezogen + ins App-Sandbox-Cache geschrieben — danach offline.
const RIVE_MODULE = require('../assets/lyra-avatar.riv');
let cachedRiveUri: string | null = null;
let preloadPromise: Promise<string | null> | null = null;

function preloadRiveAsset(): Promise<string | null> {
  if (cachedRiveUri) return Promise.resolve(cachedRiveUri);
  if (preloadPromise) return preloadPromise;
  preloadPromise = Asset.fromModule(RIVE_MODULE)
    .downloadAsync()
    .then((asset) => {
      const uri = asset.localUri ?? asset.uri;
      cachedRiveUri = uri;
      return uri;
    })
    .catch((err) => {
      console.warn('[RiveAvatar] preload failed:', err?.message ?? err);
      preloadPromise = null;
      return null;
    });
  return preloadPromise;
}

// Kicke den Preload sofort beim Modul-Import an — damit der erste
// Render bereits die cached URI nutzt (außer im allerersten App-Start).
preloadRiveAsset();

// Supported emotions sind durch state-machine im .riv-file definiert.
// Neue states: nur EMOTION_ANIMATIONS + EMOTION_LABELS (+ ggf. TWO_PHASE/SUSTAINED)
// erweitern, kein weiterer Code-Change nötig.
export type SupportedEmotion =
  | 'idle'
  | 'happy'
  | 'empathy'
  | 'thinking'
  | 'listening'
  | 'calm'
  | 'sad'
  | 'joy'
  | 'confusion'
  | 'surprise';
export type Emotion = SupportedEmotion | (string & {});

// Direkte Timeline-Namen aus der .riv-Datei (Code-Contract — siehe
// docs/RIVE_ANIMATOR_BRIEF.md). Der Animator MUSS die States exakt so benennen,
// sonst spielt nichts (silent, kein Error). Bis die erweiterte .riv geliefert
// wird, existieren die neuen Timelines noch nicht → der Avatar zeigt für diese
// States den statischen Idle-Frame (kein Crash). "thinking" ersetzt den alten
// "WALK"-Platzhalter und animiert daher erst mit der neuen .riv wieder.
export const EMOTION_ANIMATIONS: Record<string, string> = {
  idle: 'Idle Loop',
  // Reuse vorhandener Hasen-Posen (0 Rive-Arbeit). Namen, die es noch NICHT in
  // der .riv gibt (calm/sad/joy/confusion/surprise), fallen via safeAnim auf Idle.
  happy: '01 Wave 2', // fröhliches Winken (war ungenutzt)
  empathy: '01 Wave 1', // sanftes Winken
  thinking: 'Pose 1 loop', // Klemmbrett: arbeitet an deiner Eingabe (direkt Loop, kein Zucken)
  listening: 'Pose 1 loop', // Therapeut notiert beim Zuhören (direkt Loop, kein Zucken)
  calm: 'calm',
  sad: 'sad',
  joy: 'joy',
  confusion: 'confusion',
  surprise: 'surprise',
};

// Timelines, die AKTUELL wirklich in lyra-avatar.riv existieren. Wird ein Name an
// die native Rive-View gegeben, der NICHT existiert, crasht die App hart — onError
// fängt das NICHT zuverlässig ab (empirisch verifiziert, der native Runtime stürzt
// tiefer ab). Deshalb mappen wir jeden unbekannten Namen VOR dem Rendern auf den
// garantiert vorhandenen Idle-Loop. → Beim Anlegen einer neuen Timeline in der
// .riv hier den exakten Namen ergänzen, dann animiert der zugehörige State.
export const EXISTING_TIMELINES = new Set<string>([
  'Idle Loop',
  'idle to Pose 1',
  'Pose 1 loop',
  '01 Wave 1',
  '01 Wave 2',
  'WALK',
  'Kedip',
]);

function safeAnim(name: string): string {
  return EXISTING_TIMELINES.has(name) ? name : EMOTION_ANIMATIONS.idle;
}

// Mehrphasige States: Intro-Timeline läuft einmal, danach Loop-Timeline. Aktuell
// leer — der Remount beim Intro→Loop-Wechsel verursacht ein sichtbares Zucken,
// daher spielen thinking/listening direkt die Loop-Pose. Mechanismus bleibt für
// künftige '<state> intro' + '<state> loop'-States verfügbar.
const TWO_PHASE: Record<string, { loop: string; introMs: number }> = {};

// Sustained = bleibt aktiv bis die Emotion explizit wechselt (z.B. solange Lyra
// "denkt", "zuhört" oder mitatmet). Alle anderen sind One-Shot-Reaktionen und
// fallen nach SETTLE_MS in den Idle-Loop zurück, damit der Avatar lebendig bleibt
// und nie auf dem letzten Frame einfriert.
const SUSTAINED = new Set<string>(['idle', 'thinking', 'listening', 'calm']);
const SETTLE_MS = 2600;

const EMOTION_LABELS: Record<string, string> = {
  idle: 'bereit',
  happy: 'froh für dich',
  empathy: 'versteht dich',
  thinking: 'überlegt ...',
  listening: 'hört zu',
  calm: 'atmet mit dir',
  sad: 'fühlt mit dir',
  joy: 'freut sich für dich',
  confusion: 'fragt nach',
  surprise: 'überrascht',
};

const SIZE_PX: Record<'sm' | 'md' | 'lg', number> = {
  sm: 40,
  md: 112,
  lg: 160,
};

type Props = {
  emotion: Emotion;
  size?: 'sm' | 'md' | 'lg';
  showLabel?: boolean;
  fallback?: Emotion;
};

export function RiveAvatar({ emotion, size = 'md', showLabel = false, fallback = 'idle' }: Props) {
  const px = SIZE_PX[size];

  const resolvedEmotion = EMOTION_ANIMATIONS[emotion] !== undefined ? emotion : fallback;

  // Aktuelle Animation als deklarativer State (kein imperatives ref.play()).
  const [currentAnim, setCurrentAnim] = useState<string>(safeAnim(EMOTION_ANIMATIONS[resolvedEmotion] ?? EMOTION_ANIMATIONS.idle));

  // Lokale URI für die .riv-Datei — geht über expo-asset damit der File
  // im App-Sandbox gecached wird statt jedes Mal von Metro zu streamen.
  // Nach erstem Load funktioniert's auch komplett offline.
  const [riveUri, setRiveUri] = useState<string | null>(cachedRiveUri);

  useEffect(() => {
    if (riveUri) return; // schon gecached
    let active = true;
    preloadRiveAsset().then((uri) => {
      if (active && uri) setRiveUri(uri);
    });
    return () => {
      active = false;
    };
  }, [riveUri]);

  useEffect(() => {
    const wanted = EMOTION_ANIMATIONS[resolvedEmotion] ?? EMOTION_ANIMATIONS.idle;
    const base = safeAnim(wanted);
    setCurrentAnim(base);

    // Mehrphasig (z.B. happy: 'idle to Pose 1' → 'Pose 1 loop'): Intro einmal
    // spielen, dann in den Loop blenden. Nur wenn BEIDE Timelines wirklich
    // existieren — sonst bleibt's bei base (kein Sprung auf einen toten Namen).
    const phase = TWO_PHASE[resolvedEmotion];
    if (phase && EXISTING_TIMELINES.has(wanted) && EXISTING_TIMELINES.has(phase.loop)) {
      const t = setTimeout(() => setCurrentAnim(phase.loop), phase.introMs);
      return () => clearTimeout(t);
    }

    // One-Shots (empathy/sad/joy/confusion/surprise) frieren sonst auf dem
    // letzten Frame ein → nach SETTLE_MS zurück in den Idle-Loop. Sustained-
    // States (idle/happy/thinking/listening/calm) bleiben aktiv bis Emotion-Wechsel.
    if (!SUSTAINED.has(resolvedEmotion) && base !== EMOTION_ANIMATIONS.idle) {
      const t = setTimeout(() => setCurrentAnim(EMOTION_ANIMATIONS.idle), SETTLE_MS);
      return () => clearTimeout(t);
    }
  }, [resolvedEmotion]);

  // Crash-Guard: rive-react-native crasht NATIV (App-Absturz, kein JS-Error),
  // wenn animationName nicht in der .riv existiert — z.B. ein neuer Emotion-State,
  // der noch nicht gebaut wurde. Sobald onError gesetzt ist, schaltet die Lib auf
  // isUserHandlingErrors und ruft statt zu crashen diesen Handler. Wir fallen dann
  // auf den garantiert vorhandenen Idle-Loop zurück (Guard verhindert Endlos-Reset).
  const handleRiveError = (err: RNRiveError) => {
    if (__DEV__) console.warn('[RiveAvatar]', err?.type, '—', err?.message);
    if (
      err?.type === RNRiveErrorType.IncorrectAnimationName &&
      currentAnim !== EMOTION_ANIMATIONS.idle
    ) {
      setCurrentAnim(EMOTION_ANIMATIONS.idle);
    }
  };

  return (
    <View style={{ alignItems: 'center', gap: 4 }}>
      <View
        style={{
          width: px,
          height: px,
          // Floating-Shadow nur für md/lg — bei sm zu klein, würde unsauber wirken
          ...(size !== 'sm' && {
            shadowColor: '#000',
            shadowOffset: { width: 0, height: 8 },
            shadowOpacity: 0.18,
            shadowRadius: 16,
            elevation: 10,
          }),
        }}
      >
        <View
          style={{
            width: px,
            height: px,
            borderRadius: px / 2,
            overflow: 'hidden',
            backgroundColor: '#ffffff',
            borderWidth: size !== 'sm' ? 4 : 0,
            borderColor: '#ffffff',
          }}
        >
          {Platform.OS === 'android' ? (
            // Android: Bundle-Resource direkt (kein expo-asset Preload nötig)
            <Rive
              key={currentAnim}
              resourceName={ANDROID_RIVE_RESOURCE}
              autoplay
              onError={handleRiveError}
              animationName={currentAnim}
              fit={Fit.Cover}
              alignment={Alignment.Center}
              style={{ width: px, height: px }}
            />
          ) : riveUri ? (
            // iOS: file:// URI aus expo-asset Cache funktioniert
            <Rive
              key={currentAnim}
              source={{ uri: riveUri }}
              autoplay
              onError={handleRiveError}
              animationName={currentAnim}
              fit={Fit.Cover}
              alignment={Alignment.Center}
              style={{ width: px, height: px }}
            />
          ) : (
            <View style={{ width: px, height: px, backgroundColor: '#ffffff' }} />
          )}
        </View>
      </View>

      {showLabel && (
        <Text style={{ fontSize: 11, color: '#737373', letterSpacing: 0.3 }}>
          {EMOTION_LABELS[resolvedEmotion] ?? EMOTION_LABELS[fallback] ?? ''}
        </Text>
      )}
    </View>
  );
}
