import { useCallback, useEffect } from 'react';
import { Alert, Dimensions, Pressable, Text, View } from 'react-native';
import type { Human } from '@/api/types';
import { SafeAreaView } from 'react-native-safe-area-context';
import { StatusBar } from 'expo-status-bar';
import {
  AudioSession,
  LiveKitRoom,
  VideoTrack,
  isTrackReference,
  useLocalParticipant,
  useRoomContext,
  useTracks,
} from '@livekit/react-native';
import type { TrackReferenceOrPlaceholder } from '@livekit/components-core';
import { Track } from 'livekit-client';
import { Mic, MicOff, PhoneOff, Video, VideoOff } from 'lucide-react-native';
import { useNetwork } from '@/hooks/use-networks';
import { resolveHumanDisplay } from '@/lib/humans';
import { cn } from '@/lib/utils';
import type { RootStackScreenProps } from '@/navigation/types';

/**
 * Mobile huddle screen — LiveKit room with a tile grid, basic mic/camera
 * controls, and a leave button. Mirrors the desktop HuddleApp but tuned for
 * touch: bigger controls, grid layout instead of focus/carousel, no chat or
 * screenshare (camera + audio only).
 */
export function HuddleScreen({
  route,
  navigation,
}: RootStackScreenProps<'Huddle'>) {
  const { token, serverUrl, streamName, networkId } = route.params;

  // iOS in particular requires us to bracket the room session with
  // start/stop calls so the AVAudioSession is configured for VoIP routing
  // (earpiece → speaker, ducking, etc.). Without this, remote audio is
  // routed to the receiver speaker and the user has to hold the phone to
  // their ear to hear anyone.
  useEffect(() => {
    void AudioSession.startAudioSession().catch(() => {
      // Non-fatal: room will still connect, audio routing may just be
      // less ideal. Failing the whole screen here would feel worse.
    });
    return () => {
      void AudioSession.stopAudioSession();
    };
  }, []);

  const leave = useCallback(() => {
    navigation.goBack();
  }, [navigation]);

  return (
    <View className="flex-1 bg-black">
      <StatusBar style="light" />
      <LiveKitRoom
        token={token}
        serverUrl={serverUrl}
        connect={true}
        audio={true}
        video={false}
        options={{ adaptiveStream: { pixelDensity: 'screen' } }}
        onDisconnected={leave}
        onError={(err) => {
          Alert.alert('Huddle error', err.message ?? 'Failed to connect.');
          leave();
        }}
      >
        <HuddleRoom
          networkId={networkId}
          streamName={streamName}
          onLeave={leave}
        />
      </LiveKitRoom>
    </View>
  );
}

interface HuddleRoomProps {
  networkId: string;
  streamName: string;
  onLeave: () => void;
}

function HuddleRoom({ networkId, streamName, onLeave }: HuddleRoomProps) {
  const room = useRoomContext();
  const network = useNetwork(networkId);
  const { localParticipant, isMicrophoneEnabled, isCameraEnabled } =
    useLocalParticipant();

  // `withPlaceholder: true` ensures we get a tile for every participant even
  // when their camera is off — same pattern as desktop's HuddleContent.
  const tracks = useTracks(
    [{ source: Track.Source.Camera, withPlaceholder: true }],
    { onlySubscribed: false },
  );

  const toggleMic = useCallback(() => {
    void localParticipant.setMicrophoneEnabled(!isMicrophoneEnabled);
  }, [localParticipant, isMicrophoneEnabled]);

  const toggleCamera = useCallback(() => {
    void localParticipant.setCameraEnabled(!isCameraEnabled);
  }, [localParticipant, isCameraEnabled]);

  const leave = useCallback(async () => {
    try {
      await room.disconnect();
    } finally {
      onLeave();
    }
  }, [room, onLeave]);

  return (
    <SafeAreaView className="flex-1" edges={['top', 'bottom']}>
      <View className="flex-row items-center justify-between px-4 pt-2 pb-3">
        <View className="flex-1">
          <Text
            className="text-white text-base font-semibold"
            numberOfLines={1}
          >
            {streamName}
          </Text>
          <Text className="text-white/60 text-xs mt-0.5">
            {tracks.length === 1
              ? '1 participant'
              : `${tracks.length} participants`}
          </Text>
        </View>
      </View>

      <View className="flex-1 px-2">
        <TileGrid tiles={tracks} humans={network?.humans ?? []} />
      </View>

      <View className="flex-row items-center justify-center gap-4 px-4 py-4">
        <ControlButton
          label={isMicrophoneEnabled ? 'Mute' : 'Unmute'}
          active={isMicrophoneEnabled}
          onPress={toggleMic}
          icon={
            isMicrophoneEnabled ? (
              <Mic color="white" size={22} />
            ) : (
              <MicOff color="white" size={22} />
            )
          }
        />
        <ControlButton
          label={isCameraEnabled ? 'Stop video' : 'Start video'}
          active={isCameraEnabled}
          onPress={toggleCamera}
          icon={
            isCameraEnabled ? (
              <Video color="white" size={22} />
            ) : (
              <VideoOff color="white" size={22} />
            )
          }
        />
        <ControlButton
          label="Leave"
          tone="danger"
          onPress={() => void leave()}
          icon={<PhoneOff color="white" size={22} />}
        />
      </View>
    </SafeAreaView>
  );
}

