<template>
  <div>
    <div class="page-header">
      <h1>Slides</h1>
      <router-link to="/settings" class="btn btn-secondary">Back to Settings</router-link>
    </div>

    <div class="card">
      <div class="tabs">
        <button
          v-for="s in surfaces"
          :key="s.key"
          class="tab"
          :class="{ active: surface === s.key }"
          @click="switchSurface(s.key)"
        >{{ s.label }}</button>
      </div>

      <div class="toolbar">
        <label class="btn btn-primary upload-btn">
          {{ uploading ? 'Uploading...' : 'Upload Images' }}
          <input type="file" accept="image/*" multiple hidden :disabled="uploading" @change="onUpload" />
        </label>
        <button
          class="btn btn-danger"
          :disabled="!selected.length"
          @click="deleteSelected"
        >Delete Selected ({{ selected.length }})</button>
        <span class="hint">Order top-to-bottom is play order. Images show on the {{ surfaceLabel }}.</span>
      </div>

      <div v-if="loading" class="muted">Loading...</div>
      <div v-else-if="!slides.length" class="muted empty">No slides yet. Upload some images.</div>

      <div v-else class="slide-list">
        <div v-for="(slide, idx) in slides" :key="slide.slideid" class="slide-row"
             :class="{ dragging: dragIndex === idx, 'drop-target': dropIndex === idx && dragIndex !== idx }"
             draggable="true"
             @dragstart="onDragStart(idx, $event)"
             @dragover.prevent="dropIndex = idx"
             @dragleave="onDragLeave(idx)"
             @drop.prevent="onDrop(idx)"
             @dragend="onDragEnd">
          <input type="checkbox" class="pick" :value="slide.filename" v-model="selected" />
          <span class="grip" title="Drag to reorder">&#8942;&#8942;</span>
          <span class="order-num">{{ idx + 1 }}</span>
          <img :src="withBase(slide.url)" :alt="slide.filename" class="thumb"
               @mouseenter="previewSlide = slide" @mouseleave="previewSlide = null" />
          <span class="fname" :title="slide.filename">{{ slide.filename }}</span>
          <div class="move">
            <button class="btn btn-sm btn-secondary" :disabled="idx === 0" @click="move(idx, -1)" title="Move up">&uarr;</button>
            <button class="btn btn-sm btn-secondary" :disabled="idx === slides.length - 1" @click="move(idx, 1)" title="Move down">&darr;</button>
          </div>
        </div>
      </div>
    </div>

    <!--
      Hover preview. A 120x68 thumbnail is cropped (object-fit: cover) and far
      too small to read the text on a slide, so picking the right one meant
      opening images by hand. Fixed position rather than inside the row: a
      relatively-positioned parent would clip it, and the list scrolls.
      Pointer-events none so it can never sit between the cursor and a button.
    -->
    <div v-if="previewSlide" class="slide-preview">
      <img :src="withBase(previewSlide.url)" :alt="previewSlide.filename" />
      <div class="slide-preview-name">{{ previewSlide.filename }}</div>
    </div>
  </div>
</template>

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

// Slide currently hovered, shown enlarged. Null when the cursor is elsewhere.
const previewSlide = ref(null)

// Drag reordering. dragIndex is the row being carried, dropIndex the row it is
// currently over. Both null when no drag is in progress.
const dragIndex = ref(null)
const dropIndex = ref(null)

function onDragStart(idx, event) {
  dragIndex.value = idx
  // The hover preview would otherwise sit over the list for the whole drag.
  previewSlide.value = null
  if (event.dataTransfer) {
    event.dataTransfer.effectAllowed = 'move'
    // Firefox starts no drag at all unless some data is set.
    event.dataTransfer.setData('text/plain', String(idx))
  }
}

function onDragLeave(idx) {
  // Only clear when leaving the row that is actually marked, or the row being
  // entered next would clear its own highlight.
  if (dropIndex.value === idx) dropIndex.value = null
}

function onDragEnd() {
  dragIndex.value = null
  dropIndex.value = null
}

async function onDrop(target) {
  const from = dragIndex.value
  onDragEnd()
  if (from === null || from === target) return
  await reorderTo(from, target)
}

// Shared by the drag handler and the up/down buttons, so both persist the same
// way and a failure recovers the same way.
async function reorderTo(from, to) {
  const arr = slides.value.slice()
  const [item] = arr.splice(from, 1)
  arr.splice(to, 0, item)
  slides.value = arr
  try {
    await slidesApi.reorder(surface.value, arr.map(s => s.filename))
  } catch (err) {
    console.error('Reorder failed:', err)
    // The list on screen no longer matches the server; reload rather than
    // leave an order that looks saved and is not.
    await load()
  }
}

