<script setup lang="ts">
// 참여자 1명의 영상 타일 — 카메라 트랙을 <video>, (원격) 마이크 트랙을 <audio> 에 연결한다.
// 로컬 참여자 오디오는 에코 방지를 위해 재생하지 않는다. 카메라가 꺼져 있으면 이름 이니셜 아바타.
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import {
  Track,
  ParticipantEvent,
  ConnectionQuality,
  type Participant,
} from 'livekit-client'
import QualityBars from './QualityBars.vue'
import MeetingIcon from './MeetingIcon.vue'
import { avatarColor, avatarInitial } from '@/shared/utils/avatar'
import { normalizeQuality } from '@/composables/useLiveKitRoom'
import type { MeetingQuality } from '@/types/meeting'

interface Props {
  participant: Participant
  isLocal?: boolean
  // 큰 화면(stage) 렌더 여부
  featured?: boolean
  // 진행자가 이 타일을 승격할 수 있는지(호버 시 "크게" 버튼)
  canPromote?: boolean
  // 말하는 사람 하이라이트 사용 여부
  highlight?: boolean
}
const props = defineProps<Props>()
const emit = defineEmits<{ (e: 'promote', identity: string): void }>()

const videoEl = ref<HTMLVideoElement | null>(null)
const hasVideo = ref(false)
const micOn = ref(false)
const speaking = ref(false)
const quality = ref<MeetingQuality>('good')
const isHost = ref(false)

const displayName = ref(props.participant.name || props.participant.identity)
const avColor = computed(() => avatarColor(displayName.value))
const initial = computed(() => avatarInitial(displayName.value))
const showSpeaking = computed(
  () => props.highlight && speaking.value && micOn.value,
)

// 참여자 metadata 문자열에서 진행자 여부 파싱({"role":"host"})
function parseHost(metadata: string | undefined): boolean {
  if (!metadata) return false
  try {
    return (JSON.parse(metadata) as { role?: string }).role === 'host'
  } catch {
    return false
  }
}

// 현재 트랙/상태를 DOM 과 반응형 값에 반영
function sync() {
  const p = props.participant
  displayName.value = p.name || p.identity
  micOn.value = p.isMicrophoneEnabled
  speaking.value = p.isSpeaking
  quality.value = normalizeQuality(p.connectionQuality as ConnectionQuality)
  isHost.value = parseHost(p.metadata)

  // 오디오는 룸 레벨(useLiveKitRoom)에서 전원 재생하므로 여기서는 영상만 다룬다.
  // 영상 트랙 연결만 담당한다. 수신 화질(스테이지=HIGH/나머지=LOW)은 타일이 아니라
  //   useLiveKitRoom 가 스테이지 변경에 맞춰 중앙에서 제어한다(강등 시 누락·레이스 방지).
  const camPub = p.getTrackPublication(Track.Source.Camera)
  if (camPub?.track && videoEl.value) {
    camPub.track.attach(videoEl.value)
    hasVideo.value = !camPub.isMuted
  } else {
    hasVideo.value = false
  }
}

function onSpeaking() {
  speaking.value = props.participant.isSpeaking
}
function onQuality() {
  quality.value = normalizeQuality(
    props.participant.connectionQuality as ConnectionQuality,
  )
}

onMounted(() => {
  sync()
  const p = props.participant
  p.on(ParticipantEvent.TrackSubscribed, sync)
  p.on(ParticipantEvent.TrackUnsubscribed, sync)
  p.on(ParticipantEvent.LocalTrackPublished, sync)
  p.on(ParticipantEvent.LocalTrackUnpublished, sync)
  p.on(ParticipantEvent.TrackMuted, sync)
  p.on(ParticipantEvent.TrackUnmuted, sync)
  p.on(ParticipantEvent.IsSpeakingChanged, onSpeaking)
  p.on(ParticipantEvent.ConnectionQualityChanged, onQuality)
})

onBeforeUnmount(() => {
  const p = props.participant
  p.off(ParticipantEvent.TrackSubscribed, sync)
  p.off(ParticipantEvent.TrackUnsubscribed, sync)
  p.off(ParticipantEvent.LocalTrackPublished, sync)
  p.off(ParticipantEvent.LocalTrackUnpublished, sync)
  p.off(ParticipantEvent.TrackMuted, sync)
  p.off(ParticipantEvent.TrackUnmuted, sync)
  p.off(ParticipantEvent.IsSpeakingChanged, onSpeaking)
  p.off(ParticipantEvent.ConnectionQualityChanged, onQuality)
  // 타일 언마운트 시 비디오 트랙을 엘리먼트에서 분리(미분리 시 attachedElements 누수)
  const camPub = p.getTrackPublication(Track.Source.Camera)
  if (camPub?.track && videoEl.value) camPub.track.detach(videoEl.value)
})
</script>