interface TileGridProps {
  tiles: TrackReferenceOrPlaceholder[];
  humans: Human[];
}

function TileGrid({ tiles, humans }: TileGridProps) {
  // Compute a square-ish grid: 1 → 1col, 2 → 1col (stacked), 3-4 → 2col,
  // 5+ → 2col with scroll. Keeps each tile big enough on a phone screen.
  const columns = tiles.length <= 1 ? 1 : 2;
  const { width, height } = Dimensions.get('window');
  const rows = Math.max(1, Math.ceil(tiles.length / columns));
  const tileWidth = (width - 16) / columns - 8;
  // Subtract approx chrome height (header + control bar ≈ 200px). This is a
  // simple heuristic — we don't need pixel-perfect since tiles use aspectRatio.
  const availableHeight = height - 220;
  const tileHeight = Math.max(140, availableHeight / rows - 8);

  return (
    <View className="flex-1 flex-row flex-wrap items-start justify-center">
      {tiles.map((tile) => (
        <View
          key={trackKey(tile)}
          style={{
            width: tileWidth,
            height: tileHeight,
            margin: 4,
          }}
        >
          <Tile tile={tile} humans={humans} />
        </View>
      ))}
    </View>
  );
}

function Tile({
  tile,
  humans,
}: {
  tile: TrackReferenceOrPlaceholder;
  humans: Human[];
}) {
  const identity = tile.participant.identity;
  const display = resolveHumanDisplay(identity, humans);
  const name = tile.participant.name || display.displayName;
  const initials = display.initials;
  const isSpeaking = tile.participant.isSpeaking;
  const muted =
    tile.participant.getTrackPublication(Track.Source.Microphone)?.isMuted ??
    true;

  const hasVideo = isTrackReference(tile) && !tile.publication.isMuted;

  return (
    <View
      className={cn(
        'flex-1 overflow-hidden rounded-2xl bg-neutral-900',
        isSpeaking && 'border-2 border-emerald-400',
      )}
    >
      {hasVideo ? (
        <VideoTrack trackRef={tile} style={{ flex: 1 }} objectFit="cover" />
      ) : (
        <View className="flex-1 items-center justify-center">
          <View className="h-16 w-16 items-center justify-center rounded-full bg-neutral-700">
            <Text className="text-white text-xl font-semibold">{initials}</Text>
          </View>
        </View>
      )}
      <View className="absolute left-2 bottom-2 flex-row items-center gap-1 rounded-full bg-black/60 px-2 py-1">
        {muted ? (
          <MicOff color="white" size={12} />
        ) : (
          <Mic color="#34d399" size={12} />
        )}
        <Text className="text-white text-xs" numberOfLines={1}>
          {name}
        </Text>
      </View>
    </View>
  );
}

function trackKey(tile: TrackReferenceOrPlaceholder): string {
  const sid = isTrackReference(tile)
    ? tile.publication.trackSid
    : 'placeholder';
  return `${tile.participant.identity}:${tile.source}:${sid}`;
}

interface ControlButtonProps {
  icon: React.ReactNode;
  label: string;
  onPress: () => void;
  active?: boolean;
  tone?: 'default' | 'danger';
}

function ControlButton({
  icon,
  label,
  onPress,
  active = false,
  tone = 'default',
}: ControlButtonProps) {
  // Used purely for the visual state — destructive tone always wins so
  // "Leave" is unmistakable regardless of toggle state.
  return (
    <Pressable
      onPress={onPress}
      accessibilityLabel={label}
      className={cn(
        'h-14 w-14 items-center justify-center rounded-full',
        tone === 'danger'
          ? 'bg-red-600 active:bg-red-700'
          : active
            ? 'bg-white/20 active:bg-white/30'
            : 'bg-white/10 active:bg-white/20',
      )}
    >
      {icon}
    </Pressable>
  );
}