const surfaces = [
  { key: 'lobby', label: 'Lobby Display' },
  { key: 'shopfloor', label: 'Shopfloor Screensaver' }
]
const surface = ref('lobby')
const slides = ref([])
const selected = ref([])
const loading = ref(true)
const uploading = ref(false)

const surfaceLabel = computed(() => surfaces.find(s => s.key === surface.value)?.label || surface.value)

async function load() {
  loading.value = true
  selected.value = []
  try {
    const response = await slidesApi.list(surface.value)
    slides.value = response.data.data || []
  } catch (err) {
    console.error('Error loading slides:', err)
  } finally {
    loading.value = false
  }
}

function switchSurface(key) {
  if (key === surface.value) return
  surface.value = key
  load()
}

async function onUpload(event) {
  const files = Array.from(event.target.files || [])
  if (!files.length) return
  uploading.value = true
  try {
    const formData = new FormData()
    files.forEach(f => formData.append('files', f))
    await slidesApi.upload(surface.value, formData)
    await load()
  } catch (err) {
    console.error('Upload failed:', err)
  } finally {
    uploading.value = false
    event.target.value = ''
  }
}

async function move(idx, delta) {
  const target = idx + delta
  if (target < 0 || target >= slides.value.length) return
  await reorderTo(idx, target)
}

async function deleteSelected() {
  if (!selected.value.length) return
  if (!confirm(`Delete ${selected.value.length} slide(s)?`)) return
  try {
    await slidesApi.remove(surface.value, selected.value)
    await load()
  } catch (err) {
    console.error('Delete failed:', err)
  }
}

onMounted(load)
</script>

<style scoped>
.tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 16px;
}
.tab {
  padding: 10px 18px;
  border: none;
  background: none;
  color: var(--text-light);
  font-weight: 600;
  cursor: pointer;
  border-bottom: 3px solid transparent;
}
.tab.active {
  color: var(--text);
  border-bottom-color: var(--primary);
}
.toolbar {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 18px;
  flex-wrap: wrap;
}
.upload-btn { position: relative; cursor: pointer; }
.hint { color: var(--text-light); font-size: 0.85rem; }
.muted { color: var(--text-light); }
.empty { padding: 30px 0; text-align: center; }

.slide-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.slide-row {
  display: flex;
  align-items: center;
  gap: 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 8px 12px;
  background: var(--bg);
}
.slide-row .pick { flex-shrink: 0; }
.order-num {
  width: 1.6rem;
  flex-shrink: 0;
  text-align: center;
  font-weight: 700;
  color: var(--text-light);
}
.grip {
  cursor: grab;
  color: var(--text-light);
  letter-spacing: -3px;
  user-select: none;
  padding: 0 0.25rem;
}

.slide-row {
  transition: background 0.12s, opacity 0.12s;
}

.slide-row.dragging {
  opacity: 0.4;
}

/* A line on the edge being dropped against, rather than moving rows around
   under the cursor - that reads as the list fighting the drag. */
.slide-row.drop-target {
  box-shadow: inset 0 2px 0 0 var(--primary);
}

.thumb {
  width: 120px;
  height: 68px;
  flex-shrink: 0;
  object-fit: cover;
  border-radius: 4px;
  background: #000;
  cursor: zoom-in;
}

.slide-preview {
  position: fixed;
  top: 50%;
  right: 2rem;
  transform: translateY(-50%);
  z-index: 1000;
  padding: 0.5rem;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 6px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
  pointer-events: none;
}

.slide-preview img {
  display: block;
  /* Bounded by the viewport, not the image: a 3300x2550 slide would otherwise
     fill the screen. object-fit contain keeps the whole slide visible, since
     the point is reading it rather than filling the box. */
  max-width: min(46vw, 900px);
  max-height: 76vh;
  object-fit: contain;
  background: #000;
  border-radius: 4px;
}

.slide-preview-name {
  margin-top: 0.4rem;
  font-size: 0.85rem;
  color: var(--text-light);
  text-align: center;
  word-break: break-all;
}
.fname {
  flex: 1;
  min-width: 0;
  font-size: 0.82rem;
  font-family: monospace;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.move { display: flex; gap: 4px; flex-shrink: 0; }
</style>
