<template>
  <div class="apps-view pb-6">

    <!-- Nav header — tabs + categories + search, matching the Apps layout -->
    <div class="mb-4">
      <!-- Desktop: page tabs + category tabs + search on one row -->
      <div class="app-header-desktop items-center gap-4">
        <div class="flex-shrink-0">
          <div class="mode-switcher hidden md:inline-flex">
            <button
              v-for="tab in TABS"
              :key="tab.id"
              class="mode-switcher-btn"
              :class="{ 'mode-switcher-btn-active': activeTab === tab.id }"
              @click="activeTab = tab.id"
            >{{ tab.name }}</button>
          </div>
        </div>
        <div v-show="showCategories" class="mode-switcher category-tabs-wide hidden md:inline-flex">
          <button
            v-for="category in CATEGORIES"
            :key="category.id"
            @click="selectedCategory = category.id"
            class="mode-switcher-btn"
            :class="{ 'mode-switcher-btn-active': selectedCategory === category.id }"
          >{{ category.name }}</button>
        </div>
        <div class="app-header-search-wrap flex items-center gap-2">
          <input
            v-model="searchQuery"
            type="text"
            placeholder="Search your files and peer files…"
            aria-label="Search files"
            data-controller-no-submit
            class="app-header-search min-w-0 flex-1 text-white placeholder-white/50 focus:outline-none transition-colors"
          />
        </div>
      </div>

      <!-- Mobile: full-width tab switcher (distinct from the category pills),
           category pill strip, then search. .mobile-category-strip opts the
           pills out of the dashboard's swipe-to-switch-page gesture. -->
      <div class="app-header-mobile mb-4">
        <div class="cloud-tab-switcher cloud-tab-switcher-full mb-3">
          <button
            v-for="tab in TABS"
            :key="tab.id"
            @click="activeTab = tab.id"
            class="cloud-tab-btn"
            :class="{ 'cloud-tab-btn-active': activeTab === tab.id }"
            type="button"
          >{{ tab.name }}</button>
        </div>
        <div v-if="showCategories" class="mobile-category-strip mb-3" aria-label="File categories">
          <button
            v-for="category in CATEGORIES"
            :key="category.id"
            @click="selectedCategory = category.id"
            class="mobile-category-pill"
            :class="{ 'mobile-category-pill-active': selectedCategory === category.id }"
            type="button"
          >{{ category.name }}</button>
        </div>
        <input
          v-model="searchQuery"
          type="text"
          placeholder="Search your files and peer files…"
          aria-label="Search files"
          data-controller-no-submit
          class="app-header-search w-full min-w-0 text-white placeholder-white/50 focus:outline-none transition-colors"
        />
      </div>
    </div>

    <!-- ═════════════ Search results (any tab, when a query is active) ═════════════ -->
    <div v-if="searchActive">
      <div v-if="searching" class="glass-card p-8 text-center text-white/50 text-sm flex items-center justify-center gap-3">
        <svg class="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
          <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
          <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
        </svg>
        Searching your files and peers…
      </div>
      <template v-else>
        <div v-if="filteredSearchResults.length === 0" class="glass-card p-8 text-center text-white/40 text-sm">
          No files match “{{ searchQuery }}”.
        </div>
        <div v-else class="cloud-file-list">
          <template v-for="r in filteredSearchResults" :key="r.key">
            <!-- Own files act like real file rows: actions + click opens the file -->
            <FileCard
              v-if="r.item"
              :item="r.item"
              @delete="handleDelete"
              @share="handleShare"
              @play="handlePlay"
              @preview="(p: string) => handlePreview(p, searchMineItems)"
            />
            <button
              v-else
              class="w-full glass-card px-4 py-3 flex items-center gap-3 text-left hover:bg-white/10 transition-colors"
              @click="router.push({ name: 'peer-files', params: { peerId: r.peerOnion } })"
            >
              <span class="w-9 h-9 rounded-lg flex items-center justify-center shrink-0" :class="categoryMeta(r.category).iconBg">
                <svg class="w-5 h-5" :class="categoryMeta(r.category).iconColor" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path v-for="(p, i) in categoryMeta(r.category).iconPaths" :key="i" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" :d="p" />
                </svg>
              </span>
              <span class="flex-1 min-w-0">
                <span class="block text-sm text-white truncate">{{ r.name }}</span>
                <span class="block text-[11px] text-white/40 truncate">{{ r.detail }}</span>
              </span>
              <span class="text-[10px] px-2 py-0.5 rounded-full bg-purple-500/15 text-purple-400 shrink-0">{{ r.peerName }}</span>
            </button>
          </template>
        </div>
      </template>
    </div>

    <!-- ═════════════ My Files — every own file, flat, with real file actions ═════════════ -->
    <div v-else-if="activeTab === 'mine'">
      <div v-if="myFilesLoading" class="glass-card p-8 text-center text-white/50 text-sm flex items-center justify-center gap-3">
        <svg class="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
          <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
          <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
        </svg>
        Loading your files…
      </div>
      <div v-else-if="!fileBrowserRunning" class="glass-card p-8 text-center">
        <p class="text-white/60 mb-3">Install File Browser from the App Store to get started with your cloud storage.</p>
        <RouterLink to="/dashboard/marketplace" class="glass-button inline-flex items-center gap-2 px-5 py-2.5 rounded-lg text-sm font-medium">
          Open App Store
        </RouterLink>
      </div>
      <div v-else-if="filteredMyFiles.length === 0" class="glass-card p-8 text-center text-white/40 text-sm">
        {{ selectedCategory === 'all' ? 'No files yet — upload some from the Folders tab.' : 'No files in this category.' }}
      </div>
      <div v-else class="cloud-file-list">
        <FileCard
          v-for="item in filteredMyFiles"
          :key="item.path"
          :item="item"
          @delete="handleDelete"
          @share="handleShare"
          @play="handlePlay"
          @preview="(p: string) => handlePreview(p, filteredMyFiles)"
        />
      </div>
    </div>

    <!-- ═════════════ Paid Files tab — everything this node has purchased ═════════════
         Source of truth is the purchase cache (content.owned-list): filename,
         type, price paid and when — the file itself was also auto-filed into
         Photos/Music/Documents at purchase time (2026-07-22). -->
    <div v-else-if="activeTab === 'paid'">
      <div v-if="paidLoading" class="glass-card p-8 text-center text-white/50 text-sm">Loading purchases…</div>
      <div v-else-if="paidItems.length === 0" class="glass-card p-8 text-center text-white/40 text-sm">
        Nothing purchased yet — files you buy from peers appear here and are saved into your folders automatically.
      </div>
      <div v-else class="space-y-2">
        <div
          v-for="it in paidItems"
          :key="it.onion + it.content_id"
          class="glass-card p-3 flex items-center gap-3 cursor-pointer hover:bg-white/5 transition-colors"
          @click="viewPaidItem(it)"
        >
          <span class="text-xl shrink-0">{{ it.mime_type.startsWith('image/') ? '🖼️' : it.mime_type.startsWith('video/') ? '🎬' : it.mime_type.startsWith('audio/') ? '🎵' : '📄' }}</span>
          <div class="min-w-0 flex-1">
            <p class="text-sm text-white/90 truncate">{{ it.filename.split('/').pop() }}</p>
            <p class="text-[11px] text-white/40">
              {{ (it.size_bytes / 1024).toFixed(0) }} KB ·
              <span class="text-orange-300/80">{{ it.paid_sats.toLocaleString() }} sats</span>
              <span v-if="it.purchased_at"> · {{ new Date(it.purchased_at).toLocaleDateString() }}</span>
            </p>
          </div>
          <span class="text-[10px] px-2 py-0.5 rounded-full bg-emerald-400/15 text-emerald-300 shrink-0">Paid</span>
        </div>
      </div>
    </div>

    <!-- ═════════════ Peer Files tab — every file shared by every peer ═════════════ -->
    <div v-else-if="activeTab === 'peers'">
      <div v-if="peerFilesLoading" class="glass-card p-8 text-center text-white/50 text-sm flex items-center justify-center gap-3">
        <svg class="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
          <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
          <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
        </svg>
        Fetching files from {{ peerNodes.length || '' }} peer{{ peerNodes.length === 1 ? '' : 's' }}…
      </div>
      <template v-else>
        <div v-if="peerNodes.length === 0" class="glass-card p-8 text-center">
          <p class="text-white/60 mb-3">No peers yet. Set up federation to browse files shared by other nodes.</p>
          <RouterLink to="/dashboard/server/federation" class="glass-button inline-flex items-center gap-2 px-5 py-2.5 rounded-lg text-sm font-medium">
            Open Federation
          </RouterLink>
        </div>
        <div v-else-if="filteredPeerFiles.length === 0" class="glass-card p-8 text-center text-white/40 text-sm">
          {{ selectedCategory === 'all' ? 'Your peers are not sharing any files yet.' : 'No peer files in this category.' }}
        </div>
        <div v-else class="space-y-2">
          <button
            v-for="f in filteredPeerFiles"
            :key="f.key"
            class="w-full glass-card px-4 py-3 flex items-center gap-3 text-left hover:bg-white/10 transition-colors"
            @click="router.push({ name: 'peer-files', params: { peerId: f.peerOnion } })"
          >
            <span class="w-9 h-9 rounded-lg flex items-center justify-center shrink-0" :class="categoryMeta(f.category).iconBg">
              <svg class="w-5 h-5" :class="categoryMeta(f.category).iconColor" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path v-for="(p, i) in categoryMeta(f.category).iconPaths" :key="i" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" :d="p" />
              </svg>
            </span>
            <span class="flex-1 min-w-0">
              <span class="block text-sm text-white truncate">{{ f.filename }}</span>
              <span class="block text-[11px] text-white/40 truncate">{{ formatSize(f.sizeBytes) }}<template v-if="f.priceSats"> · {{ f.priceSats.toLocaleString() }} sats</template></span>
            </span>
            <span class="text-[10px] px-2 py-0.5 rounded-full bg-purple-500/15 text-purple-400 shrink-0">{{ f.peerName }}</span>
          </button>
        </div>
        <p v-if="peerFilesErrors > 0" class="text-[11px] text-white/35 text-center mt-3">
          {{ peerFilesErrors }} peer{{ peerFilesErrors === 1 ? '' : 's' }} unreachable — showing what answered.
        </p>
      </template>
    </div>

    <!-- ═════════════ Folders — section (+ peer) cards ═════════════ -->
    <div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
      <div
        v-for="section in contentSections"
        :key="section.id"
        data-controller-container
        tabindex="0"
        class="glass-card p-6 cursor-pointer transition-all hover:-translate-y-1 hover:bg-white/10"
        @click="openSection(section)"
      >
        <div class="flex items-center gap-4 mb-4">
          <div class="flex-shrink-0 w-12 h-12 rounded-xl flex items-center justify-center" :class="section.iconBg">
            <svg class="w-7 h-7" :class="section.iconColor" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path
                v-for="(path, index) in section.iconPaths"
                :key="index"
                stroke-linecap="round"
                stroke-linejoin="round"
                stroke-width="2"
                :d="path"
              />
            </svg>
          </div>
          <div class="flex-1 min-w-0">
            <h3 class="text-lg font-semibold text-white mb-0.5 truncate">{{ section.name }}</h3>
            <p class="text-xs text-white/50">{{ section.description }}</p>
          </div>
          <!-- Arrow indicator -->
          <svg class="w-5 h-5 text-white/30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
          </svg>
        </div>

        <!-- App status -->
        <div class="flex items-center gap-2 text-xs">
          <span
            class="inline-flex items-center gap-1.5 px-2 py-1 rounded-full"
            :class="isAppRunning(section.appId) ? 'bg-green-500/15 text-green-400' : 'bg-white/5 text-white/40'"
          >
            <span class="w-1.5 h-1.5 rounded-full" :class="isAppRunning(section.appId) ? 'bg-green-400' : 'bg-white/30'"></span>
            {{ section.appLabel }}
          </span>
          <span v-if="!isAppRunning(section.appId)" class="text-white/30">Not installed</span>
          <span v-else-if="countsLoading" class="text-white/30 animate-pulse">Loading...</span>
          <span v-else-if="sectionCounts[section.id] !== undefined" class="text-white/30">{{ sectionCounts[section.id] }} items</span>
        </div>
      </div>

      <!-- Individual Peer Cards -->
      <div
        v-for="peer in peerNodes"
        :key="peer.did"
        data-controller-container
        tabindex="0"
        class="glass-card p-6 cursor-pointer transition-all hover:-translate-y-1 hover:bg-white/10"
        @click="router.push({ name: 'peer-files', params: { peerId: peer.onion } })"
      >
        <div class="flex items-center gap-4 mb-4">
          <div class="flex-shrink-0 w-12 h-12 rounded-xl flex items-center justify-center bg-purple-500/15">
            <svg class="w-7 h-7 text-purple-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2" />
            </svg>
          </div>
          <div class="flex-1 min-w-0">
            <h3 class="text-lg font-semibold text-white mb-0.5 truncate" :title="peer.did">{{ peer.name || peerDisplayName(peer.did) }}</h3>
            <p class="text-xs text-white/40 truncate">{{ peer.name ? peer.did.slice(0, 20) + '...' : 'Peer node' }}</p>
          </div>
          <svg class="w-5 h-5 text-white/30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
          </svg>
        </div>
        <div class="flex items-center gap-2 text-xs">
          <span
            class="inline-flex items-center gap-1.5 px-2 py-1 rounded-full"
            :class="peer.trust_level === 'trusted' ? 'bg-green-500/15 text-green-400' : 'bg-purple-500/15 text-purple-400'"
          >
            <span class="w-1.5 h-1.5 rounded-full" :class="peer.trust_level === 'trusted' ? 'bg-green-400' : 'bg-purple-400'"></span>
            {{ peer.trust_level }}
          </span>
          <span class="text-white/30">Peer Node</span>
        </div>
      </div>

      <div
        v-if="peersLoading && peerNodes.length > 0"
        class="glass-card p-3 text-center text-white/45 text-xs md:col-span-2 lg:col-span-3 flex items-center justify-center gap-2"
      >
        <svg class="animate-spin h-3.5 w-3.5" fill="none" viewBox="0 0 24 24">
          <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
          <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
        </svg>
        Refreshing peer nodes...
      </div>

      <!-- No Peers placeholder (only if no peers found) -->
      <div
        v-if="!peersLoading && peerNodes.length === 0"
        data-controller-container
        tabindex="0"
        class="glass-card p-6 cursor-pointer transition-all hover:-translate-y-1 hover:bg-white/10"
        @click="router.push('/dashboard/server/federation')"
      >
        <div class="flex items-center gap-4 mb-4">
          <div class="flex-shrink-0 w-12 h-12 rounded-xl flex items-center justify-center bg-purple-500/15">
            <svg class="w-7 h-7 text-purple-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
            </svg>
          </div>
          <div class="flex-1 min-w-0">
            <h3 class="text-lg font-semibold text-white mb-0.5 truncate">Peer Files</h3>
            <p class="text-xs text-white/50">Set up federation to share files with peers</p>
          </div>
          <svg class="w-5 h-5 text-white/30" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
          </svg>
        </div>
        <div class="flex items-center gap-2 text-xs">
          <span class="inline-flex items-center gap-1.5 px-2 py-1 rounded-full bg-white/5 text-white/40">
            <span class="w-1.5 h-1.5 rounded-full bg-white/30"></span>
            No peers yet
          </span>
        </div>
      </div>
    </div>

    <!-- Error State -->
    <div v-if="loadError" class="alert-error mt-4">
      {{ loadError }}
    </div>

    <!-- Not Installed Hint (Folders tab only — My Files has its own) -->
    <div v-if="!fileBrowserRunning && !searchActive && activeTab === 'folders'" class="glass-card p-8 mt-6 text-center">
      <p class="text-white/60 mb-3">Install File Browser from the App Store to get started with your cloud storage.</p>
      <RouterLink to="/dashboard/marketplace" class="glass-button inline-flex items-center gap-2 px-5 py-2.5 rounded-lg text-sm font-medium">
        Open App Store
      </RouterLink>
    </div>

    <!-- Share with peers -->
    <ShareModal
      v-if="shareTarget"
      :filename="shareTarget.name"
      :filepath="shareTarget.path"
      :is-dir="shareTarget.isDir"
      @close="shareTarget = null"
      @saved="shareTarget = null"
    />

    <!-- Media viewer for own files -->
    <MediaLightbox
      v-if="lightboxIndex !== null"
      :items="lightboxItems"
      :start-index="lightboxIndex"
      :show="lightboxIndex !== null"
      :fetch-blob-url="cloudStore.fetchBlobUrl"
      :stream-url="cloudStore.streamUrl"
      @close="lightboxIndex = null"
    />
  </div>
