<template>
  <main class="library">
    <PullToRefresh :is-at-top="isAtTop" :on-refresh="refreshLibrary">
    <!-- Top action bar -->
    <div class="library__header">
      <BaseButton variant="primary" class="library__add-btn" @click="onAddPhoto">
        + Add Photo
      </BaseButton>
    </div>
    <!-- Tabs -->
    <div class="library__tabs" role="tablist">
      <button
        v-for="tab in TABS"
        :key="tab.id"
        type="button"
        role="tab"
        :aria-selected="activeTab === tab.id"
        :class="['library__tab', { 'library__tab--active': activeTab === tab.id }]"
        @click="activeTab = tab.id"
      >{{ tab.label }}</button>
    </div>

    <!-- Loading -->
    <div v-if="imagesStore.loading" class="library__loading">Loading…</div>

    <!-- All / Mine tab -->
    <template v-else-if="activeTab !== 'shared'">
      <div v-if="visibleImages.length === 0" class="library__empty">
        <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
          <rect x="3" y="3" width="18" height="18" rx="2"/>
          <circle cx="8.5" cy="8.5" r="1.5"/>
          <polyline points="21,15 16,10 5,21"/>
        </svg>
        <p class="library__empty-title">No photos yet</p>
        <p class="library__empty-sub">Tap "+ Add Photo" above to upload your first one.</p>
      </div>

      <div v-else class="library__grid">
        <div v-for="image in visibleImages" :key="image.id" class="library__item">
          <div class="library__thumb">
            <img
              :src="image.thumbnailUrl"
              :alt="image.originalFilename"
              class="library__img"
              loading="lazy"
            />
            <div
              v-if="lockedDeviceFor(image)"
              class="library__thumb-lock"
              :title="`Locked on ${lockedDeviceFor(image)!.name}`"
              aria-hidden="true"
            >
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true">
                <rect x="3" y="11" width="18" height="11" rx="2" ry="2"/>
                <path d="M7 11V7a5 5 0 0 1 10 0v4"/>
              </svg>
            </div>
            <div class="library__thumb-actions">
              <button
                v-if="mismatchedDevice(image)"
                class="library__action-btn library__action-btn--warn"
                type="button"
                :aria-label="`Crop orientation does not match ${mismatchedDevice(image)!.name}; tap to re-crop`"
                :title="`Cropped ${photoCropOrientation(image)}, but ${mismatchedDevice(image)!.name} is set to ${mismatchedDevice(image)!.orientation}.`"
                @click="startEdit(image, mismatchedDevice(image)!.id)"
              >
                <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true">
                  <path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/>
                  <line x1="12" y1="9" x2="12" y2="13"/>
                  <line x1="12" y1="17" x2="12.01" y2="17"/>
                </svg>
              </button>
              <button
                class="library__action-btn"
                type="button"
                :aria-label="`Edit ${image.originalFilename}`"
                :disabled="editingId === image.id"
                @click="startEdit(image)"
              >
                <svg v-if="editingId !== image.id" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true">
                  <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/>
                  <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/>
                </svg>
                <svg v-else width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
                  <circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/>
                </svg>
              </button>
              <button
                class="library__action-btn"
                type="button"
                :aria-label="`Share ${image.originalFilename}`"
                @click="openShare(image.id)"
              >
                <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true">
                  <circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/>
                  <line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/>
                  <line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/>
                </svg>
              </button>
              <button
                class="library__action-btn library__action-btn--danger"
                type="button"
                aria-label="Delete photo"
                @click="confirmDelete(image.id)"
              >
                <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true">
                  <polyline points="3 6 5 6 21 6"/>
                  <path d="M19 6l-1 14H6L5 6"/>
                  <path d="M10 11v6M14 11v6"/>
                  <path d="M9 6V4h6v2"/>
                </svg>
              </button>
            </div>
          </div>

          <button
            v-if="devicesStore.devices.length > 0"
            type="button"
            class="library__manage"
            :aria-label="`Manage frames for ${image.originalFilename}`"
            @click="openManage(image)"
          >
            <span class="library__manage-summary">
              <b>{{ image.approvedDeviceIds.length }}</b>/{{ devicesStore.devices.length }}
              {{ devicesStore.devices.length === 1 ? 'frame' : 'frames' }}
              <span
                v-if="lockedDeviceFor(image)"
                class="library__manage-lock"
              >· 🔒 {{ lockedDeviceFor(image)!.name }}</span>
            </span>
            <span class="library__manage-action">Manage ▸</span>
          </button>
        </div>
      </div>
    </template>

    <!-- Shared tab -->
    <template v-else>
      <!-- Sub-tabs -->
      <div class="library__subtabs" role="tablist">
        <button
          v-for="st in SHARED_TABS"
          :key="st.id"
          type="button"
          role="tab"
          :aria-selected="sharedTab === st.id"
          :class="['library__subtab', { 'library__subtab--active': sharedTab === st.id }]"
          @click="switchSharedTab(st.id)"
        >{{ st.label }}</button>
      </div>

      <div v-if="sharedLoading" class="library__loading">Loading…</div>

      <div v-else-if="sharedItems.length === 0" class="library__shared-empty">
        <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
          <circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/>
          <line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/>
          <line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/>
        </svg>
        <p class="library__empty-title">
          {{ sharedTab === 'pending' ? 'No pending photos' : sharedTab === 'approved' ? 'No approved photos' : 'No declined photos' }}
        </p>
        <p class="library__empty-sub">
          {{ sharedTab === 'pending' ? 'Photos shared with you will appear here.' : 'Photos you\'ve added to a frame will appear here.' }}
        </p>
      </div>

      <div v-else class="library__shared-list">
        <ApproveCard
          v-for="item in sharedItems"
          :key="item.id"
          :item="item"
          @updated="onSharedUpdated"
        />
      </div>

      <!-- Pagination -->
      <div v-if="sharedTotalPages > 1" class="library__pagination">
        <button
          class="library__page-btn"
          :disabled="sharedPage <= 1"
          @click="goSharedPage(sharedPage - 1)"
        >← Prev</button>
        <span class="library__page-info">{{ sharedPage }} / {{ sharedTotalPages }}</span>
        <button
          class="library__page-btn"
          :disabled="sharedPage >= sharedTotalPages"
          @click="goSharedPage(sharedPage + 1)"
        >Next →</button>
      </div>
    </template>
    </PullToRefresh>

    <!-- Share sheet -->
    <ShareSheet v-if="shareImageId !== null" v-model="shareSheetOpen" :image-id="shareImageId!" />

    <ManageImageSheet
      v-model="manageSheetOpen"
      :image="manageImage"
      :devices="devicesStore.devices"
      @approval="onManageApproval"
      @lock="onManageLock"
    />

    <!-- Confirm delete sheet -->
    <BaseBottomSheet v-model="deleteSheetOpen" label="Delete photo">
      <h2 class="library__sheet-title">Delete this photo?</h2>
      <p class="library__sheet-sub">It will be removed from all frames.</p>
      <div class="library__sheet-actions">
        <BaseButton variant="secondary" @click="deleteSheetOpen = false">Cancel</BaseButton>
        <BaseButton variant="destructive" :disabled="deleting" @click="doDelete">
          {{ deleting ? 'Deleting…' : 'Delete' }}
        </BaseButton>
      </div>
    </BaseBottomSheet>

    <!-- Hidden persistent file picker — see the matching block in HomeView
         for why this isn't a one-shot document.createElement('input'). -->
    <input
      ref="fileInputEl"
      type="file"
      accept="image/jpeg,image/png,image/webp,image/gif"
      hidden
      @change="onFileSelected"
    />
  </main>
