import { useEffect, useRef } from 'react';

import {
  CrownIcon,
  HeadphonesOffIcon,
  LockIcon,
  MicOffIcon,
  PinIcon,
  WifiLowIcon,
  WifiOffIcon,
} from './icons';

/** Discord-style connection-quality badge state. Matches LiveKit's
 *  ConnectionQuality enum values so consumers can pass the value through
 *  without conversion. `unknown` and `excellent`/`good` render nothing — the
 *  badge only surfaces when the user actually has degraded media. */
export type TileConnectionQuality = 'excellent' | 'good' | 'poor' | 'lost' | 'unknown';

export type AvatarColorKey = 'violet' | 'amber' | 'rose' | 'teal';

const AVATAR_COLORS: Record<AvatarColorKey, { bg: string; fg: string }> = {
  // Tailwind classes tuned per spec: violet/amber/rose/teal swatches in both
  // light and dark modes. Dark pairs invert to keep contrast readable.
  violet: {
    bg: 'bg-violet-200 dark:bg-violet-900/60',
    fg: 'text-violet-800 dark:text-violet-200',
  },
  amber: {
    bg: 'bg-amber-200 dark:bg-amber-900/60',
    fg: 'text-amber-900 dark:text-amber-200',
  },
  rose: {
    bg: 'bg-rose-200 dark:bg-rose-900/60',
    fg: 'text-rose-900 dark:text-rose-200',
  },
  teal: {
    bg: 'bg-teal-200 dark:bg-teal-900/60',
    fg: 'text-teal-900 dark:text-teal-200',
  },
};

const COLOR_KEYS: AvatarColorKey[] = ['violet', 'amber', 'rose', 'teal'];

// Stable per-user color from identity string — keeps avatars visually
// consistent across re-mounts without needing a color field on the profile.
export function colorKeyFor(id: string): AvatarColorKey {
  let hash = 0;
  for (let i = 0; i < id.length; i++) {
    hash = (hash * 31 + id.charCodeAt(i)) >>> 0;
  }
  return COLOR_KEYS[hash % COLOR_KEYS.length]!;
}

export interface ParticipantTileProps {
  userId: string;
  displayName: string;
  avatarUrl: string | null;
  me: boolean;
  muted: boolean;
  /** True when the participant has deafened. Propagated from peers via the
   *  LiveKit data channel, so remote tiles surface the headphones-off badge
   *  the same way the local tile does. */
  deafened: boolean;
  /** Discord-style: true if this participant is the call host (initiator).
   *  Drives the crown badge. Hidden in 1:1 calls — set false there. */
  isHost?: boolean;
  /** True iff the user has pinned this tile via right-click → "Anpinnen".
   *  Renders a small pin badge top-left and locks the focus mode to this
   *  tile regardless of who is currently speaking. */
  pinned?: boolean;
  /** Discord-style "LIVE" pill — true iff this participant is currently
   *  publishing a screen share. Shown on the user tile so observers know
   *  the share-tile next to it belongs to this person. */
  streaming?: boolean;
  speaking: boolean;
  video: boolean;
  e2ee: boolean;
  /** MediaStreamTrack for the participant's active camera, when `video` is
   *  true. When null the tile falls back to the avatar placeholder. */
  videoTrack?: MediaStreamTrack | null;
  /** SFU-measured connection quality. Only `poor` and `lost` show a badge. */
  connectionQuality?: TileConnectionQuality;
  size?: 'default' | 'small';
  focused?: boolean;
  onClick?: () => void;
  onDoubleClick?: () => void;
  onContextMenu?: (e: React.MouseEvent) => void;
}