</template>

<script setup lang="ts">
import { computed, ref, watch, onMounted } from 'vue'
import { useRouter, RouterLink } from 'vue-router'
import { useAppStore } from '../stores/app'
import { useCloudStore } from '../stores/cloud'
import { fileBrowserClient, type FileBrowserItem } from '@/api/filebrowser-client'
import { rpcClient } from '@/api/rpc-client'
import { getFileCategory } from '../composables/useFileType'
import { useAudioPlayer } from '../composables/useAudioPlayer'
import FileCard from '../components/cloud/FileCard.vue'
import ShareModal from '../components/cloud/ShareModal.vue'
import MediaLightbox from '../components/cloud/MediaLightbox.vue'

const router = useRouter()
const store = useAppStore()
const cloudStore = useCloudStore()
const audioPlayer = useAudioPlayer()
const sectionCounts = ref<Record<string, number>>({})
const countsLoading = ref(false)

// ── Tabs / categories / search state ────────────────────────────────────────
type TabId = 'folders' | 'mine' | 'peers' | 'paid'
type CategoryId = 'all' | 'photos' | 'music' | 'documents'

const TABS: Array<{ id: TabId; name: string }> = [
  { id: 'folders', name: 'Folders' },
  { id: 'mine', name: 'My Files' },
  { id: 'peers', name: 'Peer Files' },
  { id: 'paid', name: 'Paid Files' },
]
const CATEGORIES: Array<{ id: CategoryId; name: string }> = [
  { id: 'all', name: 'All' },
  { id: 'photos', name: 'Photos & Video' },
  { id: 'music', name: 'Music' },
  { id: 'documents', name: 'Documents' },
]

