import { useCallback, useEffect, useState } from 'react';
import { Pressable, Text, View } from 'react-native';
import { Mic, Type as TypeIcon, Video as VideoIcon } from 'lucide-react-native';
import * as Haptics from 'expo-haptics';
import { useCameraPermissions, useMicrophonePermissions } from 'expo-camera';
import { toast } from 'sonner-native';
import { cn } from '@/lib/utils';
import { useEvent } from '@/hooks/use-event';
import { usePlaybackPauseStore } from '@/stores/playback-pause-store';
import { useAuthStore } from '@/stores/auth-store';
import { createTextParticle, uploadMediaParticle } from '@/lib/upload';
import type { ParticlePath } from '@/lib/particle-path';
import {
  useStreamComposingBroadcastOptional,
  type ComposingMode,
} from '@/features/stream-view/stream-presence-context';
import { TextComposeModal } from './TextComposeModal';
import { VideoRecordingOverlay } from './VideoRecordingOverlay';
import { AudioRecordingOverlay } from './AudioRecordingOverlay';
import { ReviewSheet } from './ReviewSheet';

type RecordingMode = 'video' | 'audio';

type ComposeUiState =
  | { kind: 'idle' }
  | { kind: 'recording'; mode: RecordingMode }
  | {
      kind: 'review';
      mode: RecordingMode;
      uri: string;
      durationMs: number;
    }
  | {
      kind: 'uploading';
      mode: RecordingMode;
      uri: string;
      durationMs: number;
    };

interface SubmitMediaParams {
  fileUri: string;
  mimeType: string;
  durationMs: number;
  source: 'camera' | 'screen';
}

interface ComposeDockProps {
  networkId: string;
  targetPath: ParticlePath;
  silentPresence?: boolean;
  submitMedia?: (params: SubmitMediaParams) => Promise<void>;
  submitText?: (content: string) => Promise<void>;
  /**
   * Called with the new particle's id right after it's created on the default
   * send path. Not fired when `submitMedia`/`submitText` overrides are supplied,
   * since those own the created particle themselves. Lets the stream follow a
   * just-sent particle when the user was at the end.
   */
  onParticleCreated?: (particleId: string) => void;
}

