import {
  useMemo,
  useRef,
  useEffect,
  useCallback,
  memo,
  createElement,
} from 'react';
import { useNavigate } from 'react-router-dom';
import {
  Radio,
  MessageSquare,
  Video,
  Mic,
  Image,
  FileText,
  CircleCheck,
  StickyNote,
  Headphones,
  Trash2,
  type LucideIcon,
} from 'lucide-react';
import { cn, getInitials } from '@/lib/utils';
import { useLiveLatestChild } from '@/hooks/use-particle';
import { useAuthStore } from '@/stores/auth-store';
import { particlePath } from '@/lib/particle-path';
import { resolveHumanDisplay } from '@/lib/humans';
import { RelativeTimestamp } from '@/components/relative-timestamp';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { Separator } from '@/components/ui/separator';
import { Progress } from '@/components/ui/progress';
import { Small } from '@/components/ui/typography';
import { Button } from '@/components/ui/button';
import {
  isParticleDeleted,
  type Particle,
  type StreamProperties,
} from '@/api/types';
import type { StreamParticle } from '@/hooks/use-stream-particles';
import { useNetwork } from '@/hooks/use-networks';
import { useStreamAutoplay } from '@/hooks/use-stream-autoplay';
import { useDownloadUrl } from '@/hooks/use-download-url';
import { StreamContextMenu } from '@/features/particles/stream-context-menu';

function VideoThumbnail({
  objectId,
  isUnseen,
}: {
  objectId: string;
  isUnseen: boolean;
}) {
  const { data: url } = useDownloadUrl(objectId);
  return (
    <div
      className={cn(
        'size-8 shrink-0 overflow-hidden rounded-md bg-muted',
        isUnseen && 'ring-2 ring-primary',
      )}
    >
      {url && (
        <video
          // Seek ~15 frames in so we skip any initial black/fade-in frames
          src={`${url}#t=0.5`}
          muted
          playsInline
          preload="metadata"
          className="h-full w-full object-cover"
        />
      )}
    </div>
  );
}

function getParticleTypeIcon(particle: Particle): LucideIcon {
  if (isParticleDeleted(particle)) return Trash2;
  switch (particle.type) {
    case 'text':
      return MessageSquare;
    case 'media': {
      const mime = particle.properties.mime_type;
      if (mime.startsWith('video/')) return Video;
      if (mime.startsWith('audio/')) return Mic;
      if (mime.startsWith('image/')) return Image;
      return Video;
    }
    case 'file':
      return FileText;
    case 'quest':
      return CircleCheck;
    case 'paper':
      return StickyNote;
    default:
      return Radio;
  }
}

function getMessagePreview(particle: Particle): string {
  if (isParticleDeleted(particle)) return 'Deleted particle';
  switch (particle.type) {
    case 'text':
      return particle.properties.content;
    case 'media': {
      const mime = particle.properties.mime_type;
      if (mime.startsWith('image/')) return 'Photo';
      if (mime.startsWith('video/') || mime.startsWith('audio/')) {
        const transcriptText = particle.properties.transcript?.transcript;
        if (transcriptText) return transcriptText;
        return mime.startsWith('video/') ? 'Video clip' : 'Voice note';
      }
      return 'Media';
    }
    case 'file':
      return particle.properties.filename;
    case 'quest':
      return particle.properties.title;
    case 'paper':
      return particle.properties.title;
    default:
      return particle.type;
  }
}