const activeTab = ref<TabId>('folders')

// ── Paid Files tab ──────────────────────────────────────────────────────────
interface PaidItem { onion: string; content_id: string; filename: string; mime_type: string; size_bytes: number; paid_sats: number; purchased_at: string }
const paidItems = ref<PaidItem[]>([])
const paidLoading = ref(false)
async function loadPaidItems() {
  paidLoading.value = true
  try {
    const res = await rpcClient.call<{ items: PaidItem[] }>({ method: 'content.owned-list' })
    paidItems.value = (res.items || []).slice().reverse()
  } catch { paidItems.value = [] } finally { paidLoading.value = false }
}
async function viewPaidItem(it: PaidItem) {
  try {
    const res = await rpcClient.call<{ data_base64?: string; data?: string; mime_type?: string }>({
      method: 'content.owned-get',
      params: { onion: it.onion, content_id: it.content_id },
      timeout: 60000,
    })
    const b64 = res.data_base64 || res.data
    if (!b64) return
    const bin = atob(b64)
    const arr = new Uint8Array(bin.length)
    for (let i = 0; i < bin.length; i++) arr[i] = bin.charCodeAt(i)
    const url = URL.createObjectURL(new Blob([arr], { type: res.mime_type || it.mime_type }))
    window.open(url, '_blank', 'noopener')
    setTimeout(() => URL.revokeObjectURL(url), 60000)
  } catch { /* viewer is best-effort; the file is also in the user's folders */ }
}
watch(activeTab, (t) => { if (t === 'paid') void loadPaidItems() })