export function ComposeDock({
  networkId,
  targetPath,
  silentPresence = false,
  submitMedia,
  submitText: submitTextOverride,
  onParticleCreated,
}: ComposeDockProps) {
  const userId = useAuthStore((s) => s.user?.id);

  const [mode, setMode] = useState<RecordingMode>('video');
  const [ui, setUi] = useState<ComposeUiState>({ kind: 'idle' });
  const [textOpen, setTextOpen] = useState(false);

  const [camPerm, requestCamPerm] = useCameraPermissions();
  const [micPerm, requestMicPerm] = useMicrophonePermissions();

  // Tell StreamView to fully unmount its expo-video player while we record.
  // That player otherwise holds the iOS AVAudioSession and crashes the camera.
  const setComposing = usePlaybackPauseStore((s) => s.setComposing);
  const isComposing = ui.kind !== 'idle' || textOpen;
  useEffect(() => {
    setComposing(isComposing);
    return () => setComposing(false);
  }, [isComposing, setComposing]);

  useComposingBroadcast({ ui, textOpen, silent: silentPresence });

  const ensurePermissions = useCallback(
    async (forVideo: boolean): Promise<boolean> => {
      if (forVideo) {
        const cam = camPerm?.granted ? camPerm : await requestCamPerm();
        if (!cam.granted) {
          toast.error('Camera permission is required to record video.');
          return false;
        }
      }
      const mic = micPerm?.granted ? micPerm : await requestMicPerm();
      if (!mic.granted) {
        toast.error('Microphone permission is required to record.');
        return false;
      }
      return true;
    },
    [camPerm, micPerm, requestCamPerm, requestMicPerm],
  );

  const startRecording = useEvent(async () => {
    if (ui.kind !== 'idle') return;
    const ok = await ensurePermissions(mode === 'video');
    if (!ok) return;
    void Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
    setUi({ kind: 'recording', mode });
  });

  const handleRecordingComplete = useCallback(
    ({ uri, durationMs }: { uri: string; durationMs: number }) => {
      void Haptics.selectionAsync();
      setUi((prev) => {
        const m = 'mode' in prev ? prev.mode : mode;
        return { kind: 'review', mode: m, uri, durationMs };
      });
    },
    [mode],
  );

  const handleRecordingCancel = useCallback(() => {
    setUi({ kind: 'idle' });
  }, []);

  const sendReview = useEvent(async () => {
    if (ui.kind !== 'review' || !userId) return;
    const captured = ui;
    setUi({
      kind: 'uploading',
      mode: captured.mode,
      uri: captured.uri,
      durationMs: captured.durationMs,
    });
    try {
      const mimeType = captured.mode === 'audio' ? 'audio/mp4' : 'video/mp4';
      if (submitMedia) {
        await submitMedia({
          fileUri: captured.uri,
          mimeType,
          durationMs: captured.durationMs,
          source: 'camera',
        });
      } else {
        const particleId = await uploadMediaParticle({
          networkId,
          targetPath,
          fileUri: captured.uri,
          mimeType,
          durationMs: captured.durationMs,
          source: 'camera',
          createdByHumanId: userId,
        });
        onParticleCreated?.(particleId);
      }
      void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
      setUi({ kind: 'idle' });
    } catch (err) {
      void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);
      setUi(captured);
      throw err;
    }
  });

  const retake = useCallback(() => setUi({ kind: 'idle' }), []);
  const cancelReview = useCallback(() => setUi({ kind: 'idle' }), []);

  const submitText = useEvent(async (content: string) => {
    if (!userId) throw new Error('Not signed in.');
    if (submitTextOverride) {
      await submitTextOverride(content);
    } else {
      const particleId = await createTextParticle({
        networkId,
        targetPath,
        content,
        createdByHumanId: userId,
      });
      onParticleCreated?.(particleId);
    }
    void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
  });

  const dockHidden =
    ui.kind === 'review' || ui.kind === 'uploading' || ui.kind === 'recording';

  return (
    <>
      {!dockHidden ? (
        <View pointerEvents="box-none" className="absolute inset-x-0 bottom-0">
          <View
            pointerEvents="box-none"
            className="flex-row items-center justify-between px-8 pb-10"
          >
            <Pressable
              onPress={() =>
                setMode((m) => (m === 'video' ? 'audio' : 'video'))
              }
              disabled={ui.kind !== 'idle'}
              accessibilityLabel={`Switch to ${
                mode === 'video' ? 'audio' : 'video'
              } mode`}
              className={cn(
                'h-11 w-11 items-center justify-center rounded-full bg-white/15',
                ui.kind !== 'idle' && 'opacity-40',
              )}
            >
              {mode === 'video' ? (
                <VideoIcon color="white" size={20} strokeWidth={1.6} />
              ) : (
                <Mic color="white" size={20} strokeWidth={1.6} />
              )}
            </Pressable>

            <View className="items-center">
              <Pressable
                onPress={startRecording}
                disabled={ui.kind !== 'idle'}
                accessibilityLabel={`Record ${mode}`}
                className="h-20 w-20 items-center justify-center rounded-full bg-white"
              >
                <View className="h-6 w-6 rounded bg-black" />
              </Pressable>
              <Text className="text-white/60 mt-2 text-xs">Tap to record</Text>
            </View>

            <Pressable
              onPress={() => setTextOpen(true)}
              disabled={ui.kind !== 'idle'}
              accessibilityLabel="Compose text"
              className={cn(
                'h-11 w-11 items-center justify-center rounded-full bg-white/15',
                ui.kind !== 'idle' && 'opacity-40',
              )}
            >
              <TypeIcon color="white" size={20} strokeWidth={1.6} />
            </Pressable>
          </View>
        </View>
      ) : null}

      {ui.kind === 'recording' ? (
        ui.mode === 'video' ? (
          <VideoRecordingOverlay
            onComplete={handleRecordingComplete}
            onCancel={handleRecordingCancel}
          />
        ) : (
          <AudioRecordingOverlay
            onComplete={handleRecordingComplete}
            onCancel={handleRecordingCancel}
          />
        )
      ) : null}

      <ReviewSheet
        open={ui.kind === 'review' || ui.kind === 'uploading'}
        uri={ui.kind === 'review' || ui.kind === 'uploading' ? ui.uri : null}
        mode={ui.kind === 'review' || ui.kind === 'uploading' ? ui.mode : null}
        durationMs={
          ui.kind === 'review' || ui.kind === 'uploading' ? ui.durationMs : 0
        }
        sending={ui.kind === 'uploading'}
        onSend={sendReview}
        onRetake={retake}
        onCancel={cancelReview}
      />

      <TextComposeModal
        open={textOpen}
        onClose={() => setTextOpen(false)}
        onSubmit={submitText}
      />
    </>
  );
}

function useComposingBroadcast({
  ui,
  textOpen,
  silent,
}: {
  ui: ComposeUiState;
  textOpen: boolean;
  silent: boolean;
}) {
  // null when the dock is rendered outside a stream (no presence provider).
  const broadcast = useStreamComposingBroadcastOptional();

  const mode: ComposingMode | null =
    ui.kind === 'recording' ? 'recording' : textOpen ? 'typing' : null;

  useEffect(() => {
    if (silent || !broadcast) return;
    if (mode) {
      broadcast.startComposing(mode);
      return () => broadcast?.stopComposing();
    }
  }, [mode, silent, broadcast]);
}
