<script setup>
import { ConnectionState, Room, RoomEvent, Track } from "livekit-client"

const toast = useToast()
const { $api } = useNuxtApp()
const route = useRoute()

const status = ref(null)
const identity = ref(null)
const matrixRooms = ref([])
const matrixProjectRooms = ref([])
const matrixDirectRooms = ref([])
const unreadRooms = ref({})
const activeRoomKey = ref(typeof route.query.room === "string" ? route.query.room : "allgemein")
const matrixMessages = ref([])
const matrixMembers = ref([])
const matrixTenantUsers = ref([])
const matrixMessageDraft = ref("")
const matrixMessagesViewport = ref(null)
const matrixAttachmentInput = ref(null)
const matrixAttachmentObjectUrls = ref({})
const matrixReplyTarget = ref(null)
const matrixEditingMessage = ref(null)
const matrixDragActive = ref(false)
const memberInviteOpen = ref(false)
const memberInviteUserId = ref("")
const matrixSearchQuery = ref("")
const matrixSearchResults = ref([])
const roomCreateOpen = ref(false)
const collapsedRoomGroups = ref({})
const matrixCallOpen = ref(false)
const matrixCallMode = ref("video")
const matrixCallLoading = ref(false)
const matrixCallConnected = ref(false)
const matrixCallState = ref("disconnected")
const matrixCallError = ref("")
const matrixCallSession = ref(null)
const matrixCallTiles = ref([])
const matrixCallMicEnabled = ref(true)
const matrixCallCameraEnabled = ref(true)
const matrixCallAudioContainer = ref(null)
const matrixCallScreenShareEnabled = ref(false)
const matrixCallStartedAt = ref(null)
const matrixCallDurationNow = ref(Date.now())
const matrixCallAnnouncementSent = ref(false)
const roomCreateForm = ref({
  name: "",
  key: "",
  topic: ""
})
const loading = ref(false)
const roomProvisioning = ref(false)
const roomProvisioningKey = ref("")
const roomCreating = ref(false)
const roomMembersSyncing = ref(false)
const matrixMessagesLoading = ref(false)
const matrixMembersLoading = ref(false)
const memberInviting = ref(false)
const memberRemovingId = ref("")
const matrixSearchLoading = ref(false)
const matrixMessageSending = ref(false)
const matrixAttachmentUploading = ref(false)
const matrixAttachmentUploadCount = ref(0)
const matrixAutoRefreshActive = ref(false)
const lastUpdated = ref(null)
let matrixCallDurationInterval = null
let matrixMessagesRequestActive = false
let matrixMembersRequestActive = false
let matrixLiveKitRoom = null
let matrixLiveSyncRunId = 0
const matrixCallVideoElements = new Map()
const matrixAttachmentPreviewRequests = new Set()
const matrixSyncSince = ref("")

const canUseMatrixChat = computed(() =>
  Boolean(status.value?.reachable && status.value?.provisioningConfigured)
)

const activeRoom = computed(() =>
  rooms.value.find((room) => room.key === activeRoomKey.value) || {
    key: activeRoomKey.value,
    name: activeRoomKey.value,
    description: "Mandantenweiter Austausch",
    exists: false,
    roomId: "",
    unread: 0
  }
)

const activeRoomEndpoint = computed(() =>
  `/api/communication/matrix/rooms/${encodeURIComponent(activeRoomKey.value)}`
)

const activeRoomMatrixAddress = computed(() =>
  activeRoom.value?.roomId || activeRoom.value?.alias || ""
)

const canStartMatrixCall = computed(() =>
  Boolean(canUseMatrixChat.value && activeRoom.value?.exists)
)

const canSendChatInput = computed(() =>
  Boolean(canUseMatrixChat.value && activeRoom.value?.exists && !matrixMessageSending.value && !matrixAttachmentUploading.value)
)

const availableRoomInviteUsers = computed(() => {
  const roomMemberIds = new Set(matrixMembers.value.map((member) => member.matrixUserId))

  return matrixTenantUsers.value
    .filter((user) => !roomMemberIds.has(user.matrixUserId))
    .sort((first, second) =>
      String(first.displayName || first.email || "").localeCompare(
        String(second.displayName || second.email || ""),
        "de",
        { sensitivity: "base" }
      )
    )
})

const matrixCallTitle = computed(() =>
  matrixCallMode.value === "audio" ? "Audioanruf" : "Videokonferenz"
)

const matrixCallStateLabel = computed(() => {
  const labels = {
    connected: "Verbunden",
    connecting: "Verbindet",
    reconnecting: "Verbindung wird wiederhergestellt",
    disconnected: "Getrennt"
  }

  return labels[String(matrixCallState.value).toLowerCase()] || matrixCallState.value
})

const matrixCallParticipantCount = computed(() =>
  new Set(matrixCallTiles.value.map((tile) => tile.identity)).size
)

const matrixCallDurationLabel = computed(() => {
  if (!matrixCallStartedAt.value) return "00:00"

  const durationSeconds = Math.max(0, Math.floor((matrixCallDurationNow.value - matrixCallStartedAt.value) / 1000))
  const minutes = String(Math.floor(durationSeconds / 60)).padStart(2, "0")
  const seconds = String(durationSeconds % 60).padStart(2, "0")

  return `${minutes}:${seconds}`
})

const matrixCallPrimaryTile = computed(() =>
  matrixCallTiles.value.find((tile) => tile.screenSharing) ||
  matrixCallTiles.value.find((tile) => !tile.local && tile.videoTrack) ||
  matrixCallTiles.value.find((tile) => tile.videoTrack) ||
  matrixCallTiles.value[0] ||
  null
)

const matrixCallSecondaryTiles = computed(() =>
  matrixCallTiles.value.filter((tile) => tile.id !== matrixCallPrimaryTile.value?.id)
)

const roomCreateKeyPreview = computed(() =>
  normalizeRoomKey(roomCreateForm.value.key || roomCreateForm.value.name)
)

const sortRoomsByName = (roomList) => [...roomList].sort((first, second) =>
  String(first.name || "").localeCompare(String(second.name || ""), "de", { sensitivity: "base" })
)

const rooms = computed(() => {
  const baseRooms = matrixRooms.value
    .filter((room) => !["project", "direct"].includes(room.type))
    .map((room) => ({
      ...room,
      group: "Räume",
      icon: "i-heroicons-chat-bubble-left-right",
      unread: unreadRooms.value[room.key]?.count || 0,
      mentions: unreadRooms.value[room.key]?.mentions || 0,
      description: room.alias || room.roomId || "Mandantenweiter Austausch"
    }))

  const projectRooms = matrixProjectRooms.value.map((room) => ({
    ...room,
    group: "Projekte",
    icon: "i-heroicons-briefcase",
    unread: unreadRooms.value[room.key]?.count || 0,
    mentions: unreadRooms.value[room.key]?.mentions || 0,
    description: room.projectNumber || room.topic || "Projektkommunikation",
    provisionEndpoint: `/api/communication/matrix/project-rooms/${encodeURIComponent(room.projectId)}/provision`
  }))

  const directRooms = matrixDirectRooms.value.map((room) => ({
    ...room,
    group: "Direkt",
    icon: "i-heroicons-user-circle",
    unread: unreadRooms.value[room.key]?.count || 0,
    mentions: unreadRooms.value[room.key]?.mentions || 0,
    description: room.email || room.topic || "Direktnachricht",
    provisionEndpoint: `/api/communication/matrix/direct-rooms/${encodeURIComponent(room.userId)}/provision`
  }))

  return [
    ...sortRoomsByName(baseRooms),
    ...sortRoomsByName(projectRooms),
    ...sortRoomsByName(directRooms)
  ]
})

const groupedRooms = computed(() => [
  {
    key: "rooms",
    label: "Räume",
    rooms: rooms.value.filter((room) => room.group === "Räume")
  },
  {
    key: "projects",
    label: "Projekte",
    rooms: rooms.value.filter((room) => room.group === "Projekte")
  },
  {
    key: "direct",
    label: "Direktnachrichten",
    rooms: rooms.value.filter((room) => room.group === "Direkt")
  }
].filter((group) => group.rooms.length > 0))

const isRoomGroupCollapsed = (groupKey) => Boolean(collapsedRoomGroups.value[groupKey])

const toggleRoomGroup = (groupKey) => {
  collapsedRoomGroups.value = {
    ...collapsedRoomGroups.value,
    [groupKey]: !isRoomGroupCollapsed(groupKey)
  }
}