const selectedCategory = ref<CategoryId>('all')
const searchQuery = ref('')
const searchActive = computed(() => searchQuery.value.trim().length > 0)
// Categories narrow file LISTS; the Folders tab is already organized by kind.
const showCategories = computed(() => activeTab.value !== 'folders' || searchActive.value)

interface PeerNode {
  did: string
  pubkey: string
  onion: string
  name?: string
  trust_level: string
}

const peerNodes = ref<PeerNode[]>([])
const peersLoading = ref(true)
const loadError = ref('')

const APP_ALIASES: Record<string, string[]> = {
  immich: ['immich_server', 'immich-server'],
  nextcloud: ['nextcloud-aio', 'nextcloud-server'],
}

function isAppRunning(appId: string): boolean {
  const packages = store.packages
  if (packages[appId]?.state === 'running') return true
  const aliases = APP_ALIASES[appId]
  if (aliases) {
    for (const alias of aliases) {
      if (packages[alias]?.state === 'running') return true
    }
  }
  return false
}

const fileBrowserRunning = computed(() => isAppRunning('filebrowser'))

interface ContentSection {
  id: string
  name: string
  description: string
  appId: string
  appLabel: string
  iconPaths: string[]
  iconBg: string
  iconColor: string
}

const contentSections: ContentSection[] = [
  {
    id: 'photos',
    name: 'Photos & Videos',
    description: 'Auto-backup & browse your media',
    appId: 'filebrowser',
    appLabel: 'File Browser',
    iconPaths: ['M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z'],
    iconBg: 'bg-blue-500/15',
    iconColor: 'text-blue-400',
  },
  {
    id: 'music',
    name: 'Music',
    description: 'Your music collection',
    appId: 'filebrowser',
    appLabel: 'File Browser',
    iconPaths: ['M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM9 10l12-3'],
    iconBg: 'bg-orange-500/15',
    iconColor: 'text-orange-400',
  },
  {
    id: 'documents',
    name: 'Documents',
    description: 'Files, docs & spreadsheets',
    appId: 'filebrowser',
    appLabel: 'File Browser',
    iconPaths: ['M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z'],
    iconBg: 'bg-green-500/15',
    iconColor: 'text-green-400',
  },
  {
    id: 'files',
    name: 'All Files',
    description: 'Browse your server file system',
    appId: 'filebrowser',
    appLabel: 'File Browser',
    iconPaths: ['M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z'],
    iconBg: 'bg-white/10',
    iconColor: 'text-white/70',
  },
]