export function CallParticipantTile(props: ParticipantTileProps) {
  const {
    displayName,
    me,
    muted,
    deafened,
    speaking,
    video,
    e2ee,
    connectionQuality,
    isHost = false,
    pinned = false,
    streaming = false,
    size = 'default',
    focused = false,
    onClick,
    onDoubleClick,
    onContextMenu,
  } = props;

  const small = size === 'small';
  // Discord-style: full tile border switches to emerald the whole time the
  // user is speaking. Same treatment for audio + video tiles so the visual
  // is consistent regardless of camera state. The inner-glow span on top of
  // it adds a subtle inset highlight that reads even when the underlying
  // tile is bright (e.g. a video stream).
  const borderClass = speaking
    ? 'border-emerald-500 shadow-[0_0_0_2px_rgba(22,163,74,0.35)] dark:border-emerald-400'
    : focused
      ? 'border-accent'
      : 'border-line hover:border-accent';

  return (
    <div
      onClick={onClick}
      onDoubleClick={onDoubleClick}
      onContextMenu={onContextMenu}
      className={
        'relative flex flex-col overflow-hidden rounded-[14px] border-[2px] bg-surface-3 transition-colors duration-150 ' +
        (onClick ? 'cursor-pointer ' : '') +
        borderClass +
        (small ? ' min-w-[140px]' : '')
      }
    >
      {video ? (
        <VideoStub {...props} small={small} fit={focused ? 'contain' : 'cover'} />
      ) : (
        <AudioContent {...props} small={small} />
      )}

      {speaking && (
        <span
          aria-hidden="true"
          className="pointer-events-none absolute inset-0 z-10 rounded-[12px] shadow-[inset_0_0_18px_rgba(34,197,94,0.45)]"
        />
      )}

      <ConnectionQualityBadge quality={connectionQuality} small={small} />

      {pinned && (
        <span
          aria-label="Angepinnt"
          title="Angepinnt"
          className={
            'pointer-events-none absolute left-2 top-2 z-20 flex items-center justify-center rounded-md bg-accent text-accent-fg ' +
            (small ? 'h-5 w-5' : 'h-6 w-6')
          }
        >
          <PinIcon className={small ? 'h-3 w-3' : 'h-3.5 w-3.5'} />
        </span>
      )}

      {streaming && (
        <span
          aria-label="Streamt gerade"
          title="Streamt gerade"
          className={
            'pointer-events-none absolute z-20 flex items-center gap-1 rounded-md bg-rose-600 px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wider text-white shadow ' +
            (pinned ? 'left-9 top-2' : 'left-2 top-2')
          }
        >
          <span className="h-1.5 w-1.5 rounded-full bg-white motion-safe:animate-live-dot" />
          LIVE
        </span>
      )}

      {/* Reserve enough vertical space so the chip's height stays constant
          whether or not the muted/deafened badges are present. Without
          `min-h-5` on the right slot the chip grows by ~4px the moment a
          badge appears, which makes the dock visibly jiggle when somebody
          mutes mid-call. */}
      <div className="pointer-events-none absolute inset-x-2 bottom-2 flex min-h-[32px] items-center justify-between gap-2 rounded-lg glass-chip px-2.5 py-1.5">
        <div className="flex min-w-0 items-center gap-1.5 text-xs font-semibold">
          {isHost && (
            <CrownIcon
              aria-label="Anrufgründer"
              className="h-3 w-3 shrink-0 text-amber-300"
            />
          )}
          <span className="truncate">
            {displayName}
            {me ? ' (du)' : ''}
          </span>
          {e2ee && (
            <span
              aria-label="E2E verschlüsselt"
              title="E2E verschlüsselt"
              className="flex opacity-70"
            >
              <LockIcon className="h-3 w-3" />
            </span>
          )}
        </div>
        <div className="flex min-h-5 shrink-0 items-center gap-1">
          {muted && (
            <span
              aria-label="Mikro stumm"
              title="Mikro stumm"
              className="flex h-5 w-5 items-center justify-center rounded-md bg-rose-500/80 text-white"
            >
              <MicOffIcon className="h-3 w-3" />
            </span>
          )}
          {deafened && (
            <span
              aria-label="Ton aus"
              title="Ton aus"
              className="flex h-5 w-5 items-center justify-center rounded-md bg-rose-500/80 text-white"
            >
              <HeadphonesOffIcon className="h-3 w-3" />
            </span>
          )}
        </div>
      </div>
    </div>
  );
}

/** Discord-style: hidden when excellent/good/unknown, amber when poor,
 *  red with a struck-through Wifi when lost. Anchored top-right above the
 *  speaking-glow so it stays readable on a bright video stream. */
