<template>
  <div class="tv-dashboard">
    <div class="slideshow-container">
      <div
        v-for="(slide, idx) in slides"
        :key="slide.filename"
        class="slide"
        :class="{ active: idx === currentSlide }"
      >
        <img :src="withBase(basePath + slide.filename)" :alt="slide.filename" />
      </div>

      <div v-if="error" class="error-message">
        <h2>Display Error</h2>
        <p>{{ error }}</p>
      </div>

      <div v-if="!slides.length && !error" class="error-message">
        <h2>No Slides</h2>
        <p>No slides configured for display</p>
      </div>
    </div>

    <div class="progress-bar" :style="progressStyle"></div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useRoute } from 'vue-router'
import api from '@/api'
import { withBase } from '@/utils/basePath'

// Which slide surface this display renders: 'lobby' (default) or 'shopfloor'
// (the screensaver). Set by the route meta (/screensaver) or a ?surface= query.
const route = useRoute()
const VALID_SURFACES = ['lobby', 'shopfloor']
const surface = (VALID_SURFACES.includes(route.meta?.surface) && route.meta.surface)
  || (VALID_SURFACES.includes(route.query?.surface) && route.query.surface)
  || 'lobby'

const INTERVAL = 10 // seconds between slides

const slides = ref([])
const basePath = ref('/static/slides/')
const currentSlide = ref(0)
const error = ref('')
const progress = ref(0)

let slideTimer = null
let progressTimer = null
let refreshTimer = null

const progressStyle = computed(() => ({
  width: `${progress.value}%`,
  // Matches the slide's own duration, or the bar finishes early and sits full.
  transition: progress.value === 0 ? 'none' : `width ${currentSlideSeconds()}s linear`
}))

onMounted(async () => {
  // fetchSlides starts the slideshow itself once it has more than one slide.
  // Starting it again here ran TWO timer chains at once, and the second
  // assignment to slideTimer lost the handle to the first so nothing could stop
  // it. Both chains called nextSlide a few milliseconds apart, so with exactly
  // two slides the display advanced 0 -> 1 -> 0 every cycle and looked frozen.
  await fetchSlides()

  // Refresh slide list every 60 seconds
  refreshTimer = setInterval(fetchSlides, 60000)

  // On window, not on an element: the display has nothing focused, and a
  // presenter remote sends the same keys as a keyboard.
  window.addEventListener('keydown', onKeydown)
})

onUnmounted(() => {
  window.removeEventListener('keydown', onKeydown)
  if (slideTimer) clearTimeout(slideTimer)
  if (progressTimer) clearTimeout(progressTimer)
  if (refreshTimer) clearInterval(refreshTimer)
})

async function fetchSlides() {
  try {
    // Flat feed shape: { success, surface, basepath, interval, slides:[...] }
    const response = await api.get('/slides/feed', { params: { surface } })
    const data = response.data

    if (data.success && data.slides?.length > 0) {
      slides.value = data.slides
      basePath.value = data.basepath || `/api/slides/img/${surface}/`
      error.value = ''

      // Restart slideshow if slides changed
      if (slides.value.length > 1 && !slideTimer) {
        startSlideshow()
      }
    } else {
      slides.value = []
      error.value = 'No slides configured'
    }
  } catch (err) {
    console.error('Error fetching slides:', err)
    error.value = 'Unable to load slides'
  }
}

function startSlideshow() {
  scheduleNextSlide()
}

function scheduleNextSlide() {
  // Cancel anything already pending. Belt and braces after the double-start
  // bug above: any future caller that schedules twice now replaces the timer
  // rather than leaking an untracked one.
  if (slideTimer) { clearTimeout(slideTimer); slideTimer = null }
  if (progressTimer) { clearTimeout(progressTimer); progressTimer = null }

  // Reset progress bar
  progress.value = 0

  // Start progress animation after a small delay
  progressTimer = setTimeout(() => {
    progress.value = 100
  }, 50)

  // The feed sends a per-slide duration and this used to ignore it, so a slide
  // set to hold for a minute still changed after ten seconds.
  const seconds = currentSlideSeconds()

  slideTimer = setTimeout(() => {
    nextSlide()
    scheduleNextSlide()
  }, seconds * 1000)
}

function currentSlideSeconds() {
  const slide = slides.value[currentSlide.value]
  const seconds = Number(slide && slide.seconds)
  return seconds > 0 ? seconds : INTERVAL
}

function nextSlide() {
  if (slides.value.length === 0) return
  currentSlide.value = (currentSlide.value + 1) % slides.value.length
}

function previousSlide() {
  if (slides.value.length === 0) return
  const count = slides.value.length
  currentSlide.value = (currentSlide.value - 1 + count) % count
}

// Left and right arrows step through the slides by hand. Someone standing at
// the display wanting to hold a slide, or go back to one that has just passed,
// otherwise has to wait for the whole rotation to come round again.
//
// Stepping RESTARTS the timer rather than leaving it running: advancing
// manually and then having it move again a second later, because the existing
// timer was most of the way through, reads as the display ignoring you.
function onKeydown(event) {
  if (slides.value.length < 2) return
  if (event.key === 'ArrowRight' || event.key === 'PageDown') {
    event.preventDefault()
    nextSlide()
    scheduleNextSlide()
  } else if (event.key === 'ArrowLeft' || event.key === 'PageUp') {
    event.preventDefault()
    previousSlide()
    scheduleNextSlide()
  }
}
</script>

<style scoped>
.tv-dashboard {
  background: #000;
  width: 100vw;
  height: 100vh;
  margin: 0;
  padding: 0;
  overflow: hidden;
}

.slideshow-container {
  position: relative;
  width: 100%;
  height: 100%;
}

.slide {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 1s ease-in-out;
  display: flex;
  align-items: center;
  justify-content: center;
}

.slide.active {
  opacity: 1;
}

.slide img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.progress-bar {
  position: fixed;
  bottom: 0;
  left: 0;
  height: 4px;
  background: #4181ff;
  z-index: 100;
}

.error-message {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  text-align: center;
  color: #fff;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-size: 24px;
}

.error-message h2 {
  color: #ff4444;
  margin-bottom: 20px;
}
</style>