const SECTION_PATHS: Record<string, string> = {
  photos: '/Photos',
  music: '/Music',
  documents: '/Documents',
  files: '/',
}

// ── Category helpers ─────────────────────────────────────────────────────────
function categoryOf(nameOrMime: string): Exclude<CategoryId, 'all'> {
  const s = nameOrMime.toLowerCase()
  if (s.startsWith('image/') || s.startsWith('video/') || /\.(jpe?g|png|gif|webp|heic|svg|mp4|mov|mkv|webm|avi)$/.test(s)) return 'photos'
  if (s.startsWith('audio/') || /\.(mp3|flac|wav|ogg|m4a|aac|opus)$/.test(s)) return 'music'
  return 'documents'
}

const FALLBACK_SECTION: ContentSection = contentSections[2]!
function categoryMeta(cat: Exclude<CategoryId, 'all'>): ContentSection {
  return contentSections.find(s => s.id === cat) ?? FALLBACK_SECTION
}

function formatSize(bytes: number): string {
  if (!bytes) return '—'
  if (bytes < 1024) return `${bytes} B`
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)} KB`
  if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
  return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`
}

// ── My Files (flat list of every own file across the sections) ──────────────
const myFiles = ref<FileBrowserItem[]>([])
const myFilesLoading = ref(false)
const myFilesLoaded = ref(false)