</template>

<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useImagesStore } from '@/stores/images'
import { useDevicesStore } from '@/stores/devices'
import { useUploadStore } from '@/stores/upload'
import { useToastStore } from '@/stores/toast'
import BaseBottomSheet from '@/components/BaseBottomSheet.vue'
import BaseButton from '@/components/BaseButton.vue'
import ApproveCard from '@/components/ApproveCard.vue'
import ManageImageSheet from '@/components/ManageImageSheet.vue'
import ShareSheet from '@/components/ShareSheet.vue'
import PullToRefresh from '@/components/PullToRefresh.vue'
import type { Device, Image, SharedImage } from '@/types'

const router       = useRouter()
const imagesStore  = useImagesStore()
const devicesStore = useDevicesStore()
const uploadStore  = useUploadStore()
const toast        = useToastStore()
const route        = useRoute()

const TABS = [
  { id: 'all',    label: 'All'    },
  { id: 'mine',   label: 'Mine'   },
  { id: 'shared', label: 'Shared' },
] as const
type Tab = typeof TABS[number]['id']

function tabFromQuery(value: unknown): Tab {
  return TABS.some(t => t.id === value) ? (value as Tab) : 'all'
}

const activeTab = ref<Tab>(tabFromQuery(route.query.tab))

watch(() => route.query.tab, (next) => {
  const tab = tabFromQuery(next)
  if (tab === activeTab.value) return
  activeTab.value = tab
  if (tab === 'shared') loadShared(sharedTab.value)
})