const StreamRow = memo(function StreamRow({
  particle,
  networkId,
  onNavigate,
  isSelected,
  shortcutKey,
}: {
  particle: Particle & { type: 'stream'; properties: StreamProperties };
  networkId: string;
  onNavigate: (streamId: string) => void;
  isSelected?: boolean;
  shortcutKey?: number;
}) {
  const streamPath = particlePath(networkId, [particle.id]);
  const { latestChild } = useLiveLatestChild(streamPath);
  const user = useAuthStore((s) => s.user);
  const userId = user?.id ?? '';
  const network = useNetwork(networkId);

  useStreamAutoplay(latestChild, particle, networkId, network ?? undefined);

  const hasActiveHuddle =
    particle.huddle_active_participants &&
    particle.huddle_active_participants.length > 0;
  const huddleCount = particle.huddle_active_participants?.length ?? 0;

  const isDM =
    particle.visible_to.length === 2 &&
    particle.visible_to.every((v) => v.startsWith('human:'));

  const initials = useMemo(() => {
    if (isDM) {
      const otherEntry = particle.visible_to.find(
        (v) => v !== `human:${userId}`,
      );
      if (otherEntry) {
        const otherId = otherEntry.replace('human:', '');
        const otherHuman = network?.humans?.find((h) => h.id === otherId);
        if (otherHuman) return getInitials(otherHuman.email);
      }
    }

    if (latestChild) {
      const creator = network?.humans?.find(
        (h) => h.id === latestChild.created_by_human_id,
      );
      if (creator) return getInitials(creator.email);
    }

    return particle.properties.name.slice(0, 2).toUpperCase();
  }, [
    isDM,
    particle.visible_to,
    particle.properties.name,
    userId,
    latestChild,
    network,
  ]);

  const isUnseen = useMemo(() => {
    if (!latestChild) return false;
    const latestChildTimestamp = latestChild.created_at.getTime();
    const userPlaybackPosition =
      particle.playback_markers?.[userId]?.getTime() ?? 0;
    return latestChildTimestamp > userPlaybackPosition;
  }, [latestChild, particle.playback_markers, userId]);

  const senderPrefix = useMemo(() => {
    if (!latestChild) return null;
    const isCurrentUser = latestChild.created_by_human_id === userId;
    if (isDM) {
      return isCurrentUser ? 'You: ' : null;
    }
    // Group stream
    if (isCurrentUser) return 'You: ';
    const { displayName } = resolveHumanDisplay(
      latestChild.created_by_human_id,
      network?.humans,
    );
    const capitalized =
      displayName.charAt(0).toUpperCase() + displayName.slice(1);
    return `${capitalized}: `;
  }, [latestChild, userId, isDM, network]);

  const subtitle = latestChild
    ? getMessagePreview(latestChild)
    : particle.properties.name;

  // Rendered via createElement below: a call-result used directly as a JSX tag
  // is flagged as a dynamically-created component.
  const typeIcon = latestChild ? getParticleTypeIcon(latestChild) : Radio;

  const videoThumbObjectId =
    latestChild &&
    !isParticleDeleted(latestChild) &&
    latestChild.type === 'media' &&
    latestChild.properties.mime_type.startsWith('video/')
      ? latestChild.properties.object_id
      : null;

  return (
    <div
      role="button"
      tabIndex={0}
      onClick={() => onNavigate(particle.id)}
      onKeyDown={(e) => {
        if (e.key === 'Enter' || e.key === ' ') onNavigate(particle.id);
      }}
      className={cn(
        'flex w-full items-center gap-3 px-4 py-3 text-left cursor-pointer transition-colors hover:bg-accent',
        isSelected && 'bg-accent',
        hasActiveHuddle && 'bg-gradient-to-r from-red-500/10 to-transparent',
      )}
    >
      {shortcutKey && (
        <kbd className="flex size-5 shrink-0 items-center justify-center rounded bg-white/10 font-mono text-xs text-muted-foreground">
          {shortcutKey}
        </kbd>
      )}
      {videoThumbObjectId ? (
        <VideoThumbnail objectId={videoThumbObjectId} isUnseen={!!isUnseen} />
      ) : (
        <Avatar className={cn(isUnseen && 'ring-2 ring-primary')}>
          <AvatarFallback className="bg-primary/10 text-primary font-medium">
            {initials}
          </AvatarFallback>
        </Avatar>
      )}
      <div className="min-w-0 flex-1">
        <div className="flex items-center justify-between gap-2">
          <p
            className={cn(
              'truncate text-sm',
              isUnseen
                ? 'font-semibold text-foreground'
                : 'font-medium text-muted-foreground',
            )}
          >
            {particle.properties.name}
          </p>
          <div className="flex shrink-0 items-center gap-1.5">
            {hasActiveHuddle && (
              <span className="flex items-center gap-1 rounded-full bg-red-500/15 px-1.5 py-0.5">
                <Headphones className="size-3 text-red-400" />
                <span className="text-[10px] font-medium text-red-400">
                  {huddleCount}
                </span>
              </span>
            )}
            {latestChild && (
              <Small
                className={cn(
                  'shrink-0',
                  isUnseen ? 'text-primary' : 'text-muted-foreground',
                )}
              >
                <RelativeTimestamp date={latestChild.created_at} />
              </Small>
            )}
          </div>
        </div>
        <div className="flex items-center gap-1">
          {createElement(typeIcon, {
            className: cn(
              'size-3.5 shrink-0',
              isUnseen ? 'text-foreground' : 'text-muted-foreground',
            ),
          })}
          <Small
            className={cn(
              'truncate',
              isUnseen
                ? 'text-foreground font-medium'
                : 'text-muted-foreground font-normal',
            )}
          >
            {senderPrefix && (
              <span className="text-muted-foreground">{senderPrefix}</span>
            )}
            {subtitle}
          </Small>
        </div>
      </div>
      {isUnseen && <span className="size-2 shrink-0 rounded-full bg-primary" />}
    </div>
  );
});