/** Depth-limited walk of the section folders; flat file list, capped. */
async function loadMyFiles(force = false) {
  if (myFilesLoading.value || (myFilesLoaded.value && !force)) return
  if (!fileBrowserRunning.value) { myFilesLoaded.value = true; return }
  myFilesLoading.value = true
  try {
    const ok = await cloudStore.init()
    if (!ok) return
    const out: FileBrowserItem[] = []
    for (const [sectionId, root] of Object.entries(SECTION_PATHS)) {
      if (sectionId === 'files') continue // '/' would double-visit the sections
      const queue: Array<{ path: string; depth: number }> = [{ path: root, depth: 0 }]
      while (queue.length > 0 && out.length < 500) {
        const { path, depth } = queue.shift()!
        let items: FileBrowserItem[]
        try { items = await fileBrowserClient.listDirectory(path) } catch { continue }
        for (const item of items) {
          const itemPath = item.path || `${path.replace(/\/$/, '')}/${item.name}`
          if (item.isDir) {
            if (depth < 3) queue.push({ path: itemPath, depth: depth + 1 })
          } else {
            out.push({ ...item, path: itemPath })
          }
        }
      }
    }
    out.sort((a, b) => a.name.localeCompare(b.name))
    myFiles.value = out
    myFilesLoaded.value = true
  } finally {
    myFilesLoading.value = false
  }
}

const filteredMyFiles = computed(() =>
  selectedCategory.value === 'all'
    ? myFiles.value
    : myFiles.value.filter(f => categoryOf(f.name) === selectedCategory.value),
)

watch(activeTab, (tab) => {
  if (tab === 'mine') void loadMyFiles()
  if (tab === 'peers') void loadPeerFiles()
  if (tab === 'folders') selectedCategory.value = 'all' // no hidden filter behind the cards
})

// ── File actions shared by My Files rows and own-file search results ────────
const shareTarget = ref<{ path: string; name: string; isDir: boolean } | null>(null)
const lightboxIndex = ref<number | null>(null)
const lightboxItems = ref<FileBrowserItem[]>([])

function handleShare(path: string, name: string, isDir: boolean) {
  shareTarget.value = { path, name, isDir }
}

/** Music opens in the global bottom-bar player (GlobalAudioPlayer in App.vue). */
async function handlePlay(path: string, name: string) {
  const url = await cloudStore.streamUrl(path)
  audioPlayer.play(url, name)
}