<template>
  <div
    class="tile"
    :class="{ featured, speaking: showSpeaking }"
    :style="{ '--av': avColor }"
  >
    <!-- 영상: 로컬은 좌우 반전(거울)으로 표시 -->
    <video
      ref="videoEl"
      class="vid"
      :class="{ mirror: isLocal, hidden: !hasVideo }"
      autoplay
      playsinline
      :muted="isLocal"
    />
    <!-- 카메라 off/미발행 시 이름 이니셜 아바타 -->
    <div
      v-if="!hasVideo"
      class="tile-off"
    >
      <span class="av-lg">{{ initial }}</span>
    </div>

    <!-- 연결 품질 -->
    <div class="tile-q">
      <QualityBars :quality="quality" />
    </div>

    <!-- 진행자 승격 버튼(호버) -->
    <button
      v-if="canPromote && !featured"
      type="button"
      class="tile-promote"
      title="스테이지로 크게 보기"
      @click="emit('promote', participant.identity)"
    >
      <MeetingIcon name="expand" /><span>크게</span>
    </button>

    <!-- 하단 메타 -->
    <div class="tile-meta">
      <span
        class="mmic"
        :class="{ off: !micOn }"
        :title="micOn ? '마이크 켜짐' : '마이크 꺼짐'"
      >
        <MeetingIcon :name="micOn ? 'mic' : 'micOff'" />
      </span>
      <span class="mname">{{ displayName }}{{ isLocal ? ' (나)' : '' }}</span>
      <span
        v-if="isHost"
        class="mhost"
        title="진행자"
      >
        <MeetingIcon name="host" />
      </span>
    </div>
  </div>
</template>

<style scoped>
.tile {
  position: relative;
  aspect-ratio: 16 / 9;
  border-radius: 0.75rem;
  overflow: hidden;
  background: var(--tile);
  border: 0.125rem solid transparent;
}
.tile.featured {
  aspect-ratio: auto;
  height: 100%;
  border-radius: 0.875rem;
}
.tile.speaking {
  border-color: var(--green);
  box-shadow: 0 0 0 1px var(--green), 0 0 22px -4px rgba(31, 157, 87, 0.7);
}
.vid {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.vid.mirror {
  transform: scaleX(-1);
}
.vid.hidden {
  display: none;
}
.tile-off {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: var(--tile);
}
.av-lg {
  width: clamp(2.875rem, 9vh, 5.75rem);
  height: clamp(2.875rem, 9vh, 5.75rem);
  border-radius: 50%;
  background: var(--av, #555);
  color: #fff;
  display: grid;
  place-items: center;
  font-size: clamp(1.25rem, 4vh, 2.375rem);
  font-weight: 700;
  text-transform: uppercase;
}
.tile-q {
  position: absolute;
  top: 0.5625rem;
  left: 0.5625rem;
  z-index: 3;
}
.tile-meta {
  position: absolute;
  left: 0.5625rem;
  bottom: 0.5625rem;
  right: 0.5625rem;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 0.375rem;
}
.tile.featured .tile-meta {
  left: 0.875rem;
  bottom: 0.875rem;
}
.mmic {
  width: 1.375rem;
  height: 1.375rem;
  border-radius: 0.375rem;
  background: rgba(0, 0, 0, 0.6);
  color: #fff;
  display: grid;
  place-items: center;
  flex-shrink: 0;
}
.mmic svg {
  width: 0.8125rem;
  height: 0.8125rem;
}
.mmic.off {
  color: var(--d-red);
}
.mname {
  font-size: 0.78125rem;
  font-weight: 600;
  color: #fff;
  background: rgba(0, 0, 0, 0.55);
  padding: 0.1875rem 0.5625rem;
  border-radius: 0.375rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tile.featured .mname {
  font-size: 0.875rem;
  padding: 0.3125rem 0.75rem;
}
.mhost {
  width: 1.375rem;
  height: 1.375rem;
  border-radius: 0.375rem;
  background: rgba(124, 92, 240, 0.85);
  color: #fff;
  display: grid;
  place-items: center;
  flex-shrink: 0;
}
.mhost svg {
  width: 0.75rem;
  height: 0.75rem;
}
.tile-promote {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  z-index: 4;
  display: inline-flex;
  align-items: center;
  gap: 0.3125rem;
  height: 1.75rem;
  padding: 0 0.625rem;
  border: none;
  border-radius: 0.4375rem;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  font-family: inherit;
  font-size: 0.75rem;
  font-weight: 600;
  cursor: pointer;
  opacity: 0;
  transform: translateY(-0.125rem);
  transition:
    opacity 0.12s,
    transform 0.12s;
}
.tile-promote svg {
  width: 0.8125rem;
  height: 0.8125rem;
}
.tile:hover .tile-promote {
  opacity: 1;
  transform: none;
}
.tile-promote:hover {
  background: var(--accent);
}
</style>