const SHARED_TABS = [
  { id: 'pending',  label: 'Pending'  },
  { id: 'approved', label: 'Approved' },
  { id: 'declined', label: 'Declined' },
] as const
type SharedTab = typeof SHARED_TABS[number]['id']

const sharedTab        = ref<SharedTab>('pending')
const sharedItems      = ref<SharedImage[]>([])
const sharedLoading    = ref(false)
const sharedPage       = ref(1)
const sharedTotalPages = ref(1)

async function loadShared(tab: SharedTab, page = 1) {
  sharedLoading.value = true
  try {
    const result = await imagesStore.fetchSharedImages(tab, page)
    sharedItems.value      = result.items
    sharedPage.value       = result.page
    sharedTotalPages.value = result.totalPages
  } finally {
    sharedLoading.value = false
  }
}

function switchSharedTab(tab: SharedTab) {
  sharedTab.value = tab
  loadShared(tab, 1)
}

function goSharedPage(page: number) {
  loadShared(sharedTab.value, page)
}

function onSharedUpdated(updated: SharedImage) {
  const idx = sharedItems.value.findIndex(i => i.id === updated.id)
  if (idx !== -1) sharedItems.value[idx] = updated
}

onMounted(() => {
  imagesStore.fetchImages()
  devicesStore.fetchDevices()
  imagesStore.fetchPendingCount()
  if (activeTab.value === 'shared') loadShared(sharedTab.value)
})

// ── Add Photo ─────────────────────────────────────────────────────────────────

// Hidden file <input> lives in the template (see HomeView for context).
const fileInputEl = ref<HTMLInputElement | null>(null)

function onAddPhoto() {
  // File picker must be triggered in the user-gesture context (the click
  // handler), otherwise browsers block it as a popup.
  fileInputEl.value?.click()
}

function onFileSelected(e: Event) {
  const input = e.target as HTMLInputElement
  const file  = input.files?.[0]
  // Reset so re-selecting the same file later still fires change.
  input.value = ''
  if (!file) return
  uploadStore.init(file)
  router.push('/upload')
}

// ── Pull-to-refresh ───────────────────────────────────────────────────────────

function isAtTop(): boolean {
  return window.scrollY === 0
}

async function refreshLibrary() {
  await Promise.all([
    imagesStore.fetchImages({ silent: true }),
    imagesStore.fetchPendingCount(),
    devicesStore.fetchDevices({ silent: true }),
    activeTab.value === 'shared' ? loadShared(sharedTab.value, sharedPage.value) : Promise.resolve(),
  ])
}

// For now "mine" and "all" show the same list; shared is a placeholder
const visibleImages = computed(() => imagesStore.images)

// ── Share ─────────────────────────────────────────────────────────────────────

const shareSheetOpen = ref(false)
const shareImageId   = ref<number | null>(null)

function openShare(id: number) {
  shareImageId.value   = id
  shareSheetOpen.value = true
}

// ── Edit ──────────────────────────────────────────────────────────────────────

const editingId = ref<number | null>(null)

async function startEdit(image: Image, deviceId?: number) {
  if (editingId.value) return
  editingId.value = image.id
  try {
    await uploadStore.initEdit(image, deviceId)
    router.push('/upload')
  } catch {
    toast.show('Could not load photo for editing', 'error')
  } finally {
    editingId.value = null
  }
}

// ── Orientation mismatch ──────────────────────────────────────────────────────

// Photo's effective crop orientation. Prefers the explicit cropOrientation
// field; falls back to inferring from cropParams aspect for legacy uploads
// predating the field. Returns null when neither is available.
function photoCropOrientation(image: Image): 'landscape' | 'portrait' | null {
  if (image.cropOrientation) return image.cropOrientation
  const p = image.cropParams
  if (!p?.natW || !p?.natH) return null
  return p.natW >= p.natH ? 'landscape' : 'portrait'
}