function handlePreview(path: string, context: FileBrowserItem[]) {
  // Audio never opens the lightbox — it belongs to the bottom-bar player.
  const clicked = context.find(item => item.path === path)
  if (clicked) {
    const ext = clicked.name.includes('.') ? clicked.name.split('.').pop()!.toLowerCase() : ''
    if (getFileCategory(ext, clicked.isDir) === 'audio') {
      void handlePlay(path, clicked.name)
      return
    }
  }
  // MediaLightbox filters to media internally; index within that filtered list.
  const mediaItems = context.filter(item => {
    const ext = item.name.includes('.') ? item.name.split('.').pop()!.toLowerCase() : ''
    const cat = getFileCategory(ext, item.isDir)
    return cat === 'image' || cat === 'video' || cat === 'audio'
  })
  const idx = mediaItems.findIndex(item => item.path === path)
  lightboxItems.value = context
  lightboxIndex.value = idx >= 0 ? idx : 0
}

async function handleDelete(path: string) {
  try {
    await cloudStore.deleteItem(path)
    myFiles.value = myFiles.value.filter(f => f.path !== path)
    searchResults.value = searchResults.value.filter(r => r.item?.path !== path)
  } catch (e) {
    loadError.value = e instanceof Error ? e.message : 'Delete failed'
  }
}

// ── Peer files (aggregated across every federation peer) ────────────────────
interface PeerFileEntry {
  key: string
  filename: string
  sizeBytes: number
  priceSats: number
  category: Exclude<CategoryId, 'all'>
  peerName: string
  peerOnion: string
}

const peerFiles = ref<PeerFileEntry[]>([])
const peerFilesLoading = ref(false)
const peerFilesLoaded = ref(false)
const peerFilesErrors = ref(0)

interface CatalogItem {
  id: string
  filename: string
  mime_type: string
  size_bytes: number
  description: string
  access: string | { paid: { price_sats: number } }
}

function priceOf(access: CatalogItem['access']): number {
  return typeof access === 'object' && access?.paid ? access.paid.price_sats : 0
}

/** Fan out content.browse-peer over every federation node; tolerate stragglers. */
async function loadPeerFiles(force = false) {
  if (peerFilesLoading.value || (peerFilesLoaded.value && !force)) return
  peerFilesLoading.value = true
  peerFilesErrors.value = 0
  try {
    if (peerNodes.value.length === 0) await loadPeers()
    const results = await Promise.allSettled(
      peerNodes.value.map(async (peer) => {
        const res = await rpcClient.call<{ items?: CatalogItem[] }>({
          method: 'content.browse-peer',
          params: { onion: peer.onion },
          timeout: 30000,
        })
        return { peer, items: res?.items ?? [] }
      }),
    )
    const merged: PeerFileEntry[] = []
    for (const r of results) {
      if (r.status !== 'fulfilled') { peerFilesErrors.value++; continue }
      const { peer, items } = r.value
      const peerName = peer.name || peerDisplayName(peer.did)
      for (const item of items) {
        merged.push({
          key: `${peer.onion}:${item.id}`,
          filename: item.filename,
          sizeBytes: item.size_bytes,
          priceSats: priceOf(item.access),
          category: categoryOf(item.mime_type || item.filename),
          peerName,
          peerOnion: peer.onion,
        })
      }
    }
    merged.sort((a, b) => a.filename.localeCompare(b.filename))
    peerFiles.value = merged
    peerFilesLoaded.value = true
  } finally {
    peerFilesLoading.value = false
  }
}

const filteredPeerFiles = computed(() =>
  selectedCategory.value === 'all'
    ? peerFiles.value
    : peerFiles.value.filter(f => f.category === selectedCategory.value),
)

// ── Search (own files + all peer files) ─────────────────────────────────────
interface SearchResult {
  key: string
  name: string
  detail: string
  category: Exclude<CategoryId, 'all'>
  item?: FileBrowserItem
  peerName?: string
  peerOnion?: string
}

const searching = ref(false)
const searchResults = ref<SearchResult[]>([])
let searchTimer: ReturnType<typeof setTimeout> | null = null
let searchSeq = 0

watch(searchQuery, () => {
  if (searchTimer) clearTimeout(searchTimer)
  if (!searchActive.value) { searchResults.value = []; searching.value = false; return }
  searching.value = true
  searchTimer = setTimeout(() => void runSearch(), 350)
})