function ConnectionQualityBadge({
  quality,
  small,
}: {
  quality: TileConnectionQuality | undefined;
  small: boolean;
}) {
  if (!quality || quality === 'excellent' || quality === 'good' || quality === 'unknown') {
    return null;
  }
  const isLost = quality === 'lost';
  const Icon = isLost ? WifiOffIcon : WifiLowIcon;
  const label = isLost ? 'Verbindung verloren' : 'Schlechte Verbindung';
  const tone = isLost
    ? 'bg-rose-500/85 text-white'
    : 'bg-amber-400/90 text-amber-950 dark:text-amber-950';
  const sz = small ? 'h-5 w-5' : 'h-6 w-6';
  const iconSz = small ? 'h-3 w-3' : 'h-3.5 w-3.5';
  return (
    <span
      aria-label={label}
      title={label}
      className={
        'pointer-events-none absolute right-2 top-2 z-20 flex items-center justify-center rounded-md ' +
        sz + ' ' + tone
      }
    >
      <Icon className={iconSz} />
    </span>
  );
}

function AudioContent({
  userId,
  displayName,
  avatarUrl,
  small,
}: ParticipantTileProps & { small: boolean }) {
  const key = colorKeyFor(userId);
  const colors = AVATAR_COLORS[key];
  const letter = displayName.trim().charAt(0).toUpperCase() || '?';
  return (
    <div className="flex min-h-0 flex-1 items-center justify-center p-4">
      <div
        className={
          'relative flex items-center justify-center rounded-full ' +
          (small ? 'h-11 w-11' : 'h-[72px] w-[72px]')
        }
      >
        {avatarUrl ? (
          <img
            src={avatarUrl}
            alt=""
            className="relative h-full w-full rounded-full object-cover"
          />
        ) : (
          <span
            className={
              'relative flex h-full w-full items-center justify-center rounded-full font-bold ' +
              colors.bg + ' ' + colors.fg + ' ' +
              (small ? 'text-lg' : 'text-2xl')
            }
          >
            {letter}
          </span>
        )}
      </div>
    </div>
  );
}

function VideoStub({
  userId,
  displayName,
  avatarUrl,
  videoTrack,
  me,
  small,
  fit,
}: ParticipantTileProps & { small: boolean; fit: 'cover' | 'contain' }) {
  const videoRef = useRef<HTMLVideoElement | null>(null);
  useEffect(() => {
    const el = videoRef.current;
    if (!el || !videoTrack) return;
    el.srcObject = new MediaStream([videoTrack]);
    return () => {
      if (el.srcObject) {
        (el.srcObject as MediaStream).getTracks().forEach((t) => {
          // Don't stop the live track — other consumers may still need it.
          // Just detach from this element.
          void t;
        });
        el.srcObject = null;
      }
    };
  }, [videoTrack]);

  if (videoTrack) {
    return (
      <div className="relative flex min-h-0 flex-1 items-center justify-center bg-black">
        <video
          ref={videoRef}
          autoPlay
          playsInline
          muted
          className={
            'h-full w-full ' +
            (fit === 'contain' ? 'object-contain ' : 'object-cover ') +
            (me ? 'scale-x-[-1]' : '') /* mirror local preview */
          }
        />
      </div>
    );
  }

  // Camera flag true but no track yet (publishing / subscribing race).
  const key = colorKeyFor(userId);
  const colors = AVATAR_COLORS[key];
  const letter = displayName.trim().charAt(0).toUpperCase() || '?';
  return (
    <div className="flex min-h-0 flex-1 items-center justify-center bg-gradient-to-br from-accent/20 to-surface-2 p-4">
      <div
        className={
          'flex items-center justify-center rounded-full font-bold ' +
          colors.bg + ' ' + colors.fg + ' ' +
          (small ? 'h-11 w-11 text-lg' : 'h-[72px] w-[72px] text-2xl')
        }
      >
        {avatarUrl ? (
          <img src={avatarUrl} alt="" className="h-full w-full rounded-full object-cover" />
        ) : (
          letter
        )}
      </div>
    </div>
  );
}