const normalizeRoomKey = (value) => {
  const normalized = String(value || "")
    .toLowerCase()
    .normalize("NFKD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/ä/g, "a")
    .replace(/ö/g, "o")
    .replace(/ü/g, "u")
    .replace(/ß/g, "ss")
    .replace(/[^a-z0-9._=-]+/g, "_")
    .replace(/_+/g, "_")
    .replace(/^[._=-]+|[._=-]+$/g, "")

  return normalized || "raum"
}

const setActiveRoom = async (room) => {
  if (room.disabled || room.key === activeRoomKey.value) return

  if (!room.exists && room.provisionEndpoint) {
    await provisionRoomFromList(room)
    return
  }

  activeRoomKey.value = room.key
  matrixMessages.value = []
  matrixMembers.value = []
  matrixSearchResults.value = []
  memberInviteOpen.value = false
  await loadRoomChat({ includeMembers: true })
  restartMatrixLiveSync()
}

const mergeRoomIntoLists = (room) => {
  upsertRoom({ ...room, exists: true })

  if (room.type === "project") {
    matrixProjectRooms.value = matrixProjectRooms.value.map((item) =>
      item.key === room.key ? { ...item, ...room, exists: true } : item
    )
  }

  if (room.type === "direct") {
    matrixDirectRooms.value = matrixDirectRooms.value.map((item) =>
      item.key === room.key ? { ...item, ...room, exists: true } : item
    )
  }
}

const provisionRoomFromList = async (room) => {
  roomProvisioningKey.value = room.key
  try {
    const createdRoom = await $api(room.provisionEndpoint, {
      method: "POST"
    })

    mergeRoomIntoLists(createdRoom)
    activeRoomKey.value = createdRoom.key
    matrixMessages.value = []
    matrixMembers.value = []
    matrixSearchResults.value = []
    memberInviteOpen.value = false

    toast.add({
      title: "Chatraum ist bereit",
      color: "success"
    })

    await loadRoomChat({ includeMembers: true })
    restartMatrixLiveSync()
  } catch (error) {
    toast.add({
      title: "Chatraum konnte nicht erstellt werden",
      color: "error"
    })
  } finally {
    roomProvisioningKey.value = ""
  }
}

const upsertRoom = (room) => {
  const roomWasKnown = matrixRooms.value.some((item) => item.key === room.key)
  matrixRooms.value = roomWasKnown
    ? matrixRooms.value.map((item) => item.key === room.key ? { ...item, ...room } : item)
    : [...matrixRooms.value, room]
}

const mergeMatrixMessages = (incomingMessages) => {
  const byId = new Map()

  for (const message of matrixMessages.value) {
    byId.set(message.id, message)
  }

  for (const message of incomingMessages || []) {
    const member = matrixMembers.value.find((item) => item.matrixUserId === message.sender)
    byId.set(message.id, {
      ...byId.get(message.id),
      ...message,
      senderDisplayName: member?.displayName || message.senderDisplayName,
      pending: false,
      failed: false
    })
  }

  matrixMessages.value = Array.from(byId.values())
    .sort((a, b) => (a.timestamp || 0) - (b.timestamp || 0))
  loadAttachmentPreviews()
}

const applyMatrixReplacements = (replacements) => {
  if (!replacements?.length) return

  const replacementByTarget = new Map()
  for (const replacement of replacements) {
    if (!replacement.targetEventId) continue
    const previous = replacementByTarget.get(replacement.targetEventId)
    if (!previous || (replacement.timestamp || 0) >= (previous.timestamp || 0)) {
      replacementByTarget.set(replacement.targetEventId, replacement)
    }
  }

  matrixMessages.value = matrixMessages.value.map((message) => {
    const replacement = replacementByTarget.get(message.id)
    if (!replacement) return message

    return {
      ...message,
      body: replacement.body,
      edited: true,
      timestamp: replacement.timestamp || message.timestamp
    }
  })
}

const applyMatrixReactions = (reactions) => {
  if (!reactions?.length) return

  matrixMessages.value = matrixMessages.value.map((message) => {
    const messageReactions = reactions.filter((reaction) => reaction.targetEventId === message.id)
    if (!messageReactions.length) return message

    let nextReactions = [...(message.reactions || [])]
    for (const reaction of messageReactions) {
      const current = nextReactions.find((item) => item.key === reaction.key)
      nextReactions = current
        ? nextReactions.map((item) => item.key === reaction.key ? {
          ...item,
          count: item.count + 1,
          own: item.own || reaction.own
        } : item)
        : [...nextReactions, { key: reaction.key, count: 1, own: reaction.own }]
    }

    return {
      ...message,
      reactions: nextReactions
    }
  })
}

const applyMatrixRedactions = (redactions) => {
  const redactedIds = new Set((redactions || []).map((redaction) => redaction.targetEventId).filter(Boolean))
  if (!redactedIds.size) return

  matrixMessages.value = matrixMessages.value.filter((message) => !redactedIds.has(message.id))
}

const findMessage = (messageId) =>
  matrixMessages.value.find((message) => message.id === messageId)

const replyPreview = (message) => {
  if (!message) return ""
  return message.body || message.attachment?.fileName || "Nachricht"
}

const participantInitial = (tile) =>
  (tile?.name || tile?.identity || "?").slice(0, 1).toUpperCase()

const setReplyTarget = (message) => {
  matrixEditingMessage.value = null
  matrixReplyTarget.value = {
    id: message.id,
    senderDisplayName: message.own ? "Du" : message.senderDisplayName || message.sender,
    body: replyPreview(message)
  }
}

const clearReplyTarget = () => {
  matrixReplyTarget.value = null
}

const beginEditMessage = (message) => {
  if (!message?.id || !message.own || message.attachment) return

  matrixEditingMessage.value = {
    id: message.id,
    body: message.body || ""
  }
  matrixMessageDraft.value = message.body || ""
  clearReplyTarget()
}

const cancelEditMessage = () => {
  matrixEditingMessage.value = null
  matrixMessageDraft.value = ""
}

const scrollMessagesToBottom = async () => {
  await nextTick()
  if (!matrixMessagesViewport.value) return

  matrixMessagesViewport.value.scrollTop = matrixMessagesViewport.value.scrollHeight
}

const loadUnreadCounts = async () => {
  if (!canUseMatrixChat.value) return

  try {
    const res = await $api("/api/communication/matrix/unread")
    unreadRooms.value = res.rooms || {}
  } catch (error) {
    unreadRooms.value = {}
  }
}

const markActiveRoomRead = async () => {
  if (!canUseMatrixChat.value || !activeRoom.value?.exists) return

  try {
    const latestMessage = matrixMessages.value.at(-1)
    await $api(`${activeRoomEndpoint.value}/read`, {
      method: "POST",
      body: {
        eventId: latestMessage?.id
      }
    })
    unreadRooms.value = {
      ...unreadRooms.value,
      [activeRoomKey.value]: { count: 0, mentions: 0 }
    }
  } catch (error) {
    // Lesestatus ist Komfortfunktion; Chat selbst soll dadurch nicht blockieren.
  }
}

const loadChatInfo = async () => {
  loading.value = true
  try {
    const [statusRes, identityRes, roomsRes, projectRoomsRes, directRoomsRes, usersRes] = await Promise.all([
      $api("/api/communication/matrix/status"),
      $api("/api/communication/matrix/me"),
      $api("/api/communication/matrix/rooms"),
      $api("/api/communication/matrix/project-rooms"),
      $api("/api/communication/matrix/direct-rooms"),
      $api("/api/communication/matrix/users")
    ])

    status.value = statusRes
    identity.value = identityRes
    matrixRooms.value = roomsRes.rooms || []
    matrixProjectRooms.value = projectRoomsRes.rooms || []
    matrixDirectRooms.value = directRoomsRes.rooms || []
    matrixTenantUsers.value = usersRes.users || []
    lastUpdated.value = new Date()
    await loadUnreadCounts()

    if (activeRoom.value?.exists && canUseMatrixChat.value) {
      await loadRoomChat({ silent: true, includeMembers: true })
    }
  } catch (error) {
    toast.add({
      title: "Chat konnte nicht geladen werden",
      color: "error"
    })
  } finally {
    loading.value = false
  }
}

const provisionActiveRoom = async () => {
  roomProvisioning.value = true
  try {
    const res = await $api(`${activeRoomEndpoint.value}/provision`, {
      method: "POST"
    })

    upsertRoom({ ...res, exists: true })

    toast.add({
      title: res.alreadyExisted ? "Chatraum ist bereit" : "Chatraum erstellt",
      color: "success"
    })

    await loadRoomChat({ includeMembers: true })
  } catch (error) {
    toast.add({
      title: "Chatraum konnte nicht erstellt werden",
      color: "error"
    })
  } finally {
    roomProvisioning.value = false
  }
}

const createRoom = async () => {
  const name = roomCreateForm.value.name.trim()
  const key = roomCreateKeyPreview.value
  const topic = roomCreateForm.value.topic.trim()

  if (!name) {
    toast.add({
      title: "Bitte gib einen Raumnamen ein",
      color: "warning"
    })
    return
  }

  roomCreating.value = true
  try {
    const room = await $api("/api/communication/matrix/rooms", {
      method: "POST",
      body: {
        key,
        name,
        topic: topic || undefined,
        type: "room"
      }
    })

    upsertRoom({ ...room, exists: true })
    activeRoomKey.value = room.key
    matrixMessages.value = []
    matrixMembers.value = []
    matrixSearchResults.value = []
    memberInviteOpen.value = false
    roomCreateForm.value = {
      name: "",
      key: "",
      topic: ""
    }
    roomCreateOpen.value = false

    toast.add({
      title: room.alreadyExisted ? "Chatraum ist bereit" : "Chatraum erstellt",
      color: "success"
    })

    await loadRoomChat({ includeMembers: true })
    restartMatrixLiveSync()
  } catch (error) {
    toast.add({
      title: "Chatraum konnte nicht erstellt werden",
      color: "error"
    })
  } finally {
    roomCreating.value = false
  }
}

const loadRoomMessages = async ({ silent = false } = {}) => {
  if (!canUseMatrixChat.value || !activeRoom.value?.exists) return
  if (matrixMessagesRequestActive) return

  matrixMessagesRequestActive = true
  if (!silent) matrixMessagesLoading.value = true

  try {
    const res = await $api(`${activeRoomEndpoint.value}/messages`)
    mergeMatrixMessages(res.messages || [])
    await markActiveRoomRead()
    matrixRooms.value = matrixRooms.value.map((room) => room.key === activeRoomKey.value ? {
      ...room,
      alias: res.alias || room.alias,
      exists: true,
      roomId: res.roomId || room.roomId
    } : room)
    await scrollMessagesToBottom()
  } catch (error) {
    if (!silent) {
      toast.add({
        title: "Nachrichten konnten nicht geladen werden",
        color: "error"
      })
    }
  } finally {
    matrixMessagesRequestActive = false
    if (!silent) matrixMessagesLoading.value = false
  }
}

const loadRoomMembers = async ({ silent = false } = {}) => {
  if (!canUseMatrixChat.value || !activeRoom.value?.exists) return
  if (matrixMembersRequestActive) return

  matrixMembersRequestActive = true
  if (!silent) matrixMembersLoading.value = true

  try {
    const res = await $api(`${activeRoomEndpoint.value}/members`)
    matrixMembers.value = res.members || []
  } catch (error) {
    if (!silent) {
      toast.add({
        title: "Teilnehmer konnten nicht geladen werden",
        color: "error"
      })
    }
  } finally {
    matrixMembersRequestActive = false
    if (!silent) matrixMembersLoading.value = false
  }
}

const syncRoomMembers = async () => {
  if (!canUseMatrixChat.value || !activeRoom.value?.exists) return

  roomMembersSyncing.value = true
  try {
    const res = await $api(`${activeRoomEndpoint.value}/members/sync`, {
      method: "POST"
    })

    toast.add({
      title: "Teilnehmer synchronisiert",
      description: `${res.joined || 0} synchronisiert, ${res.failed || 0} fehlgeschlagen`,
      color: res.failed ? "warning" : "success"
    })

    await loadRoomMembers()
  } catch (error) {
    toast.add({
      title: "Teilnehmer konnten nicht synchronisiert werden",
      color: "error"
    })
  } finally {
    roomMembersSyncing.value = false
  }
}

const toggleMemberInvite = () => {
  memberInviteOpen.value = !memberInviteOpen.value
  if (memberInviteOpen.value && !memberInviteUserId.value && availableRoomInviteUsers.value[0]) {
    memberInviteUserId.value = availableRoomInviteUsers.value[0].userId
  }
}

const inviteRoomMember = async () => {
  if (!memberInviteUserId.value || !canUseMatrixChat.value || !activeRoom.value?.exists) return

  memberInviting.value = true
  try {
    const result = await $api(`${activeRoomEndpoint.value}/members/invite`, {
      method: "POST",
      body: {
        userId: memberInviteUserId.value
      }
    })

    toast.add({
      title: "Teilnehmer eingeladen",
      description: result.displayName || result.email || result.matrixUserId,
      color: "success"
    })

    memberInviteUserId.value = ""
    memberInviteOpen.value = false
    await loadRoomMembers()
  } catch (error) {
    toast.add({
      title: "Teilnehmer konnte nicht eingeladen werden",
      color: "error"
    })
  } finally {
    memberInviting.value = false
  }
}

const removeRoomMember = async (member) => {
  if (!member?.matrixUserId || member.own || !canUseMatrixChat.value || !activeRoom.value?.exists) return

  memberRemovingId.value = member.matrixUserId
  try {
    await $api(`${activeRoomEndpoint.value}/members/${encodeURIComponent(member.matrixUserId)}`, {
      method: "DELETE"
    })

    matrixMembers.value = matrixMembers.value.filter((item) => item.matrixUserId !== member.matrixUserId)
    toast.add({
      title: "Teilnehmer entfernt",
      color: "success"
    })
  } catch (error) {
    toast.add({
      title: "Teilnehmer konnte nicht entfernt werden",
      color: "error"
    })
  } finally {
    memberRemovingId.value = ""
  }
}

const searchRoomMessages = async () => {
  const query = matrixSearchQuery.value.trim()
  if (query.length < 2 || !canUseMatrixChat.value || !activeRoom.value?.exists) {
    matrixSearchResults.value = []
    return
  }

  matrixSearchLoading.value = true
  try {
    const res = await $api(`${activeRoomEndpoint.value}/search?q=${encodeURIComponent(query)}`)
    matrixSearchResults.value = res.results || []
  } catch (error) {
    toast.add({
      title: "Suche konnte nicht ausgeführt werden",
      color: "error"
    })
  } finally {
    matrixSearchLoading.value = false
  }
}

const openMatrixCall = async (mode = "video") => {
  if (matrixLiveKitRoom && matrixCallConnected.value) {
    matrixCallOpen.value = true
    return
  }

  if (!canStartMatrixCall.value) {
    toast.add({
      title: "Besprechung kann noch nicht gestartet werden",
      color: "warning"
    })
    return
  }

  matrixCallMode.value = mode
  matrixCallLoading.value = true
  matrixCallError.value = ""
  matrixCallOpen.value = true
  matrixCallAnnouncementSent.value = false

  try {
    await leaveMatrixCall()
    const session = await $api(`${activeRoomEndpoint.value}/call-session`, {
      method: "POST",
      body: { mode }
    })
    matrixCallSession.value = session
    await connectMatrixCall(session, { video: mode === "video" })
    await announceMatrixCallStarted(mode)
  } catch (error) {
    matrixCallError.value = error?.data?.error || error?.message || "Verbindung fehlgeschlagen"
    toast.add({
      title: "Besprechung konnte nicht gestartet werden",
      description: matrixCallError.value,
      color: "error"
    })
  } finally {
    matrixCallLoading.value = false
  }
}

const getParticipantTiles = (participant, local = false) => {
  const publications = participant.getTrackPublications?.() || []
  const screenSharePublication = publications.find((publication) =>
    publication.kind === Track.Kind.Video &&
    publication.source === Track.Source.ScreenShare &&
    publication.track
  )
  const cameraPublication = publications.find((publication) =>
    publication.kind === Track.Kind.Video &&
    publication.source !== Track.Source.ScreenShare &&
    publication.track
  )
  const audioPublication = publications.find((publication) =>
    publication.kind === Track.Kind.Audio && publication.track
  )
  const participantId = local ? "local" : participant.sid || participant.identity
  const name = local ? "Du" : participant.name || participant.identity
  const participantTile = {
    id: `${participantId}-camera`,
    identity: participant.identity,
    name,
    local,
    speaking: participant.isSpeaking,
    screenSharing: false,
    videoTrack: cameraPublication?.track || null,
    audioTrack: audioPublication?.track || null,
    cameraEnabled: participant.isCameraEnabled,
    microphoneEnabled: participant.isMicrophoneEnabled,
  }

  if (!screenSharePublication?.track) {
    return [participantTile]
  }

  return [
    {
      id: `${participantId}-screen`,
      identity: participant.identity,
      name,
      local,
      speaking: false,
      screenSharing: true,
      videoTrack: screenSharePublication.track,
      audioTrack: null,
      cameraEnabled: participant.isCameraEnabled,
      microphoneEnabled: participant.isMicrophoneEnabled,
    },
    participantTile
  ]
}

const attachMatrixCallMedia = async () => {
  await nextTick()

  for (const tile of matrixCallTiles.value) {
    const videoElement = matrixCallVideoElements.get(tile.id)

    if (videoElement && tile.videoTrack) {
      tile.videoTrack.attach(videoElement)
    }

    if (!tile.local && tile.audioTrack && matrixCallAudioContainer.value) {
      const audioElement = tile.audioTrack.attach()
      audioElement.autoplay = true
      audioElement.dataset.participant = tile.id
      matrixCallAudioContainer.value.appendChild(audioElement)
    }
  }
}

const refreshMatrixCallTiles = async () => {
  if (!matrixLiveKitRoom) {
    matrixCallTiles.value = []
    return
  }

  matrixCallAudioContainer.value?.replaceChildren()
  matrixCallTiles.value = [
    ...getParticipantTiles(matrixLiveKitRoom.localParticipant, true),
    ...Array.from(matrixLiveKitRoom.remoteParticipants.values()).flatMap((participant) =>
      getParticipantTiles(participant)
    )
  ].sort((a, b) => Number(b.screenSharing) - Number(a.screenSharing))

  await attachMatrixCallMedia()
}

const startMatrixCallDurationTimer = () => {
  matrixCallStartedAt.value = Date.now()
  matrixCallDurationNow.value = Date.now()

  if (matrixCallDurationInterval) {
    window.clearInterval(matrixCallDurationInterval)
  }

  matrixCallDurationInterval = window.setInterval(() => {
    matrixCallDurationNow.value = Date.now()
  }, 1000)
}

const stopMatrixCallDurationTimer = () => {
  if (matrixCallDurationInterval) {
    window.clearInterval(matrixCallDurationInterval)
    matrixCallDurationInterval = null
  }

  matrixCallStartedAt.value = null
  matrixCallDurationNow.value = Date.now()
}

const announceMatrixCallStarted = async (mode) => {
  if (matrixCallAnnouncementSent.value || !activeRoom.value?.exists) return

  matrixCallAnnouncementSent.value = true

  try {
    const message = await $api(`${activeRoomEndpoint.value}/messages`, {
      method: "POST",
      body: {
        text: `${mode === "audio" ? "Audioanruf" : "Videokonferenz"} gestartet.`
      }
    })

    mergeMatrixMessages([message])
    await scrollMessagesToBottom()
  } catch (error) {
    matrixCallAnnouncementSent.value = false
  }
}

const connectMatrixCall = async (session, { video = true } = {}) => {
  const room = new Room({
    adaptiveStream: true,
    dynacast: true,
  })

  matrixLiveKitRoom = room
  matrixCallState.value = ConnectionState.Connecting

  const refreshEvents = [
    RoomEvent.TrackSubscribed,
    RoomEvent.TrackUnsubscribed,
    RoomEvent.LocalTrackPublished,
    RoomEvent.LocalTrackUnpublished,
    RoomEvent.ParticipantConnected,
    RoomEvent.ParticipantDisconnected,
    RoomEvent.TrackMuted,
    RoomEvent.TrackUnmuted,
    RoomEvent.ActiveSpeakersChanged,
    RoomEvent.LocalTrackSubscribed,
  ]

  for (const eventName of refreshEvents) {
    room.on(eventName, refreshMatrixCallTiles)
  }

  room.on(RoomEvent.ConnectionStateChanged, (state) => {
    matrixCallState.value = state
    matrixCallConnected.value = state === ConnectionState.Connected
    if (state === ConnectionState.Connected && !matrixCallStartedAt.value) {
      startMatrixCallDurationTimer()
    }
  })

  room.on(RoomEvent.Disconnected, () => {
    matrixCallConnected.value = false
    matrixCallState.value = ConnectionState.Disconnected
    matrixCallScreenShareEnabled.value = false
    stopMatrixCallDurationTimer()
  })

  await room.connect(session.liveKitUrl, session.liveKitToken)
  await room.localParticipant.setMicrophoneEnabled(true)
  await room.localParticipant.setCameraEnabled(video)
  matrixCallMicEnabled.value = true
  matrixCallCameraEnabled.value = video
  matrixCallScreenShareEnabled.value = false
  if (!matrixCallStartedAt.value) {
    startMatrixCallDurationTimer()
  }
  await refreshMatrixCallTiles()
}

const leaveMatrixCall = async () => {
  if (matrixLiveKitRoom) {
    matrixLiveKitRoom.disconnect()
    matrixLiveKitRoom = null
  }

  matrixCallVideoElements.clear()
  matrixCallAudioContainer.value?.replaceChildren()
  matrixCallTiles.value = []
  matrixCallConnected.value = false
  matrixCallState.value = "disconnected"
  matrixCallScreenShareEnabled.value = false
  matrixCallAnnouncementSent.value = false
  stopMatrixCallDurationTimer()
}

const closeMatrixCall = async () => {
  await leaveMatrixCall()
  matrixCallOpen.value = false
}

const minimizeMatrixCall = () => {
  matrixCallOpen.value = false
}

const setMatrixCallVideoElement = (tileId, element) => {
  if (element) {
    matrixCallVideoElements.set(tileId, element)
    const tile = matrixCallTiles.value.find((item) => item.id === tileId)
    tile?.videoTrack?.attach(element)
  } else {
    matrixCallVideoElements.delete(tileId)
  }
}

const toggleMatrixCallMic = async () => {
  if (!matrixLiveKitRoom) return

  const enabled = !matrixCallMicEnabled.value
  await matrixLiveKitRoom.localParticipant.setMicrophoneEnabled(enabled)
  matrixCallMicEnabled.value = enabled
  await refreshMatrixCallTiles()
}

const toggleMatrixCallCamera = async () => {
  if (!matrixLiveKitRoom) return

  const enabled = !matrixCallCameraEnabled.value
  await matrixLiveKitRoom.localParticipant.setCameraEnabled(enabled)
  matrixCallCameraEnabled.value = enabled
  await refreshMatrixCallTiles()
}

const toggleMatrixCallScreenShare = async () => {
  if (!matrixLiveKitRoom) return

  const enabled = !matrixCallScreenShareEnabled.value
  try {
    await matrixLiveKitRoom.localParticipant.setScreenShareEnabled(enabled)
    matrixCallScreenShareEnabled.value = enabled
    await refreshMatrixCallTiles()
  } catch (error) {
    toast.add({
      title: "Bildschirmfreigabe konnte nicht geändert werden",
      color: "error"
    })
  }
}

const loadRoomChat = async ({ silent = false, includeMembers = false } = {}) => {
  await loadRoomMessages({ silent })

  if (includeMembers) {
    await loadRoomMembers({ silent })
  }
}

const sendMatrixMessage = async () => {
  const text = matrixMessageDraft.value.trim()
  if (!text || !canUseMatrixChat.value || !activeRoom.value?.exists) return

  if (matrixEditingMessage.value?.id) {
    const editingId = matrixEditingMessage.value.id
    matrixMessageSending.value = true
    try {
      const replacement = await $api(`${activeRoomEndpoint.value}/messages/${encodeURIComponent(editingId)}`, {
        method: "PUT",
        body: { text }
      })

      applyMatrixReplacements([replacement])
      matrixMessageDraft.value = ""
      matrixEditingMessage.value = null
    } catch (error) {
      toast.add({
        title: "Nachricht konnte nicht bearbeitet werden",
        color: "error"
      })
    } finally {
      matrixMessageSending.value = false
    }
    return
  }

  const optimisticId = `pending-${Date.now()}`
  const optimisticMessage = {
    id: optimisticId,
    sender: identity.value?.matrixUserId || "Du",
    senderDisplayName: identity.value?.displayName || "Du",
    body: text,
    replyToEventId: matrixReplyTarget.value?.id || null,
    timestamp: Date.now(),
    own: true,
    pending: true,
    failed: false
  }

  matrixMessages.value = [
    ...matrixMessages.value,
    optimisticMessage
  ]
  matrixMessageDraft.value = ""
  await scrollMessagesToBottom()

  matrixMessageSending.value = true
  try {
    const message = await $api(`${activeRoomEndpoint.value}/messages`, {
      method: "POST",
      body: {
        text,
        replyToEventId: matrixReplyTarget.value?.id
      }
    })

    matrixMessages.value = matrixMessages.value.map((item) =>
      item.id === optimisticId ? message : item
    )
    clearReplyTarget()
    loadAttachmentPreviews()
  } catch (error) {
    matrixMessages.value = matrixMessages.value.map((item) =>
      item.id === optimisticId ? { ...item, pending: false, failed: true } : item
    )
    toast.add({
      title: "Nachricht konnte nicht gesendet werden",
      color: "error"
    })
  } finally {
    matrixMessageSending.value = false
  }
}

const openAttachmentPicker = () => {
  if (!canUseMatrixChat.value || !activeRoom.value?.exists || matrixAttachmentUploading.value) return
  matrixAttachmentInput.value?.click()
}

const uploadSingleMatrixAttachment = async (file) => {
  if (!file || !canUseMatrixChat.value || !activeRoom.value?.exists) return

  if (file.size > 25 * 1024 * 1024) {
    toast.add({
      title: "Anhang ist zu groß",
      description: "Maximal erlaubt sind 25 MB.",
      color: "warning"
    })
    return
  }

  const optimisticId = `attachment-${Date.now()}`
  const optimisticMessage = {
    id: optimisticId,
    sender: identity.value?.matrixUserId || "Du",
    senderDisplayName: identity.value?.displayName || "Du",
    body: file.name,
    attachment: {
      fileName: file.name,
      mimeType: file.type || "application/octet-stream",
      size: file.size,
      isImage: file.type?.startsWith("image/"),
    },
    timestamp: Date.now(),
    own: true,
    pending: true,
    failed: false
  }

  matrixMessages.value = [
    ...matrixMessages.value,
    optimisticMessage
  ]
  await scrollMessagesToBottom()

  const formData = new FormData()
  formData.append("file", file)

  try {
    const message = await $api(`${activeRoomEndpoint.value}/attachments`, {
      method: "POST",
      body: formData
    })

    matrixMessages.value = matrixMessages.value.map((item) =>
      item.id === optimisticId ? message : item
    )
  } catch (error) {
    matrixMessages.value = matrixMessages.value.map((item) =>
      item.id === optimisticId ? { ...item, pending: false, failed: true } : item
    )
    toast.add({
      title: "Anhang konnte nicht gesendet werden",
      color: "error"
    })
  }
}

const uploadMatrixFiles = async (files) => {
  const fileList = Array.from(files || [])
  if (!fileList.length || !canUseMatrixChat.value || !activeRoom.value?.exists) return

  matrixAttachmentUploading.value = true
  matrixAttachmentUploadCount.value = fileList.length

  try {
    for (const file of fileList) {
      await uploadSingleMatrixAttachment(file)
    }
  } finally {
    matrixAttachmentUploading.value = false
    matrixAttachmentUploadCount.value = 0
    matrixDragActive.value = false
  }
}

const uploadMatrixAttachment = async (event) => {
  const files = event.target.files
  event.target.value = ""
  await uploadMatrixFiles(files)
}

const handleAttachmentDrop = async (event) => {
  matrixDragActive.value = false
  await uploadMatrixFiles(event.dataTransfer?.files)
}

const downloadMatrixAttachment = async (attachment) => {
  if (!attachment?.url) return

  try {
    const blob = await $api(matrixMediaProxyUrl(attachment), {
      responseType: "blob"
    })
    const objectUrl = URL.createObjectURL(blob)
    const link = document.createElement("a")
    link.href = objectUrl
    link.download = attachment.fileName || "Anhang"
    link.click()
    URL.revokeObjectURL(objectUrl)
  } catch (error) {
    toast.add({
      title: "Anhang konnte nicht geladen werden",
      color: "error"
    })
  }
}

const reactToMatrixMessage = async (message, key) => {
  if (!message?.id || !canUseMatrixChat.value || !activeRoom.value?.exists) return

  try {
    await $api(`${activeRoomEndpoint.value}/messages/${encodeURIComponent(message.id)}/reactions`, {
      method: "POST",
      body: { key }
    })
    const existing = message.reactions || []
    const current = existing.find((reaction) => reaction.key === key)
    const reactions = current
      ? existing.map((reaction) => reaction.key === key ? { ...reaction, count: reaction.count + 1, own: true } : reaction)
      : [...existing, { key, count: 1, own: true }]

    matrixMessages.value = matrixMessages.value.map((item) =>
      item.id === message.id ? { ...item, reactions } : item
    )
  } catch (error) {
    toast.add({
      title: "Reaktion konnte nicht gesendet werden",
      color: "error"
    })
  }
}

const deleteMatrixMessage = async (message) => {
  if (!message?.id || !message.own || !canUseMatrixChat.value || !activeRoom.value?.exists) return

  try {
    await $api(`${activeRoomEndpoint.value}/messages/${encodeURIComponent(message.id)}`, {
      method: "DELETE"
    })
    matrixMessages.value = matrixMessages.value.filter((item) => item.id !== message.id)
    if (matrixEditingMessage.value?.id === message.id) {
      cancelEditMessage()
    }
  } catch (error) {
    toast.add({
      title: "Nachricht konnte nicht gelöscht werden",
      color: "error"
    })
  }
}

const applyMatrixSync = async (syncResult) => {
  if (syncResult.nextBatch) {
    matrixSyncSince.value = syncResult.nextBatch
  }

  mergeMatrixMessages(syncResult.messages || [])
  applyMatrixReplacements(syncResult.replacements)
  applyMatrixReactions(syncResult.reactions)
  applyMatrixRedactions(syncResult.redactions)

  if ((syncResult.messages || []).length) {
    await markActiveRoomRead()
    await scrollMessagesToBottom()
  }

  if (syncResult.membersChanged) {
    await loadRoomMembers({ silent: true })
  }
}

const runMatrixLiveSync = async (runId) => {
  while (matrixLiveSyncRunId === runId) {
    if (!canUseMatrixChat.value || !activeRoom.value?.exists || document.hidden) {
      await new Promise((resolve) => window.setTimeout(resolve, 3000))
      continue
    }

    try {
      const params = new URLSearchParams()
      if (matrixSyncSince.value) {
        params.set("since", matrixSyncSince.value)
      } else {
        params.set("initial", "1")
      }

      const syncResult = await $api(`${activeRoomEndpoint.value}/sync?${params.toString()}`)
      if (matrixLiveSyncRunId !== runId) return

      await applyMatrixSync(syncResult)
      await loadUnreadCounts()
      lastUpdated.value = new Date()
    } catch (error) {
      if (matrixLiveSyncRunId !== runId) return
      await new Promise((resolve) => window.setTimeout(resolve, 5000))
    }
  }
}

const startMatrixAutoRefresh = () => {
  if (matrixAutoRefreshActive.value) return

  matrixAutoRefreshActive.value = true
  matrixLiveSyncRunId += 1
  runMatrixLiveSync(matrixLiveSyncRunId)
}

const stopMatrixAutoRefresh = () => {
  matrixLiveSyncRunId += 1
  matrixAutoRefreshActive.value = false
}

const restartMatrixLiveSync = () => {
  matrixSyncSince.value = ""
  stopMatrixAutoRefresh()
  startMatrixAutoRefresh()
}

const formatMessageTime = (timestamp) => {
  if (!timestamp) return ""

  return new Intl.DateTimeFormat("de-DE", {
    dateStyle: "short",
    timeStyle: "short"
  }).format(new Date(timestamp))
}

const formatAttachmentSize = (size) => {
  const bytes = Number(size || 0)
  if (!bytes) return ""
  if (bytes < 1024) return `${bytes} B`
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
  return `${(bytes / 1024 / 1024).toFixed(1)} MB`
}

const matrixMediaProxyUrl = (attachment) => {
  if (!attachment?.url) return ""

  const params = new URLSearchParams({
    uri: attachment.url,
    name: attachment.fileName || "Anhang"
  })

  return `/api/communication/matrix/media?${params.toString()}`
}

const attachmentObjectUrl = (attachment) =>
  attachment?.url ? matrixAttachmentObjectUrls.value[attachment.url] || "" : ""

const ensureAttachmentObjectUrl = async (attachment) => {
  if (!attachment?.url || matrixAttachmentObjectUrls.value[attachment.url] || matrixAttachmentPreviewRequests.has(attachment.url)) return

  matrixAttachmentPreviewRequests.add(attachment.url)
  try {
    const blob = await $api(matrixMediaProxyUrl(attachment), {
      responseType: "blob"
    })
    const objectUrl = URL.createObjectURL(blob)
    matrixAttachmentObjectUrls.value = {
      ...matrixAttachmentObjectUrls.value,
      [attachment.url]: objectUrl
    }
  } catch (error) {
    // Fällt nur auf den Link zurück; der Chat selbst soll weiter funktionieren.
  } finally {
    matrixAttachmentPreviewRequests.delete(attachment.url)
  }
}

const loadAttachmentPreviews = () => {
  for (const message of matrixMessages.value) {
    if (message.attachment?.isImage && message.attachment.url) {
      ensureAttachmentObjectUrl(message.attachment)
    }
  }
}

const formatLastUpdated = computed(() => {
  if (!lastUpdated.value) return "Noch nicht aktualisiert"

  return new Intl.DateTimeFormat("de-DE", {
    hour: "2-digit",
    minute: "2-digit"
  }).format(lastUpdated.value)
})

watch(
  () => matrixMessages.value.length,
  () => scrollMessagesToBottom()
)

onMounted(async () => {
  await loadChatInfo()
  startMatrixAutoRefresh()
})

onBeforeUnmount(() => {
  stopMatrixAutoRefresh()
  stopMatrixCallDurationTimer()
  leaveMatrixCall()
  Object.values(matrixAttachmentObjectUrls.value).forEach((objectUrl) => URL.revokeObjectURL(objectUrl))
})
</script>

<template>
  <div class="flex min-h-0 flex-1 overflow-hidden">
    <aside class="hidden w-72 shrink-0 flex-col border-r border-default bg-default lg:flex">
      <div class="border-b border-default p-4">
        <div class="flex items-center justify-between gap-3">
          <div>
            <h1 class="text-lg font-semibold text-highlighted">
              Chat
            </h1>
            <p class="text-xs text-muted">
              {{ formatLastUpdated }}
            </p>
          </div>
          <UButton
              icon="i-heroicons-arrow-path"
              color="neutral"
              variant="ghost"
              :loading="loading || matrixMessagesLoading"
              :disabled="!canUseMatrixChat"
              @click="loadRoomChat({ includeMembers: true })"
          />
        </div>
      </div>

      <div class="border-b border-default p-3">
        <UButton
            icon="i-heroicons-plus"
            color="primary"
            variant="soft"
            block
            :disabled="!canUseMatrixChat"
            @click="roomCreateOpen = !roomCreateOpen"
        >
          Raum erstellen
        </UButton>

        <form
            v-if="roomCreateOpen"
            class="mt-3 space-y-3 rounded-md border border-default bg-muted p-3"
            @submit.prevent="createRoom"
        >
          <UInput
              v-model="roomCreateForm.name"
              placeholder="Raumname"
              :disabled="roomCreating"
          />
          <UInput
              v-model="roomCreateForm.key"
              placeholder="Raum-Key optional"
              :disabled="roomCreating"
          />
          <UTextarea
              v-model="roomCreateForm.topic"
              placeholder="Thema optional"
              :rows="2"
              :disabled="roomCreating"
          />
          <p class="truncate text-xs text-muted">
            Key: {{ roomCreateKeyPreview }}
          </p>
          <div class="flex justify-end gap-2">
            <UButton
                type="button"
                color="neutral"
                variant="ghost"
                :disabled="roomCreating"
                @click="roomCreateOpen = false"
            >
              Abbrechen
            </UButton>
            <UButton
                type="submit"
                icon="i-heroicons-check"
                :loading="roomCreating"
                :disabled="!roomCreateForm.name.trim()"
            >
              Erstellen
            </UButton>
          </div>
        </form>
      </div>

      <div class="flex-1 space-y-4 overflow-y-auto p-3">
        <section
            v-for="group in groupedRooms"
            :key="group.key"
            class="space-y-1"
        >
          <button
              type="button"
              class="flex w-full items-center justify-between rounded-md px-2 py-1 text-left text-muted transition hover:bg-muted hover:text-highlighted"
              :aria-expanded="!isRoomGroupCollapsed(group.key)"
              @click="toggleRoomGroup(group.key)"
          >
            <span class="flex min-w-0 items-center gap-2">
              <UIcon
                  name="i-heroicons-chevron-right"
                  class="size-3.5 shrink-0 transition-transform"
                  :class="!isRoomGroupCollapsed(group.key) ? 'rotate-90' : ''"
              />
              <span class="truncate text-[11px] font-semibold uppercase tracking-wide">
                {{ group.label }}
              </span>
            </span>
            <span class="text-[11px]">{{ group.rooms.length }}</span>
          </button>
          <div
              v-if="!isRoomGroupCollapsed(group.key)"
              class="space-y-1"
          >
            <button
                v-for="room in group.rooms"
                :key="room.key"
                type="button"
                class="flex w-full items-center gap-3 rounded-md px-3 py-2 text-left transition"
                :class="[
                  room.disabled ? 'cursor-not-allowed opacity-50' : 'text-highlighted hover:bg-muted',
                  room.key === activeRoomKey ? 'bg-muted ring-1 ring-primary/20' : ''
                ]"
                :disabled="room.disabled"
                @click="setActiveRoom(room)"
            >
              <span class="flex size-9 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary">
                <UIcon :name="room.icon || 'i-heroicons-chat-bubble-left-right'" class="size-5" />
              </span>
              <span class="min-w-0 flex-1">
                <span class="block truncate text-sm font-medium">{{ room.name }}</span>
                <span class="block truncate text-xs text-muted">{{ room.description }}</span>
              </span>
              <UBadge
                  v-if="roomProvisioningKey === room.key"
                  color="primary"
                  variant="soft"
                  size="xs"
              >
                lädt
              </UBadge>
              <UBadge
                  v-else-if="room.unread"
                  :color="room.mentions ? 'error' : 'primary'"
                  variant="solid"
                  size="xs"
              >
                {{ room.mentions ? `@${room.mentions}` : room.unread }}
              </UBadge>
              <UBadge
                  v-else-if="room.exists"
                  color="success"
                  variant="soft"
                  size="xs"
              >
                aktiv
              </UBadge>
              <UBadge
                  v-else-if="!room.disabled"
                  color="neutral"
                  variant="soft"
                  size="xs"
              >
                offen
              </UBadge>
            </button>
          </div>
        </section>
      </div>
    </aside>

    <main class="flex min-w-0 flex-1 flex-col bg-muted">
      <header class="flex shrink-0 items-center justify-between gap-3 border-b border-default bg-default px-4 py-3">
        <div class="min-w-0">
          <div class="flex items-center gap-2">
            <h2 class="truncate text-base font-semibold text-highlighted">
              {{ activeRoom.name }}
            </h2>
            <UBadge
                v-if="matrixAutoRefreshActive && activeRoom?.exists"
                color="success"
                variant="soft"
                size="xs"
            >
              Live
            </UBadge>
          </div>
          <p class="truncate text-xs text-muted">
            <span v-if="matrixMembers.length">{{ matrixMembers.length }} Teilnehmer</span>
            <span v-else>{{ activeRoom.description }}</span>
          </p>
        </div>

        <div class="flex items-center gap-2">
          <UButton
              icon="i-heroicons-phone"
              color="neutral"
              variant="outline"
              aria-label="Audioanruf starten"
              :loading="matrixCallLoading && matrixCallMode === 'audio'"
              :disabled="!canStartMatrixCall"
              @click="openMatrixCall('audio')"
          />
          <UButton
              icon="i-heroicons-video-camera"
              color="primary"
              variant="soft"
              aria-label="Videokonferenz starten"
              :loading="matrixCallLoading && matrixCallMode === 'video'"
              :disabled="!canStartMatrixCall"
              @click="openMatrixCall('video')"
          />
          <UButton
              class="lg:hidden"
              icon="i-heroicons-plus"
              color="primary"
              variant="soft"
              :disabled="!canUseMatrixChat"
              @click="roomCreateOpen = !roomCreateOpen"
          />
          <UButton
              icon="i-heroicons-arrow-path"
              color="neutral"
              variant="outline"
              :loading="matrixMessagesLoading"
              :disabled="!canUseMatrixChat || !activeRoom?.exists"
              @click="loadRoomChat({ includeMembers: true })"
          >
            Laden
          </UButton>
        </div>
      </header>

      <div class="border-b border-default bg-default p-3 lg:hidden">
        <div class="flex gap-2 overflow-x-auto pb-1">
          <button
              v-for="room in rooms"
              :key="room.key"
              type="button"
              class="shrink-0 rounded-md border border-default px-3 py-2 text-left text-sm"
              :class="room.key === activeRoomKey ? 'bg-muted text-highlighted' : 'text-muted'"
              :disabled="room.disabled"
              @click="setActiveRoom(room)"
          >
            <span>{{ room.name }}</span>
            <UBadge
                v-if="room.unread"
                class="ml-2"
                :color="room.mentions ? 'error' : 'primary'"
                variant="solid"
                size="xs"
            >
              {{ room.mentions ? `@${room.mentions}` : room.unread }}
            </UBadge>
          </button>
        </div>

        <form
            v-if="roomCreateOpen"
            class="mt-3 grid gap-2 rounded-md border border-default bg-muted p-3"
            @submit.prevent="createRoom"
        >
          <UInput
              v-model="roomCreateForm.name"
              placeholder="Raumname"
              :disabled="roomCreating"
          />
          <UInput
              v-model="roomCreateForm.key"
              placeholder="Raum-Key optional"
              :disabled="roomCreating"
          />
          <UTextarea
              v-model="roomCreateForm.topic"
              placeholder="Thema optional"
              :rows="2"
              :disabled="roomCreating"
          />
          <p class="truncate text-xs text-muted">
            Key: {{ roomCreateKeyPreview }}
          </p>
          <div class="flex justify-end gap-2">
            <UButton
                type="button"
                color="neutral"
                variant="ghost"
                :disabled="roomCreating"
                @click="roomCreateOpen = false"
            >
              Abbrechen
            </UButton>
            <UButton
                type="submit"
                icon="i-heroicons-check"
                :loading="roomCreating"
                :disabled="!roomCreateForm.name.trim()"
            >
              Erstellen
            </UButton>
          </div>
        </form>
      </div>

      <div
          v-if="status && (!status.reachable || !status.provisioningConfigured)"
          class="border-b border-default bg-default px-4 py-3"
      >
        <UAlert
            icon="i-heroicons-exclamation-triangle"
            color="warning"
            variant="soft"
            title="Matrix ist noch nicht bereit"
            description="Bitte prüfe das Matrix-Setup, bevor der Chat genutzt wird."
        />
      </div>

      <div
          v-else-if="status && !activeRoom?.exists"
          class="border-b border-default bg-default px-4 py-3"
      >
        <UAlert
            icon="i-heroicons-chat-bubble-left-right"
            color="primary"
            variant="soft"
            :title="`${activeRoom.name} ist noch nicht in Matrix angelegt`"
            description="Die Raum-Metadaten sind in FEDEO vorhanden. Lege jetzt den Matrix-Raum dazu an."
        >
          <template #actions>
            <UButton
                icon="i-heroicons-plus"
                :loading="roomProvisioning"
                @click="provisionActiveRoom"
            >
              Matrix-Raum erstellen
            </UButton>
          </template>
        </UAlert>
      </div>

      <div
          v-if="matrixLiveKitRoom && matrixCallConnected"
          class="flex shrink-0 flex-wrap items-center justify-between gap-3 border-b border-primary/20 bg-primary/10 px-4 py-3"
      >
        <button
            type="button"
            class="min-w-0 text-left"
            @click="matrixCallOpen = true"
        >
          <p class="truncate text-sm font-medium text-highlighted">
            {{ matrixCallTitle }} läuft in {{ activeRoom.name }}
          </p>
          <p class="truncate text-xs text-muted">
            {{ matrixCallParticipantCount }} Teilnehmer · {{ matrixCallDurationLabel }} · {{ matrixCallStateLabel }}
          </p>
        </button>
        <div class="flex flex-wrap items-center gap-2">
          <UButton
              :icon="matrixCallMicEnabled ? 'i-heroicons-microphone' : 'i-heroicons-microphone-slash'"
              color="neutral"
              :variant="matrixCallMicEnabled ? 'soft' : 'outline'"
              size="sm"
              :disabled="!matrixLiveKitRoom"
              @click="toggleMatrixCallMic"
          />
          <UButton
              :icon="matrixCallCameraEnabled ? 'i-heroicons-video-camera' : 'i-heroicons-video-camera-slash'"
              color="neutral"
              :variant="matrixCallCameraEnabled ? 'soft' : 'outline'"
              size="sm"
              :disabled="!matrixLiveKitRoom"
              @click="toggleMatrixCallCamera"
          />
          <UButton
              :icon="matrixCallScreenShareEnabled ? 'i-heroicons-computer-desktop' : 'i-heroicons-arrow-up-tray'"
              color="neutral"
              :variant="matrixCallScreenShareEnabled ? 'soft' : 'outline'"
              size="sm"
              :disabled="!matrixLiveKitRoom"
              @click="toggleMatrixCallScreenShare"
          />
          <UButton
              icon="i-heroicons-arrow-top-right-on-square"
              color="primary"
              variant="soft"
              size="sm"
              @click="matrixCallOpen = true"
          >
            Öffnen
          </UButton>
          <UButton
              icon="i-heroicons-phone-x-mark"
              color="error"
              variant="soft"
              size="sm"
              @click="closeMatrixCall"
          >
            Auflegen
          </UButton>
        </div>
      </div>

      <div
          ref="matrixMessagesViewport"
          class="relative min-h-0 flex-1 space-y-3 overflow-y-auto p-4 sm:p-5"
          :class="matrixDragActive ? 'ring-2 ring-inset ring-primary' : ''"
          @dragenter.prevent="matrixDragActive = true"
          @dragover.prevent="matrixDragActive = true"
          @dragleave.prevent="matrixDragActive = false"
          @drop.prevent="handleAttachmentDrop"
      >
        <div
            v-if="matrixDragActive"
            class="pointer-events-none absolute inset-4 z-10 flex items-center justify-center rounded-lg border-2 border-dashed border-primary bg-primary/10 text-sm font-medium text-primary"
        >
          Dateien hier ablegen
        </div>
        <div
            v-if="!matrixMessages.length && !matrixMessagesLoading"
            class="flex h-full min-h-64 items-center justify-center text-sm text-muted"
        >
          Noch keine Nachrichten in diesem Chat.
        </div>

        <div
            v-for="message in matrixMessages"
            :key="message.id"
            class="flex"
            :class="message.own ? 'justify-end' : 'justify-start'"
        >
          <div
              class="max-w-[82%] rounded-lg px-3 py-2 shadow-sm sm:max-w-[68%]"
              :class="[
                message.own ? 'bg-primary text-inverted' : 'bg-default text-highlighted',
                message.pending ? 'opacity-70' : '',
                message.failed ? 'bg-error text-inverted' : ''
              ]"
          >
            <div class="mb-1 flex flex-wrap items-center gap-x-2 gap-y-1 text-[11px] opacity-75">
              <span class="truncate font-medium">
                {{ message.own ? "Du" : message.senderDisplayName || message.sender }}
              </span>
              <span>{{ formatMessageTime(message.timestamp) }}</span>
              <span v-if="message.pending">wird gesendet</span>
              <span v-if="message.failed">nicht gesendet</span>
              <span v-if="message.edited">bearbeitet</span>
            </div>
            <button
                v-if="message.replyToEventId"
                type="button"
                class="mb-2 block w-full rounded border px-2 py-1 text-left text-xs opacity-80"
                :class="message.own ? 'border-white/20' : 'border-default'"
            >
              Antwort auf {{ findMessage(message.replyToEventId)?.senderDisplayName || "Nachricht" }}:
              {{ replyPreview(findMessage(message.replyToEventId)) }}
            </button>
            <p class="whitespace-pre-wrap break-words text-sm">
              {{ message.body }}
            </p>
            <div
                v-if="message.attachment"
                class="mt-2 overflow-hidden rounded-md border"
                :class="message.own ? 'border-white/20' : 'border-default'"
            >
              <img
                  v-if="message.attachment.isImage && attachmentObjectUrl(message.attachment)"
                  :src="attachmentObjectUrl(message.attachment)"
                  :alt="message.attachment.fileName || message.body"
                  class="max-h-72 w-full object-contain"
              >
              <button
                  v-if="message.attachment.url"
                  type="button"
                  class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm"
                  @click="downloadMatrixAttachment(message.attachment)"
              >
                <UIcon name="i-heroicons-paper-clip" class="size-4 shrink-0" />
                <span class="min-w-0 flex-1 truncate">
                  {{ message.attachment.fileName || message.body }}
                </span>
                <span class="shrink-0 text-xs opacity-70">
                  {{ formatAttachmentSize(message.attachment.size) }}
                </span>
              </button>
              <div
                  v-else
                  class="flex items-center gap-2 px-3 py-2 text-sm"
              >
                <UIcon name="i-heroicons-paper-clip" class="size-4 shrink-0" />
                <span class="min-w-0 flex-1 truncate">
                  {{ message.attachment.fileName || message.body }}
                </span>
                <span class="shrink-0 text-xs opacity-70">
                  {{ formatAttachmentSize(message.attachment.size) }}
                </span>
              </div>
            </div>
            <div class="mt-2 flex flex-wrap items-center gap-1">
              <button
                  v-for="reaction in message.reactions || []"
                  :key="reaction.key"
                  type="button"
                  class="rounded-full px-2 py-0.5 text-xs"
                  :class="reaction.own ? 'bg-white/20' : 'bg-muted'"
                  @click="reactToMatrixMessage(message, reaction.key)"
              >
                {{ reaction.key }} {{ reaction.count }}
              </button>
              <button
                  v-for="reactionKey in ['👍', '✅', '👀']"
                  :key="reactionKey"
                  type="button"
                  class="rounded-full px-2 py-0.5 text-xs opacity-70 hover:opacity-100"
                  :class="message.own ? 'bg-white/10' : 'bg-muted'"
                  @click="reactToMatrixMessage(message, reactionKey)"
              >
                {{ reactionKey }}
              </button>
              <button
                  type="button"
                  class="rounded-full px-2 py-0.5 text-xs opacity-70 hover:opacity-100"
                  :class="message.own ? 'bg-white/10' : 'bg-muted'"
                  @click="setReplyTarget(message)"
              >
                Antworten
              </button>
              <button
                  v-if="message.own && !message.attachment && !message.pending"
                  type="button"
                  class="rounded-full px-2 py-0.5 text-xs opacity-70 hover:opacity-100"
                  :class="message.own ? 'bg-white/10' : 'bg-muted'"
                  @click="beginEditMessage(message)"
              >
                Bearbeiten
              </button>
              <button
                  v-if="message.own && !message.pending"
                  type="button"
                  class="rounded-full px-2 py-0.5 text-xs opacity-70 hover:opacity-100"
                  :class="message.own ? 'bg-white/10' : 'bg-muted'"
                  @click="deleteMatrixMessage(message)"
              >
                Löschen
              </button>
            </div>
          </div>
        </div>
      </div>

      <div
          v-if="matrixEditingMessage"
          class="flex shrink-0 items-center gap-3 border-t border-default bg-default px-3 py-2 text-sm"
      >
        <UIcon name="i-heroicons-pencil-square" class="size-4 text-muted" />
        <div class="min-w-0 flex-1">
          <p class="truncate text-xs text-muted">Nachricht bearbeiten</p>
          <p class="truncate text-highlighted">{{ matrixEditingMessage.body }}</p>
        </div>
        <UButton
            icon="i-heroicons-x-mark"
            color="neutral"
            variant="ghost"
            size="xs"
            @click="cancelEditMessage"
        />
      </div>

      <div
          v-if="matrixReplyTarget"
          class="flex shrink-0 items-center gap-3 border-t border-default bg-default px-3 py-2 text-sm"
      >
        <UIcon name="i-heroicons-arrow-uturn-left" class="size-4 text-muted" />
        <div class="min-w-0 flex-1">
          <p class="truncate text-xs text-muted">Antwort an {{ matrixReplyTarget.senderDisplayName }}</p>
          <p class="truncate text-highlighted">{{ matrixReplyTarget.body }}</p>
        </div>
        <UButton
            icon="i-heroicons-x-mark"
            color="neutral"
            variant="ghost"
            size="xs"
            @click="clearReplyTarget"
        />
      </div>

      <form
          class="flex shrink-0 gap-2 border-t border-default bg-default p-3"
          @submit.prevent="sendMatrixMessage"
      >
        <input
            ref="matrixAttachmentInput"
            type="file"
            multiple
            class="hidden"
            @change="uploadMatrixAttachment"
        >
        <UButton
            type="button"
            icon="i-heroicons-paper-clip"
            color="neutral"
            variant="outline"
            :loading="matrixAttachmentUploading"
            :disabled="matrixAttachmentUploading || !canUseMatrixChat || !activeRoom?.exists"
            @click="openAttachmentPicker"
        />
        <UInput
            v-model="matrixMessageDraft"
            class="min-w-0 flex-1"
            :placeholder="matrixEditingMessage ? 'Änderung schreiben' : (matrixAttachmentUploading ? `${matrixAttachmentUploadCount} Anhang/Anhänge werden hochgeladen` : 'Nachricht schreiben')"
            :disabled="!canSendChatInput"
            @keydown.enter.exact.prevent="sendMatrixMessage"
        />
        <UButton
            type="submit"
            icon="i-heroicons-paper-airplane"
            :loading="matrixMessageSending"
            :disabled="!matrixMessageDraft.trim() || !canSendChatInput"
        >
          {{ matrixEditingMessage ? "Speichern" : "Senden" }}
        </UButton>
      </form>
    </main>

    <aside class="hidden w-80 shrink-0 border-l border-default bg-default xl:block">
      <div class="border-b border-default p-4">
        <h3 class="text-sm font-semibold text-highlighted">
          Raumdetails
        </h3>
        <p class="mt-1 break-all font-mono text-xs text-muted">
          {{ activeRoom?.roomId || activeRoom?.alias || "Noch kein Matrix-Raum" }}
        </p>
      </div>

      <div class="space-y-5 p-4">
        <div>
          <h4 class="mb-2 text-xs font-medium uppercase text-muted">
            Suche
          </h4>
          <form
              class="flex gap-2"
              @submit.prevent="searchRoomMessages"
          >
            <UInput
                v-model="matrixSearchQuery"
                size="sm"
                class="min-w-0 flex-1"
                placeholder="Nachrichten suchen"
                :disabled="!canUseMatrixChat || !activeRoom?.exists"
            />
            <UButton
                type="submit"
                icon="i-heroicons-magnifying-glass"
                size="sm"
                color="neutral"
                variant="outline"
                :loading="matrixSearchLoading"
                :disabled="matrixSearchQuery.trim().length < 2 || !canUseMatrixChat || !activeRoom?.exists"
            />
          </form>
          <div
              v-if="matrixSearchResults.length"
              class="mt-3 max-h-52 space-y-2 overflow-y-auto"
          >
            <button
                v-for="result in matrixSearchResults"
                :key="result.id"
                type="button"
                class="block w-full rounded-md border border-default px-3 py-2 text-left hover:bg-muted"
                @click="setReplyTarget(result)"
            >
              <div class="mb-1 flex items-center justify-between gap-2 text-xs text-muted">
                <span class="truncate">{{ result.senderDisplayName || result.sender }}</span>
                <span class="shrink-0">{{ formatMessageTime(result.timestamp) }}</span>
              </div>
              <p class="line-clamp-2 text-sm text-highlighted">
                {{ result.body || result.attachment?.fileName || "Anhang" }}
              </p>
            </button>
          </div>
          <p
              v-else-if="matrixSearchQuery.trim().length >= 2 && !matrixSearchLoading"
              class="mt-3 text-sm text-muted"
          >
            Keine Treffer in diesem Raum.
          </p>
        </div>

        <div>
          <div class="mb-2 flex items-center justify-between gap-2">
            <h4 class="text-xs font-medium uppercase text-muted">
              Teilnehmer
            </h4>
            <div class="flex items-center gap-1">
              <UButton
                  icon="i-heroicons-users"
                  color="neutral"
                  variant="ghost"
                  size="xs"
                  :loading="roomMembersSyncing"
                  :disabled="!canUseMatrixChat || !activeRoom?.exists"
                  @click="syncRoomMembers"
              />
              <UButton
                  icon="i-heroicons-plus"
                  color="neutral"
                  variant="ghost"
                  size="xs"
                  :disabled="!canUseMatrixChat || !activeRoom?.exists || !availableRoomInviteUsers.length"
                  @click="toggleMemberInvite"
              />
              <UButton
                  icon="i-heroicons-arrow-path"
                  color="neutral"
                  variant="ghost"
                  size="xs"
                  :loading="matrixMembersLoading"
                  :disabled="!canUseMatrixChat || !activeRoom?.exists"
                  @click="loadRoomMembers"
              />
            </div>
          </div>

          <form
              v-if="memberInviteOpen"
              class="mb-3 rounded-md border border-default p-2"
              @submit.prevent="inviteRoomMember"
          >
            <select
                v-model="memberInviteUserId"
                class="mb-2 w-full rounded-md border border-default bg-default px-2 py-1.5 text-sm text-highlighted"
                :disabled="memberInviting"
            >
              <option
                  v-for="user in availableRoomInviteUsers"
                  :key="user.userId"
                  :value="user.userId"
              >
                {{ user.displayName || user.email }}
              </option>
            </select>
            <UButton
                type="submit"
                icon="i-heroicons-user-plus"
                size="xs"
                block
                :loading="memberInviting"
                :disabled="!memberInviteUserId || memberInviting"
            >
              Einladen
            </UButton>
          </form>

          <div class="space-y-2">
            <div
                v-for="member in matrixMembers"
                :key="member.matrixUserId"
                class="flex items-center gap-2"
            >
              <span class="flex size-8 shrink-0 items-center justify-center rounded-md bg-muted text-xs font-medium text-highlighted">
                {{ (member.displayName || member.matrixUserId || "?").slice(0, 1).toUpperCase() }}
              </span>
              <div class="min-w-0">
                <p class="truncate text-sm font-medium text-highlighted">
                  {{ member.displayName || member.matrixUserId }}
                </p>
                <p class="truncate text-xs text-muted">
                  {{ member.matrixUserId }}
                </p>
              </div>
              <UButton
                  v-if="!member.own"
                  icon="i-heroicons-x-mark"
                  color="neutral"
                  variant="ghost"
                  size="xs"
                  class="ml-auto"
                  :loading="memberRemovingId === member.matrixUserId"
                  :disabled="Boolean(memberRemovingId)"
                  @click="removeRoomMember(member)"
              />
            </div>

            <p
                v-if="!matrixMembers.length"
                class="text-sm text-muted"
            >
              Noch keine Teilnehmer geladen.
            </p>
          </div>
        </div>

        <div>
          <h4 class="mb-2 text-xs font-medium uppercase text-muted">
            Besprechung
          </h4>
          <div class="grid grid-cols-2 gap-2">
            <UButton
                icon="i-heroicons-phone"
                color="neutral"
                variant="outline"
                block
                :loading="matrixCallLoading && matrixCallMode === 'audio'"
                :disabled="!canStartMatrixCall"
                @click="openMatrixCall('audio')"
            >
              Audio
            </UButton>
            <UButton
                icon="i-heroicons-video-camera"
                color="primary"
                variant="soft"
                block
                :loading="matrixCallLoading && matrixCallMode === 'video'"
                :disabled="!canStartMatrixCall"
                @click="openMatrixCall('video')"
            >
              Video
            </UButton>
          </div>
        </div>

        <div>
          <h4 class="mb-2 text-xs font-medium uppercase text-muted">
            Eigene Identität
          </h4>
          <p class="break-all font-mono text-xs text-highlighted">
            {{ identity?.matrixUserId || "-" }}
          </p>
        </div>
      </div>
    </aside>

    <UModal
        v-model:open="matrixCallOpen"
        fullscreen
        class="h-[100dvh]"
    >
      <template #content>
        <div class="flex h-[100dvh] flex-col bg-inverted text-inverted">
          <header class="flex shrink-0 items-center justify-between gap-3 border-b border-white/10 bg-black px-4 py-3">
            <div class="min-w-0">
              <h3 class="truncate text-base font-semibold text-white">
                {{ matrixCallTitle }} · {{ activeRoom.name }}
              </h3>
              <p class="truncate text-xs text-white/60">
                {{ matrixCallParticipantCount }} Teilnehmer · {{ matrixCallDurationLabel }} · {{ matrixCallStateLabel }}
              </p>
            </div>
            <div class="flex items-center gap-2">
              <UButton
                  icon="i-heroicons-arrows-pointing-in"
                  color="neutral"
                  variant="ghost"
                  :disabled="!matrixLiveKitRoom"
                  @click="minimizeMatrixCall"
              >
                Minimieren
              </UButton>
              <UButton
                  icon="i-heroicons-phone-x-mark"
                  color="error"
                  variant="soft"
                  @click="closeMatrixCall"
              >
                Auflegen
              </UButton>
              <UButton
                  icon="i-heroicons-x-mark"
                  color="neutral"
                  variant="ghost"
                  @click="closeMatrixCall"
              />
            </div>
          </header>

          <div class="min-h-0 flex-1 bg-black">
            <div
                v-if="matrixCallLoading"
                class="flex h-full items-center justify-center text-sm text-white/70"
            >
              Besprechung wird gestartet...
            </div>
            <div
                v-else-if="matrixCallError"
                class="flex h-full items-center justify-center p-6"
            >
              <UAlert
                  icon="i-heroicons-exclamation-triangle"
                  color="error"
                  variant="soft"
                  title="Besprechung konnte nicht gestartet werden"
                  :description="matrixCallError"
              />
            </div>
            <div
                v-else-if="canStartMatrixCall"
                class="flex h-full flex-col"
            >
              <div class="flex min-h-0 flex-1 flex-col gap-3 p-3 lg:flex-row">
                <div class="relative min-h-0 flex-1 overflow-hidden rounded-lg bg-zinc-950">
                  <template v-if="matrixCallPrimaryTile">
                    <video
                        v-if="matrixCallPrimaryTile.videoTrack"
                        :ref="(element) => setMatrixCallVideoElement(matrixCallPrimaryTile.id, element)"
                        class="h-full min-h-[60dvh] w-full"
                        :class="matrixCallPrimaryTile.screenSharing ? 'object-contain bg-black' : 'object-cover'"
                        autoplay
                        playsinline
                        :muted="matrixCallPrimaryTile.local"
                    />
                    <div
                        v-else
                        class="flex h-full min-h-[60dvh] items-center justify-center bg-zinc-900"
                    >
                      <span class="flex size-24 items-center justify-center rounded-lg bg-primary/15 text-3xl font-semibold text-primary">
                        {{ participantInitial(matrixCallPrimaryTile) }}
                      </span>
                    </div>
                    <div class="absolute inset-x-0 bottom-0 flex items-center justify-between bg-black/65 px-4 py-3 text-sm text-white">
                      <span class="truncate font-medium">
                        {{ matrixCallPrimaryTile.name }}
                        <span v-if="matrixCallPrimaryTile.screenSharing" class="ml-1 text-xs text-white/70">teilt Bildschirm</span>
                      </span>
                      <div class="flex items-center gap-2 text-white/80">
                        <UIcon
                            v-if="matrixCallPrimaryTile.screenSharing"
                            name="i-heroicons-computer-desktop"
                            class="size-4"
                        />
                        <UIcon
                            v-if="!matrixCallPrimaryTile.screenSharing"
                            :name="matrixCallPrimaryTile.microphoneEnabled ? 'i-heroicons-microphone' : 'i-heroicons-microphone-slash'"
                            class="size-4"
                        />
                        <UIcon
                            v-if="!matrixCallPrimaryTile.screenSharing"
                            :name="matrixCallPrimaryTile.cameraEnabled ? 'i-heroicons-video-camera' : 'i-heroicons-video-camera-slash'"
                            class="size-4"
                        />
                      </div>
                    </div>
                  </template>
                  <div
                      v-else
                      class="flex h-full min-h-[60dvh] items-center justify-center text-sm text-white/60"
                  >
                    Warte auf Medien...
                  </div>
                </div>

                <div
                    v-if="matrixCallSecondaryTiles.length"
                    class="grid max-h-48 shrink-0 grid-cols-2 gap-2 overflow-y-auto lg:max-h-none lg:w-72 lg:grid-cols-1"
                >
                  <button
                      v-for="tile in matrixCallSecondaryTiles"
                      :key="tile.id"
                      type="button"
                      class="relative min-h-28 overflow-hidden rounded-lg bg-zinc-900 text-left"
                      :class="tile.speaking ? 'ring-2 ring-primary' : ''"
                  >
                    <video
                        v-if="tile.videoTrack"
                        :ref="(element) => setMatrixCallVideoElement(tile.id, element)"
                        class="h-full min-h-28 w-full"
                        :class="tile.screenSharing ? 'object-contain bg-black' : 'object-cover'"
                        autoplay
                        playsinline
                        :muted="tile.local"
                    />
                    <div
                        v-else
                        class="flex h-full min-h-28 items-center justify-center"
                    >
                      <span class="flex size-12 items-center justify-center rounded-lg bg-primary/15 text-lg font-semibold text-primary">
                        {{ participantInitial(tile) }}
                      </span>
                    </div>
                    <div class="absolute inset-x-0 bottom-0 flex items-center justify-between bg-black/60 px-2 py-1 text-xs text-white">
                      <span class="truncate">{{ tile.name }}</span>
                      <UIcon
                          :name="tile.screenSharing ? 'i-heroicons-computer-desktop' : (tile.microphoneEnabled ? 'i-heroicons-microphone' : 'i-heroicons-microphone-slash')"
                          class="size-3.5 shrink-0"
                      />
                    </div>
                  </button>
                </div>
              </div>

              <div class="flex shrink-0 flex-wrap items-center justify-center gap-2 border-t border-white/10 bg-black px-4 py-3">
                <UButton
                    :icon="matrixCallMicEnabled ? 'i-heroicons-microphone' : 'i-heroicons-microphone-slash'"
                    color="neutral"
                    :variant="matrixCallMicEnabled ? 'soft' : 'outline'"
                    :disabled="!matrixLiveKitRoom"
                    @click="toggleMatrixCallMic"
                >
                  {{ matrixCallMicEnabled ? "Mikro an" : "Mikro aus" }}
                </UButton>
                <UButton
                    :icon="matrixCallCameraEnabled ? 'i-heroicons-video-camera' : 'i-heroicons-video-camera-slash'"
                    color="neutral"
                    :variant="matrixCallCameraEnabled ? 'soft' : 'outline'"
                    :disabled="!matrixLiveKitRoom"
                    @click="toggleMatrixCallCamera"
                >
                  {{ matrixCallCameraEnabled ? "Kamera an" : "Kamera aus" }}
                </UButton>
                <UButton
                    :icon="matrixCallScreenShareEnabled ? 'i-heroicons-computer-desktop' : 'i-heroicons-arrow-up-tray'"
                    color="neutral"
                    :variant="matrixCallScreenShareEnabled ? 'soft' : 'outline'"
                    :disabled="!matrixLiveKitRoom"
                    @click="toggleMatrixCallScreenShare"
                >
                  {{ matrixCallScreenShareEnabled ? "Freigabe beenden" : "Bildschirm teilen" }}
                </UButton>
                <UButton
                    icon="i-heroicons-phone-x-mark"
                    color="error"
                    variant="solid"
                    @click="closeMatrixCall"
                >
                  Auflegen
                </UButton>
              </div>
              <div ref="matrixCallAudioContainer" class="hidden" />
            </div>
            <div
                v-else
                class="flex h-full items-center justify-center p-6"
            >
              <UAlert
                  icon="i-heroicons-exclamation-triangle"
                  color="warning"
                  variant="soft"
                  title="Besprechung nicht verfügbar"
                  description="Der Matrix-Raum oder LiveKit ist noch nicht bereit."
              />
            </div>
          </div>
        </div>
      </template>
    </UModal>
  </div>
</template>