interface ParticleListViewProps {
  streams: StreamParticle[];
  networkId: string;
  isLoading: boolean;
  selectedIndex?: number | null;
  /** When true, render a footer that invokes onLoadMore. */
  canLoadMore?: boolean;
  onLoadMore?: () => void;
}

/**
 * List of stream particles for a container (network root, folder, etc.).
 */
export function ParticleListView({
  streams,
  networkId,
  isLoading,
  selectedIndex,
  canLoadMore,
  onLoadMore,
}: ParticleListViewProps) {
  const navigate = useNavigate();
  const rowRefs = useRef<(HTMLDivElement | null)[]>([]);

  const navigateToStream = useCallback(
    (streamId: string) => navigate(`/${networkId}/${streamId}`),
    [navigate, networkId],
  );

  useEffect(() => {
    if (
      selectedIndex !== null &&
      selectedIndex !== undefined &&
      selectedIndex >= 0
    ) {
      rowRefs.current[selectedIndex]?.scrollIntoView({ block: 'nearest' });
    }
  }, [selectedIndex]);

  if (isLoading) {
    return <Progress />;
  }

  if (streams.length === 0) {
    return (
      <div className="mx-auto flex h-full max-w-sm flex-col items-center justify-center gap-2 px-4 text-center">
        <Radio className="text-muted-foreground size-8" />
        <p className="text-muted-foreground text-sm">
          No streams here. Start a conversation using the keyboard shortcuts
          below.
        </p>
      </div>
    );
  }

  return (
    <div>
      {streams.map((stream, index) => (
        <StreamContextMenu
          key={stream.id}
          particle={stream}
          networkId={networkId}
        >
          <div
            ref={(el) => {
              rowRefs.current[index] = el;
            }}
          >
            <StreamRow
              particle={stream}
              networkId={networkId}
              onNavigate={navigateToStream}
              isSelected={index === selectedIndex}
              shortcutKey={index < 9 ? index + 1 : undefined}
            />
            {index < streams.length - 1 && <Separator className="px-4" />}
          </div>
        </StreamContextMenu>
      ))}
      {canLoadMore && onLoadMore && (
        <div className="flex justify-center p-3">
          <Button variant="ghost" size="sm" onClick={onLoadMore}>
            Load more
          </Button>
        </div>
      )}
    </div>
  );
}