async function runSearch() {
  const query = searchQuery.value.trim()
  const seq = ++searchSeq
  searching.value = true
  try {
    // Both corpora are cached flat lists after their first load.
    await Promise.all([loadMyFiles(), loadPeerFiles()])
    if (seq !== searchSeq) return // a newer query superseded this run
    const q = query.toLowerCase()
    const mine: SearchResult[] = myFiles.value
      .filter(f => f.name.toLowerCase().includes(q))
      .map(f => ({
        key: `mine:${f.path}`,
        name: f.name,
        detail: f.path,
        category: categoryOf(f.name),
        item: f,
      }))
    const peers: SearchResult[] = peerFiles.value
      .filter(f => f.filename.toLowerCase().includes(q))
      .map(f => ({
        key: `peer:${f.key}`,
        name: f.filename,
        detail: `${formatSize(f.sizeBytes)}${f.priceSats ? ` · ${f.priceSats.toLocaleString()} sats` : ''}`,
        category: f.category,
        peerName: f.peerName,
        peerOnion: f.peerOnion,
      }))
    searchResults.value = [...mine, ...peers]
  } finally {
    if (seq === searchSeq) searching.value = false
  }
}

const filteredSearchResults = computed(() =>
  selectedCategory.value === 'all'
    ? searchResults.value
    : searchResults.value.filter(r => r.category === selectedCategory.value),
)

/** Own-file items among the current search results (lightbox context). */
const searchMineItems = computed(() =>
  filteredSearchResults.value.flatMap(r => (r.item ? [r.item] : [])),
)

// ── Existing counts / peers loading ──────────────────────────────────────────
async function loadCounts() {
  if (!fileBrowserRunning.value) return
  countsLoading.value = true
  try {
    const ok = await fileBrowserClient.login()
    if (!ok) return
    for (const section of contentSections) {
      const path = SECTION_PATHS[section.id]
      if (!path) continue
      try {
        const items = await fileBrowserClient.listDirectory(path)
        sectionCounts.value[section.id] = items.length
      } catch {
        sectionCounts.value[section.id] = 0
      }
    }
  } catch (e) {
    loadError.value = e instanceof Error ? e.message : 'Failed to load file counts'
    if (import.meta.env.DEV) console.warn('FileBrowser count loading failed', e)
  } finally {
    countsLoading.value = false
  }
}

onMounted(() => {
  loadCounts()
  loadPeers()
})

async function loadPeers() {
  const hadPeers = peerNodes.value.length > 0
  peersLoading.value = true
  try {
    const result = await rpcClient.federationListNodes()
    peerNodes.value = result?.nodes ?? []
  } catch (e) {
    if (!hadPeers) peerNodes.value = []
    loadError.value = e instanceof Error ? e.message : 'Failed to load peer nodes'
  } finally {
    peersLoading.value = false
  }
}

function peerDisplayName(did: string): string {
  const suffix = did.replace(/^did:key:z6Mk/, '').slice(-6).toUpperCase()
  return `Node-${suffix}`
}

function openSection(section: ContentSection) {
  router.push({ name: 'cloud-folder', params: { folderId: section.id } })
}

defineExpose({ loadPeers })
</script>

<style scoped>
/* Mobile-only top-level tab switcher — deliberately a DIFFERENT look from the
   neutral category pills below it (orange tint). Desktop keeps the standard
   mode-switcher styling. */
.cloud-tab-switcher {
  gap: 2px;
  padding: 3px;
  border-radius: 0.5rem;
  background: rgba(247, 147, 26, 0.08);
  border: 1px solid rgba(247, 147, 26, 0.22);
}

.cloud-tab-btn {
  padding: 0.45rem 0.9rem;
  border-radius: 0.375rem;
  border: none;
  background: transparent;
  color: rgba(255, 255, 255, 0.6);
  font-size: 0.8rem;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.cloud-tab-btn:hover {
  color: rgba(255, 255, 255, 0.9);
}

.cloud-tab-btn-active {
  background: rgba(247, 147, 26, 0.25);
  color: #ffd9a8;
}

/* Mobile: the three tabs share the full width equally. */
.cloud-tab-switcher-full {
  display: flex;
  width: 100%;
}

.cloud-tab-switcher-full .cloud-tab-btn {
  flex: 1 1 0;
  text-align: center;
  padding: 0.6rem 0.25rem;
}
</style>