// First approved device whose orientation doesn't match the photo's crop
// orientation. Drives the warning triangle and the click target for re-crop.
function mismatchedDevice(image: Image): Device | null {
  const photoOri = photoCropOrientation(image)
  if (!photoOri) return null
  for (const id of image.approvedDeviceIds) {
    const d = devicesStore.devices.find(dev => dev.id === id)
    if (d && d.orientation !== photoOri) return d
  }
  return null
}

// ── Lock + approval helpers ──────────────────────────────────────────────────
// Curation happens through the ManageImageSheet — see openManage(). These
// helpers stay around because the sheet emits one-shot events the parent
// pushes to the stores.

/** First device that currently has this image locked, if any. Drives the
 *  corner-lock badge on the thumb and the "🔒 [name]" suffix in the status
 *  badge. Multiple-lock case shows the first match. */
function lockedDeviceFor(image: Image): Device | null {
  return devicesStore.devices.find(d => d.lockedImageId === image.id) ?? null
}

async function applyApproval(imageId: number, deviceId: number, approved: boolean) {
  try {
    await imagesStore.setApproval(imageId, deviceId, approved)
  } catch {
    toast.show('Failed to update frame approval', 'error')
  }
}

async function applyLock(imageId: number, deviceId: number, locked: boolean) {
  try {
    if (locked) {
      await devicesStore.lockImage(deviceId, imageId)
    } else {
      await devicesStore.unlockImage(deviceId)
    }
  } catch {
    toast.show('Failed to update lock', 'error')
  }
}

// ── Manage sheet (per-photo, all-device approve + lock) ─────────────────────

const manageSheetOpen = ref(false)
const manageImageId   = ref<number | null>(null)

/** Live-bound to the store so toggles inside the sheet update the row
 *  immediately as the store mutates, without re-mount. */
const manageImage = computed<Image | null>(() =>
  manageImageId.value === null
    ? null
    : imagesStore.images.find(i => i.id === manageImageId.value) ?? null,
)

function openManage(image: Image) {
  manageImageId.value   = image.id
  manageSheetOpen.value = true
}

function onManageApproval(p: { imageId: number; deviceId: number; approved: boolean }) {
  applyApproval(p.imageId, p.deviceId, p.approved)
}

function onManageLock(p: { imageId: number; deviceId: number; locked: boolean }) {
  applyLock(p.imageId, p.deviceId, p.locked)
}

// ── Delete ────────────────────────────────────────────────────────────────────

const deleteSheetOpen = ref(false)
const deletingId      = ref<number | null>(null)
const deleting        = ref(false)

function confirmDelete(id: number) {
  deletingId.value      = id
  deleteSheetOpen.value = true
}

async function doDelete() {
  if (!deletingId.value) return
  deleting.value = true
  try {
    await imagesStore.deleteImage(deletingId.value)
    deleteSheetOpen.value = false
    toast.show('Photo deleted', 'success')
  } catch {
    toast.show('Delete failed', 'error')
  } finally {
    deleting.value = false
  }
}
</script>

<style scoped lang="scss">
.library {
  padding-bottom: calc(var(--bottom-nav-height) + var(--space-4));

  // Desktop: wider container for grid, but capped; bottom nav is hidden so
  // the calc value above is harmless padding.
  @media (min-width: 960px) {
    max-width: 1100px;
    margin: 0 auto;
    width: 100%;
    padding-bottom: var(--space-8);
  }

  &__header {
    padding: var(--space-4) var(--space-4) var(--space-3);

    @media (min-width: 960px) {
      display: flex;
      align-items: center;
      gap: var(--space-4);
    }
  }

  &__add-btn {
    width: 100%;

    @media (min-width: 960px) {
      width: auto;
    }
  }

  &__tabs {
    display: flex;
    border-bottom: 1px solid var(--color-border);
    position: sticky;
    top: env(safe-area-inset-top);
    background: var(--color-bg);
    z-index: 5;
  }

  &__tab {
    flex: 1;
    padding: var(--space-3) 0;
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--color-text-muted);
    border: none;
    background: none;
    cursor: pointer;
    border-bottom: 2px solid transparent;
    transition: all var(--duration-fast);

    &--active {
      color: var(--color-primary);
      border-bottom-color: var(--color-primary);
    }
  }

  &__loading {
    color: var(--color-text-muted);
    font-size: var(--text-sm);
    padding: var(--space-4);
    text-align: center;
  }

  &__subtabs {
    display: flex;
    border-bottom: 1px solid var(--color-border);
    background: var(--color-bg);
  }

  &__subtab {
    flex: 1;
    padding: var(--space-2) 0;
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--color-text-muted);
    border: none;
    background: none;
    cursor: pointer;
    border-bottom: 2px solid transparent;
    transition: all var(--duration-fast);

    &--active {
      color: var(--color-primary);
      border-bottom-color: var(--color-primary);
    }
  }

  &__shared-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding: var(--space-4);
  }

  &__pagination {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-3);
    padding: var(--space-4);
  }

  &__page-btn {
    padding: var(--space-2) var(--space-3);
    border: 1.5px solid var(--color-border);
    border-radius: var(--radius-md);
    background: none;
    font-size: var(--text-sm);
    font-weight: 600;
    color: var(--color-text);
    cursor: pointer;
    &:disabled { opacity: .4; cursor: default; }
  }

  &__page-info {
    font-size: var(--text-sm);
    color: var(--color-text-muted);
  }

  &__empty, &__shared-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-6) var(--space-4);
    color: var(--color-text-muted);
    text-align: center;
  }

  &__empty-title {
    font-size: var(--text-md);
    font-weight: 700;
    color: var(--color-text);
  }

  &__empty-sub {
    font-size: var(--text-sm);
    max-width: 280px;
    line-height: 1.5;
  }

  &__grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-3);
    padding: var(--space-4);
  }

  &__item {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
  }

  // Square cells with object-fit: contain so both portrait and landscape
  // photos render in full (no aggressive crop); grid stays uniform; aspect
  // mismatches show as symmetric letterbox bars top/bottom or left/right.
  &__thumb {
    position: relative;
    aspect-ratio: 1/1;
    border-radius: var(--radius-sm);
    overflow: hidden;
    background: var(--color-surface-2);
  }

  &__img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    display: block;
  }

  &__thumb-actions {
    position: absolute;
    top: var(--space-2);
    right: var(--space-2);
    display: flex;
    gap: 4px;
  }

  &__action-btn {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.55);
    border: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    transition: background var(--duration-fast);
    flex-shrink: 0;

    &:disabled { opacity: 0.5; cursor: default; }
    &:hover:not(:disabled) { background: rgba(0, 0, 0, 0.75); }
    &--danger:hover:not(:disabled) { background: rgba(180, 0, 0, 0.8); }

    &--warn {
      background: var(--color-warning, #f59e0b);
      &:hover:not(:disabled) { background: color-mix(in srgb, var(--color-warning, #f59e0b) 85%, black); }
    }
  }

  // Locked-on-some-frame badge tucked into the bottom-left of the thumb.
  // Visual mirror of the per-device lock chip on the manage sheet.
  &__thumb-lock {
    position: absolute;
    left: var(--space-2);
    bottom: var(--space-2);
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: #c49a20;
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
    pointer-events: none;
  }

  // Single tappable summary row beneath the thumb — opens the manage sheet.
  // Replaces the per-device approval + lock chip grids that didn't scale
  // beyond a few frames (see _bmad-output/.../library-many-frames-design-ideas.md).
  &__manage {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    background: transparent;
    border: 0;
    padding: var(--space-2) var(--space-1);
    cursor: pointer;
    text-align: left;
    border-radius: var(--radius-sm);
    transition: background var(--duration-fast);

    &:hover { background: var(--color-surface-2); }
  }

  &__manage-summary {
    font-size: var(--text-xs);
    color: var(--color-text-muted);
    b { color: var(--color-text); font-weight: 700; }
  }

  &__manage-lock {
    color: #6b5210;
    font-weight: 600;
  }

  &__manage-action {
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--color-primary);
    white-space: nowrap;
  }

  &__sheet-title {
    font-size: var(--text-md);
    font-weight: 700;
    margin-bottom: var(--space-2);
  }

  &__sheet-sub {
    font-size: var(--text-sm);
    color: var(--color-text-muted);
    margin-bottom: var(--space-5);
  }

  &__sheet-actions {
    display: flex;
    gap: var(--space-3);
    > * { flex: 1; }
  }
}
</style>
