<template>
  <div class="marketplace-container">
    <!-- Header Section -->
    <div>
    <!-- Installation Progress Banner - Multiple Apps -->
    <div v-if="installingApps.size > 0" aria-live="polite" class="mb-6 space-y-3">
      <div 
        v-for="[appId, progress] in installingApps" 
        :key="appId"
        class="glass-card p-4 border-l-4"
        :class="{
          'border-blue-500': progress.status === 'downloading' || progress.status === 'installing',
          'border-orange-500': progress.status === 'starting',
          'border-green-500': progress.status === 'complete',
          'border-red-500': progress.status === 'error'
        }"
      >
        <div class="flex items-center justify-between mb-3">
          <div class="flex items-center gap-3">
            <svg
              v-if="progress.status !== 'complete' && progress.status !== 'error'"
              class="animate-spin h-5 w-5 text-blue-400"
              aria-hidden="true"
              xmlns="http://www.w3.org/2000/svg"
              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>
            <svg 
              v-else-if="progress.status === 'complete'"
              class="h-5 w-5 text-green-400" 
              xmlns="http://www.w3.org/2000/svg" 
              fill="none" 
              viewBox="0 0 24 24" 
              stroke="currentColor"
            >
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
            </svg>
            <svg 
              v-else
              class="h-5 w-5 text-red-400" 
              xmlns="http://www.w3.org/2000/svg" 
              fill="none" 
              viewBox="0 0 24 24" 
              stroke="currentColor"
            >
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
            </svg>
            <div>
              <p class="text-white font-medium">{{ progress.title }}</p>
              <p class="text-white/70 text-sm">{{ progress.message }}</p>
            </div>
          </div>
          <div class="text-white/60 text-sm">
            {{ progress.progress }}%
          </div>
        </div>
        
        <!-- Progress Bar -->
        <div class="w-full bg-white/10 rounded-full h-2 overflow-hidden">
          <div 
            class="h-full rounded-full transition-all duration-500"
            :class="{
              'bg-gradient-to-r from-blue-500 to-blue-400': progress.status === 'downloading' || progress.status === 'installing',
              'bg-gradient-to-r from-orange-500 to-orange-400': progress.status === 'starting',
              'bg-gradient-to-r from-green-500 to-green-400': progress.status === 'complete',
              'bg-gradient-to-r from-red-500 to-red-400': progress.status === 'error'
            }"
            :style="{ width: `${progress.progress}%` }"
          ></div>
        </div>
      </div>
    </div>

      <div class="hidden md:flex mb-8 items-start justify-between gap-4">
        <div>
          <h1 class="text-3xl font-bold text-white mb-2">{{ t('marketplace.title') }}</h1>
          <p class="text-white/70">{{ t('marketplace.subtitle') }}</p>
        </div>
        <div class="mode-switcher flex-shrink-0">
          <RouterLink to="/dashboard/apps" class="mode-switcher-btn">My Apps</RouterLink>
          <RouterLink to="/dashboard/marketplace" class="mode-switcher-btn mode-switcher-btn-active">App Store</RouterLink>
        </div>
      </div>

      <!-- Category Tabs + Search (Desktop only) -->
      <div class="hidden md:flex mb-6 items-center justify-between gap-4">
        <div class="mode-switcher">
          <button
            v-for="category in categoriesWithApps"
            :key="category.id"
            @click="selectCategory(category.id)"
            class="mode-switcher-btn"
            :class="{ 'mode-switcher-btn-active': selectedCategory === category.id }"
          >
            {{ category.name }}
            <span v-if="category.id === 'nostr' && nostrApps.length > 0" class="ml-1 text-xs px-1.5 py-0.5 rounded-full bg-white/10">+{{ nostrApps.length }}</span>
          </button>
        </div>
        <input
          v-model="searchQuery"
          type="text"
          :placeholder="t('marketplace.searchPlaceholder')"
          :aria-label="t('marketplace.searchApps')"
          class="flex-shrink-0 w-64 px-4 py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:outline-none focus:border-white/40 transition-colors"
        />
      </div>

      <!-- Mobile: Search only (categories handled by floating filter modal) -->
      <div class="md:hidden mb-4">
        <input
          v-model="searchQuery"
          type="text"
          :placeholder="t('marketplace.searchPlaceholder')"
          :aria-label="t('marketplace.searchApps')"
          class="w-full px-4 py-3 md:py-2 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:outline-none focus:border-white/40 transition-colors"
        />
      </div>
    </div>

    <!-- Scrollable Apps Section -->
    <div class="pb-8">
      <!-- Apps Grid -->
    <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
      <div
          v-for="(app, index) in filteredApps"
        :key="app.id"
          data-controller-container
          :data-controller-install="!(isInstalled(app.id) || installingApps.has(app.id)) && (app.source === 'local' || !!app.dockerImage) ? '1' : undefined"
          tabindex="0"
          role="link"
          class="glass-card card-stagger p-6 hover:bg-white/10 transition-all cursor-pointer flex flex-col"
          :style="{ '--stagger-index': index }"
          @click="viewAppDetails(app)"
          @keydown.enter="viewAppDetails(app)"
      >
        <div class="flex items-start gap-4 mb-4">
        <img
              v-if="app.icon"
          :src="app.icon"
          :alt="app.title"
            class="w-16 h-16 rounded-lg object-cover"
            @error="handleImageError"
          />
            <div v-else class="w-16 h-16 rounded-lg bg-white/10 flex items-center justify-center">
              <svg class="w-8 h-8 text-white/40" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 7l-8-4-8 4m16 0l-8 4m8-4v10l-8 4m0-10L4 7m8 4v10M4 7v10l8 4" />
              </svg>
            </div>
          <div class="flex-1">
            <h3 class="text-xl font-semibold text-white mb-1">
              {{ app.title }}
              <span
                v-if="getAppTier(app.id) !== 'optional'"
                class="tier-badge"
                :class="getAppTier(app.id) === 'core' ? 'tier-badge-core' : 'tier-badge-recommended'"
              >{{ getAppTier(app.id) }}</span>
            </h3>
              <p class="text-sm text-white/60">{{ app.version ? `v${app.version}` : 'latest' }}</p>
              <p v-if="app.author" class="text-xs text-white/50 mt-1">by {{ app.author }}</p>
          </div>
        </div>

          <!-- Trust badge for Nostr community apps -->
          <div v-if="app.trustTier" class="flex items-center gap-2 mb-3">
            <span
              class="text-xs px-2 py-0.5 rounded-full font-medium"
              :class="{
                'bg-green-400/20 text-green-400': app.trustTier === 'verified',
                'bg-yellow-400/20 text-yellow-400': app.trustTier === 'community',
                'bg-orange-400/20 text-orange-400': app.trustTier === 'unverified',
                'bg-red-400/20 text-red-400': app.trustTier === 'untrusted',
              }"
            >{{ app.trustTier }}</span>
            <span class="text-xs text-white/40">Score: {{ app.trustScore }}/100</span>
            <span v-if="app.relayCount" class="text-xs text-white/40">&middot; {{ app.relayCount }} relay{{ app.relayCount !== 1 ? 's' : '' }}</span>
          </div>

          <p class="text-white/80 text-sm mb-4 line-clamp-3 flex-1">
            {{ typeof app.description === 'object' ? app.description.short : (app.description || 'No description available') }}
          </p>
        
          <div class="flex gap-2 mt-auto">
        <span
          v-if="isInstalled(app.id)"
              class="flex-1 px-4 py-2 bg-white/20 rounded-lg text-white/60 text-sm font-medium text-center cursor-default"
        >
          {{ t('marketplace.alreadyInstalled') }}
        </span>
            <button
              v-if="isInstalled(app.id)"
              @click.stop="launchInstalledApp(app)"
              class="px-4 py-2 glass-button glass-button-sm rounded-lg text-sm font-medium"
            >
              {{ t('common.launch') }}
            </button>
            <button
              v-else-if="app.source === 'local' || app.dockerImage"
              data-controller-install-btn
              @click.stop="app.source === 'local' ? installApp(app) : installCommunityApp(app)"
          :disabled="installingApps.has(app.id)"
              class="flex-1 px-4 py-2 glass-button glass-button-sm rounded-lg text-sm font-medium disabled:opacity-50 disabled:cursor-not-allowed"
        >
          <span v-if="installingApps.has(app.id)" class="flex items-center justify-center gap-2">
            <svg class="animate-spin h-4 w-4" xmlns="http://www.w3.org/2000/svg" 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>
            {{ installingApps.get(app.id)?.message || t('common.installing') }}
          </span>
          <span v-else>{{ t('common.install') }}</span>
            </button>
            <button
              v-else
              disabled
              class="flex-1 px-4 py-2 bg-white/10 rounded-lg text-white/40 text-sm font-medium cursor-not-allowed"
            >
              {{ t('common.notAvailable') }}
            </button>
          </div>
        </div>
      </div>

      <!-- Empty State -->
      <div v-if="filteredApps.length === 0" class="text-center py-12">
        <div v-if="loadingCommunity || nostrLoading" class="flex flex-col items-center gap-4">
          <svg class="animate-spin h-12 w-12 text-blue-400" xmlns="http://www.w3.org/2000/svg" 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>
          <p class="text-white/70">{{ nostrLoading ? t('marketplace.queryingRelays') : t('common.loading') }}</p>
        </div>
        <div v-else-if="nostrError && selectedCategory === 'nostr'" class="flex flex-col items-center gap-4">
          <p class="text-white/70">{{ t('marketplace.noCommunityApps') }}</p>
          <p class="text-white/40 text-sm">{{ nostrError }}</p>
          <button @click="nostrApps = []; loadNostrMarketplace()" class="px-4 py-2 glass-button rounded-lg text-sm">{{ t('common.retry') }}</button>
        </div>
        <p v-else class="text-white/70">{{ searchQuery && selectedCategory !== 'all' ? t('marketplace.noResults', { category: categories.find(c => c.id === selectedCategory)?.name, query: searchQuery }) : searchQuery ? t('marketplace.noResultsSearch', { query: searchQuery }) : t('marketplace.noResultsCategory', { category: categories.find(c => c.id === selectedCategory)?.name }) }}</p>
      </div>
          </div>
    <!-- End Scrollable Apps Section -->

    <!-- Floating Filter Button (teleported to escape CSS transform containing block) -->
    <Teleport to="body">
      <button
        @click="showFilterModal = true"
        class="md:hidden fixed right-4 z-40 w-14 h-14 rounded-full glass-button flex items-center justify-center shadow-2xl mobile-back-btn"
        style="left: auto;"
      >
        <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
        </svg>
      </button>
    </Teleport>

    <!-- Filter Modal (Mobile only) -->
    <Transition name="modal">
      <div
        v-if="showFilterModal"
        class="fixed inset-0 z-50 flex items-end justify-center md:hidden bg-black/60 backdrop-blur-sm"
        @click.self="closeFilterModal()"
      >
        <div ref="filterModalRef" class="glass-card p-6 w-full rounded-t-3xl max-h-[80vh] overflow-y-auto">
          <!-- Header -->
          <div class="flex items-center justify-between mb-6">
            <h2 class="text-2xl font-bold text-white">{{ t('marketplace.filterByCategory') }}</h2>
            <button
              @click="closeFilterModal()"
              class="text-white/60 hover:text-white transition-colors"
            >
              <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
              </svg>
            </button>
          </div>

          <!-- Category Grid -->
          <div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
            <button
              v-for="category in categoriesWithApps"
              :key="category.id"
              @click="selectCategory(category.id); closeFilterModal()"
              :class="[
                'p-4 rounded-xl font-medium transition-all text-left',
                selectedCategory === category.id
                  ? 'bg-white/20 text-white border-2 border-white/40'
                  : 'glass-card text-white/80 hover:bg-white/10'
              ]"
            >
              <div class="flex items-center gap-3">
                <!-- Category Icon -->
                <div class="flex-shrink-0 w-10 h-10 rounded-lg bg-white/10 flex items-center justify-center">
                  <svg v-if="category.id === 'all'" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
                  </svg>
                  <svg v-else-if="category.id === 'community'" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
                  </svg>
                  <svg v-else-if="category.id === 'nostr'" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
                  </svg>
                  <svg v-else-if="category.id === 'commerce'" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" />
                  </svg>
                  <svg v-else-if="category.id === 'money'" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
                  </svg>
                  <svg v-else-if="category.id === 'data'" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
                  </svg>
                  <svg v-else-if="category.id === 'home'" class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
                  </svg>
                  <svg v-else-if="category.id === 'car'" class="w-6 h-6" viewBox="0 0 122.88 122.88" fill="currentColor">
                    <path d="M61.44,0c33.93,0,61.44,27.51,61.44,61.44c0,33.93-27.51,61.44-61.44,61.44S0,95.37,0,61.44 C0,27.51,27.51,0,61.44,0L61.44,0z M61.17,61.6c1.76,0,3.18,1.42,3.18,3.18c0,1.76-1.42,3.18-3.18,3.18 c-1.76,0-3.18-1.42-3.18-3.18C57.99,63.03,59.42,61.6,61.17,61.6L61.17,61.6z M61.2,53.28c6.34,0,11.47,5.14,11.47,11.47 c0,6.34-5.14,11.47-11.47,11.47c-6.33,0-11.47-5.14-11.47-11.47C49.73,58.41,54.87,53.28,61.2,53.28L61.2,53.28z M14.78,44.57 c4.45-12.31,13.52-22.7,24.9-28.01c15.63-7.29,34.61-7.75,50.69,4.15c9.48,7.01,12.94,12.76,17.67,22.95 c3.58,9.03,0.64,11.97-10.87,6.9c-23.79-11.77-47.84-11.24-72.12,0C16.09,56.41,11.06,51.53,14.78,44.57L14.78,44.57z M75.9,109.05 c16.62-5.23,26.32-15.81,32.27-29.3c3.87-10.43-8.26-13.97-12.52-7.1c-2.55,5.06-5.59,9.4-9.55,12.77 c-6.2,5.27-15.18,6.23-16.58,16.16C68.79,106.74,69.97,111.38,75.9,109.05L75.9,109.05z M47.26,109.05 c-16.62-5.23-26.32-15.81-32.27-29.3c-3.87-10.43,8.26-13.97,12.52-7.1c2.55,5.06,5.59,9.4,9.55,12.77 c6.2,5.27,15.18,6.23,16.58,16.16C54.37,106.74,53.19,111.38,47.26,109.05L47.26,109.05z"/>
                  </svg>
                  <svg v-else-if="category.id === 'networking'" class="w-6 h-6" 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>
                  <svg v-else class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
                  </svg>
                </div>
                <div class="flex-1">
                  <p class="font-semibold">{{ category.name }}</p>
                  <p v-if="selectedCategory === category.id" class="text-xs text-white/60 mt-1">Currently viewing</p>
                </div>
                <svg v-if="selectedCategory === category.id" class="w-5 h-5 text-white flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
                  <path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" />
                </svg>
              </div>
            </button>
          </div>
      </div>
    </div>
    </Transition>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue'
import { useRouter, RouterLink } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useAppStore } from '@/stores/app'
import { rpcClient } from '@/api/rpc-client'
import { useMarketplaceApp, type MarketplaceAppInfo } from '@/composables/useMarketplaceApp'
import { useAppLauncherStore } from '@/stores/appLauncher'
import { useModalKeyboard } from '@/composables/useModalKeyboard'

type MarketplaceApp = Partial<MarketplaceAppInfo> & { id: string; trustScore?: number; trustTier?: string; relayCount?: number }

const router = useRouter()
const store = useAppStore()
const { t } = useI18n()
const { setCurrentApp } = useMarketplaceApp()
const appLauncher = useAppLauncherStore()

// Category state
const selectedCategory = ref('all')

const categories = computed(() => [
  { id: 'all', name: t('marketplace.all') },
  { id: 'community', name: t('marketplace.community') },
  { id: 'nostr', name: 'Nostr' },
  { id: 'commerce', name: t('marketplace.commerce') },
  { id: 'money', name: t('marketplace.money') },
  { id: 'data', name: t('marketplace.data') },
  { id: 'home', name: t('marketplace.homeCategory') },
  { id: 'car', name: t('marketplace.auto') },
  { id: 'networking', name: t('marketplace.networking') },
  { id: 'l484', name: 'L484' },
  { id: 'other', name: t('marketplace.other') }
])

// Installation state - support multiple concurrent installations
interface InstallProgress {
  id: string
  title: string
  status: 'downloading' | 'installing' | 'starting' | 'complete' | 'error'
  progress: number  // 0-100
  message: string
  attempt: number
}

const installingApps = ref<Map<string, InstallProgress>>(new Map())
const maxAttempts = ref(60)

// Watch WebSocket data for real install progress from backend
watch(() => store.packages, (packages) => {
  if (!packages) return
  for (const [appId, pkg] of Object.entries(packages)) {
    const progress = pkg['install-progress']
    if (progress && pkg.state === 'installing' && installingApps.value.has(appId)) {
      const current = installingApps.value.get(appId)!
      const pct = progress.size > 0 ? Math.round((progress.downloaded / progress.size) * 100) : 0
      const downloadedMB = (progress.downloaded / (1024 * 1024)).toFixed(1)
      const totalMB = (progress.size / (1024 * 1024)).toFixed(1)
      installingApps.value.set(appId, {
        ...current,
        status: 'downloading',
        progress: Math.min(pct, 95),
        message: progress.size > 0 ? `Downloading: ${downloadedMB} / ${totalMB} MB (${pct}%)` : t('marketplace.downloading'),
      })
    }
  }
}, { deep: true })

// Filter modal state (for mobile)
const showFilterModal = ref(false)
const filterModalRef = ref<HTMLElement | null>(null)
const filterRestoreFocusRef = ref<HTMLElement | null>(null)
function closeFilterModal() {
  filterRestoreFocusRef.value?.focus?.()
  showFilterModal.value = false
}
useModalKeyboard(filterModalRef, showFilterModal, closeFilterModal, { restoreFocusRef: filterRestoreFocusRef })

// Select category and trigger Nostr relay discovery when 'nostr' is chosen
function selectCategory(id: string) {
  selectedCategory.value = id
  if (id === 'nostr' && nostrApps.value.length === 0 && !nostrLoading.value) {
    loadNostrMarketplace()
  }
}

// Community marketplace state
const loadingCommunity = ref(false)
const communityError = ref('')
const communityApps = ref<MarketplaceApp[]>([])
const searchQuery = ref('')

// Nostr community marketplace state
const nostrApps = ref<(MarketplaceApp & { trustScore?: number; trustTier?: string; relayCount?: number })[]>([])
const nostrLoading = ref(false)
const nostrError = ref('')

async function loadNostrMarketplace() {
  if (nostrApps.value.length > 0 || nostrLoading.value) return
  nostrLoading.value = true
  nostrError.value = ''
  try {
    const res = await rpcClient.marketplaceDiscover()
    nostrApps.value = res.apps.map(app => ({
      id: app.manifest.app_id,
      title: app.manifest.name,
      version: app.manifest.version,
      description: typeof app.manifest.description === 'string'
        ? app.manifest.description
        : app.manifest.description,
      icon: app.manifest.icon_url || '',
      author: app.manifest.author.name,
      dockerImage: app.manifest.container.image,
      repoUrl: app.manifest.repo_url,
      category: app.manifest.category,
      source: 'nostr',
      trustScore: app.trust_score,
      trustTier: app.trust_tier,
      relayCount: app.relay_count,
    }))
  } catch (e) {
    nostrError.value = e instanceof Error ? e.message : 'Discovery failed'
    if (import.meta.env.DEV) console.warn('Nostr marketplace discovery failed:', e)
  } finally {
    nostrLoading.value = false
  }
}

// Available apps in marketplace
// const availableApps = ref([
  // {
  //   id: 'atob',
  //   title: 'A to B Bitcoin',
  //   version: '0.1.0',
  //   icon: '/assets/img/atob.png',
  //   category: 'community',
  //   description: {
  //     short: 'Bitcoin tools and services for seamless transactions',
  //     long: 'A to B Bitcoin provides tools and services for Bitcoin transactions. Access the A to B platform through your Archipelago server with full privacy and control.'
  //   },
  //   s9pkUrl: '/packages/atob.s9pk'
  // },
  // {
  //   id: 'k484',
  //   title: 'K484',
  //   version: '0.1.0',
  //   icon: '/assets/img/k484.png',
  //   category: 'commerce',
  //   description: {
  //     short: 'Point of Sale and Admin system for Archipelago',
  //     long: 'K484 provides a complete POS and administration system for your Archipelago server. Choose between POS mode for transactions or Admin mode for management.'
  //   },
  //   s9pkUrl: '/packages/k484.s9pk'
  // },
// ])

const installedPackages = computed(() => {
  return store.data?.['package-data'] || {}
})

// Function to categorize community apps based on their ID and description
function categorizeCommunityApp(app: MarketplaceApp): string {
  // If app already has a category set, use it
  if (app.category) return app.category

  const id = app.id.toLowerCase()
  const title = app.title?.toLowerCase() || ''
  const description = (typeof app.description === 'string' ? app.description : app.description?.short ?? '').toLowerCase()
  const combined = `${id} ${title} ${description}`

  // Money category
  if (id.includes('bitcoin') || id.includes('btc') || id.includes('lightning') || 
      id.includes('lnd') || id.includes('cln') || id.includes('electr') || 
      id.includes('fedimint') || id.includes('cashu') || title.includes('lightning') ||
      combined.includes('wallet') || combined.includes('satoshi')) {
    return 'money'
  }
  
  // Commerce category
  if (id.includes('btcpay') || id.includes('commerce') || id.includes('shop') || 
      id.includes('store') || id.includes('pos') || id.includes('payment') ||
      combined.includes('merchant') || combined.includes('invoice')) {
    return 'commerce'
  }
  
  // Data category
  if (id.includes('cloud') || id.includes('nextcloud') || id.includes('sync') || 
      id.includes('storage') || id.includes('backup') || id.includes('file') ||
      id.includes('photo') || id.includes('immich') || id.includes('jellyfin') ||
      id.includes('plex') || id.includes('media') || id.includes('vault') ||
      combined.includes('password manager') || combined.includes('file storage')) {
    return 'data'
  }
  
  // Home category
  if (id.includes('home-assistant') || id.includes('homeassistant') || 
      id.includes('smart-home') || id.includes('automation') || id.includes('iot') ||
      combined.includes('home automation') || combined.includes('smart home')) {
    return 'home'
  }
  
  // Nostr category
  if (id.includes('nostr') || (id.includes('relay') && combined.includes('nostr')) ||
      combined.includes('nostr relay') || combined.includes('nostr client')) {
    return 'nostr'
  }

  // Networking category
  if (id.includes('vpn') || id.includes('wireguard') || id.includes('tailscale') ||
      id.includes('proxy') || id.includes('dns') || id.includes('pihole') ||
      id.includes('adguard') || id.includes('nginx') || id.includes('tor') ||
      combined.includes('network') || combined.includes('firewall')) {
    return 'networking'
  }

  // Community category
  if (id.includes('matrix') || id.includes('synapse') || id.includes('element') ||
      id.includes('mastodon') || id.includes('lemmy') ||
      id.includes('messenger') || id.includes('chat') || id.includes('social') ||
      id.includes('cups') || combined.includes('communication') ||
      combined.includes('messaging')) {
    return 'community'
  }
  
  // Default to other
  return 'other'
}

// Combine curated apps with Nostr relay-discovered apps (merged into Nostr category)
const allApps = computed(() => {
  // Always start with curated Docker apps
  const local: (MarketplaceApp & { category: string; source: string })[] = []

  const community = communityApps.value.map(app => {
    const category = categorizeCommunityApp(app)
    return {
      ...app,
      category,
      source: 'community'
    }
  })

  const base = [...local, ...community]

  // Merge Nostr relay-discovered apps (deduplicated by ID)
  if (nostrApps.value.length > 0) {
    const existingIds = new Set(base.map(a => a.id))
    const nostrMerged = nostrApps.value
      .filter(app => !existingIds.has(app.id))
      .map(app => {
        const category = app.category || categorizeCommunityApp(app)
        return { ...app, category, source: 'nostr' }
      })
    return [...base, ...nostrMerged]
  }

  return base
})

// Only show categories that have at least one app
const categoriesWithApps = computed(() => {
  const apps = allApps.value
  return categories.value.filter(cat => {
    if (cat.id === 'all') return apps.length > 0
    return apps.some(app => app.category === cat.id)
  })
})

// Filtered apps by category and search
const filteredApps = computed(() => {
  let apps = allApps.value
  
  // Filter by category
  if (selectedCategory.value && selectedCategory.value !== 'all') {
    apps = apps.filter(app => app.category === selectedCategory.value)
  }
  
  // Filter by search query
  if (searchQuery.value) {
    const query = searchQuery.value.toLowerCase()
    apps = apps.filter(app => 
      app.title?.toLowerCase().includes(query) ||
      (typeof app.description === 'string' && app.description.toLowerCase().includes(query)) ||
      (typeof app.description === 'object' && app.description?.short?.toLowerCase().includes(query)) ||
      app.id?.toLowerCase().includes(query) ||
      app.author?.toLowerCase().includes(query)
    )
  }
  
  return apps
})


/** Marketplace app ID -> backend package keys (for "Already Installed" when first-boot/deploy created them) */
const INSTALLED_ALIASES: Record<string, string[]> = {
  mempool: ['mempool-web'],
  bitcoin: ['bitcoin-knots'],
  btcpay: ['btcpay-server'],
  immich: ['immich-server', 'immich-app', 'immich_server'],
  nextcloud: ['nextcloud-aio', 'nextcloud-server'],
}
function isInstalled(appId: string): boolean {
  if (appId in installedPackages.value) return true
  const aliases = INSTALLED_ALIASES[appId]
  return aliases ? aliases.some((a) => a in installedPackages.value) : false
}

/** Web-only apps — external URLs with no container */
const WEB_ONLY_APP_URLS: Record<string, string> = {
  'indeedhub': 'https://archipelago.indeehub.studio',
  'botfights': 'https://botfights.net',
  'nwnn': 'https://nwnn.l484.com',
  '484-kitchen': 'https://484.kitchen',
  'call-the-operator': 'https://cta.tx1138.com',
  'arch-presentation': 'https://present.l484.com',
  'syntropy-institute': 'https://syntropy.institute',
  't-zero': 'https://teeminuszero.net',
  'nostrudel': 'https://nostrudel.ninja',
}

/** App ID to port-based URL for container apps */
const APP_LAUNCH_URLS: Record<string, string> = {
  'bitcoin-knots': 'http://localhost:8334',
  'electrs': 'http://localhost:50002',
  'btcpay-server': 'http://localhost:23000',
  'lnd': 'http://localhost:8081',
  'mempool': 'http://localhost:4080',
  'homeassistant': 'http://localhost:8123',
  'grafana': 'http://localhost:3000',
  'searxng': 'http://localhost:8888',
  'ollama': 'http://localhost:11434',
  'onlyoffice': 'http://localhost:9980',
  'penpot': 'http://localhost:9001',
  'nextcloud': 'http://localhost:8085',
  'vaultwarden': 'http://localhost:8082',
  'jellyfin': 'http://localhost:8096',
  'photoprism': 'http://localhost:2342',
  'immich': 'http://localhost:2283',
  'filebrowser': 'http://localhost:8083',
  'nginx-proxy-manager': 'http://localhost:81',
  'portainer': 'http://localhost:9000',
  'uptime-kuma': 'http://localhost:3001',
  'tailscale': 'http://localhost:8240',
  'fedimint': 'http://localhost:8175',
  'nostr-rs-relay': 'http://localhost:18081',
  'dwn': 'http://localhost:3100',
}

function launchInstalledApp(app: MarketplaceApp) {
  const id = app.id

  // Web-only apps
  const webOnlyUrl = WEB_ONLY_APP_URLS[id]
  if (webOnlyUrl) {
    appLauncher.open({ url: webOnlyUrl, title: app.title || id })
    return
  }

  // Web URL on the marketplace app object (e.g. Nostr-discovered apps)
  if (app.webUrl) {
    appLauncher.open({ url: app.webUrl, title: app.title || id })
    return
  }

  // Container apps with known ports
  const portUrl = APP_LAUNCH_URLS[id]
  if (portUrl) {
    let url = portUrl
    if (url.includes('localhost')) {
      url = url.replace('localhost', window.location.hostname)
    }
    appLauncher.open({ url, title: app.title || id })
    return
  }

  // Fallback: navigate to the app detail page
  router.push({ name: 'app-details', params: { id } })
}

// Load community marketplace on mount
onMounted(() => {
  if (communityApps.value.length === 0 && !loadingCommunity.value) {
    loadCommunityMarketplace()
  }
})

// Load community marketplace from Start9 registry
async function loadCommunityMarketplace() {
  loadingCommunity.value = true
  communityError.value = ''
  
  // Use curated list of Docker-based apps
  // These are standard Docker images, not StartOS packages
  if (import.meta.env.DEV) console.log('Loading Docker-based app marketplace')
  communityApps.value = getCuratedAppList()
  loadingCommunity.value = false
}

// Get app tier classification (matches backend get_app_tier)
function getAppTier(appId: string): string {
  const core = ['bitcoin-knots', 'bitcoin', 'lnd', 'mempool', 'electrs', 'btcpay-server', 'dwn', 'filebrowser']
  const recommended = ['fedimint', 'vaultwarden', 'uptime-kuma', 'grafana', 'searxng', 'tailscale', 'portainer']
  if (core.includes(appId)) return 'core'
  if (recommended.includes(appId)) return 'recommended'
  return 'optional'
}

// Curated list of apps with Docker Hub images
function getCuratedAppList() {
  return [
    {
      id: 'bitcoin-knots',
      title: 'Bitcoin Knots',
      version: '28.1.0',
      description: 'Run a full Bitcoin node. Validate and relay blocks and transactions on the Bitcoin network.',
      icon: '/assets/img/app-icons/bitcoin-knots.webp',
      author: 'Bitcoin Knots',
      dockerImage: 'docker.io/bitcoinknots/bitcoin:v28.1',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/bitcoinknots/bitcoin'
    },
    {
      id: 'electrs',
      title: 'Electrs',
      version: '0.4.1',
      description: 'Electrum protocol indexer for Bitcoin. Powers Mempool and other Electrum clients. Requires Bitcoin Knots or Bitcoin Core.',
      icon: '/assets/img/app-icons/electrs.svg',
      author: 'Roman Zeyde',
      dockerImage: 'docker.io/mempool/electrs:v0.4.1',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/romanz/electrs'
    },
    {
      id: 'btcpay-server',
      title: 'BTCPay Server',
      version: '1.13.5',
      description: 'Self-hosted Bitcoin payment processor. Accept Bitcoin payments without intermediaries or fees.',
      icon: '/assets/img/app-icons/btcpay-server.png',
      author: 'BTCPay Server Foundation',
      dockerImage: 'docker.io/btcpayserver/btcpayserver:1.13.5',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/btcpayserver/btcpayserver'
    },
    {
      id: 'lnd',
      title: 'LND',
      version: '0.17.4',
      description: 'Lightning Network Daemon. Fast and cheap Bitcoin payments through the Lightning Network.',
      icon: '/assets/img/app-icons/lnd.svg',
      author: 'Lightning Labs',
      dockerImage: 'docker.io/lightninglabs/lnd:v0.17.4-beta',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/lightningnetwork/lnd'
    },
    {
      id: 'mempool',
      title: 'Mempool Explorer',
      version: '2.5.0',
      description: 'Self-hosted Bitcoin blockchain and mempool visualizer with beautiful explorer interface.',
      icon: '/assets/img/app-icons/mempool.webp',
      author: 'Mempool',
      dockerImage: 'docker.io/mempool/frontend:v2.5.0',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/mempool/mempool'
    },
    {
      id: 'homeassistant',
      title: 'Home Assistant',
      version: '2024.1',
      description: 'Open-source home automation platform. Control and automate your smart home devices privately.',
      icon: '/assets/img/app-icons/homeassistant.png',
      author: 'Home Assistant',
      dockerImage: 'docker.io/homeassistant/home-assistant:2024.1',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/home-assistant/core'
    },
    {
      id: 'grafana',
      title: 'Grafana',
      version: '10.2.0',
      description: 'Analytics and monitoring platform. Create dashboards and visualize data from multiple sources.',
      icon: '/assets/img/app-icons/grafana.png',
      author: 'Grafana Labs',
      dockerImage: 'docker.io/grafana/grafana:10.2.0',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/grafana/grafana'
    },
    {
      id: 'searxng',
      title: 'SearXNG',
      version: '2024.1.0',
      description: 'Privacy-respecting metasearch engine. Search without tracking or ads.',
      icon: '/assets/img/app-icons/searxng.png',
      author: 'SearXNG',
      dockerImage: 'docker.io/searxng/searxng:2024.11.17-e2554de75',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/searxng/searxng'
    },
    {
      id: 'ollama',
      title: 'Ollama',
      version: '0.1.0',
      description: 'Run large language models locally. Download and run AI models like Llama, Mistral on your own hardware.',
      icon: '/assets/img/app-icons/ollama.png',
      author: 'Ollama',
      dockerImage: 'docker.io/ollama/ollama:0.5.4',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/ollama/ollama'
    },
    {
      id: 'onlyoffice',
      title: 'OnlyOffice',
      version: '7.5.1',
      description: 'Office suite for document collaboration. Edit docs, spreadsheets, and presentations.',
      icon: '/assets/img/app-icons/onlyoffice.webp',
      author: 'Ascensio System SIA',
      dockerImage: 'docker.io/onlyoffice/documentserver:7.5.1',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/ONLYOFFICE/DocumentServer'
    },
    {
      id: 'penpot',
      title: 'Penpot',
      version: '2.4',
      description: 'Open-source design and prototyping platform. Self-hosted alternative to Figma.',
      icon: '/assets/img/app-icons/penpot.webp',
      author: 'Penpot',
      dockerImage: 'docker.io/penpotapp/frontend:2.4',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/penpot/penpot'
    },
    {
      id: 'nextcloud',
      title: 'Nextcloud',
      version: '28.0',
      description: 'Self-hosted cloud storage and collaboration platform. Your own private cloud.',
      icon: '/assets/img/app-icons/nextcloud.webp',
      author: 'Nextcloud',
      dockerImage: 'docker.io/library/nextcloud:28',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/nextcloud/server'
    },
    {
      id: 'vaultwarden',
      title: 'Vaultwarden',
      version: '1.30.0',
      description: 'Self-hosted password manager (Bitwarden-compatible). Secure vault for passwords and secrets.',
      icon: '/assets/img/app-icons/vaultwarden.webp',
      author: 'Vaultwarden',
      dockerImage: 'docker.io/vaultwarden/server:1.30.0-alpine',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/dani-garcia/vaultwarden'
    },
    {
      id: 'jellyfin',
      title: 'Jellyfin',
      version: '10.8.0',
      description: 'Free media server system. Stream your movies, music, and photos to any device.',
      icon: '/assets/img/app-icons/jellyfin.webp',
      author: 'Jellyfin',
      dockerImage: 'docker.io/jellyfin/jellyfin:10.8.13',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/jellyfin/jellyfin'
    },
    {
      id: 'photoprism',
      title: 'PhotoPrism',
      version: '240915',
      description: 'AI-powered photo management. Organize and browse photos with facial recognition.',
      icon: '/assets/img/app-icons/photoprism.svg',
      author: 'PhotoPrism',
      dockerImage: 'docker.io/photoprism/photoprism:240915',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/photoprism/photoprism'
    },
    {
      id: 'immich',
      title: 'Immich',
      version: '1.90.0',
      description: 'High-performance self-hosted photo and video backup. Mobile-first with ML features.',
      icon: '/assets/img/app-icons/immich.png',
      author: 'Immich',
      dockerImage: 'ghcr.io/immich-app/immich-server:release',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/immich-app/immich'
    },
    {
      id: 'filebrowser',
      title: 'File Browser',
      version: '2.27.0',
      description: 'Web-based file manager. Browse, upload, and manage files through a web interface.',
      icon: '/assets/img/app-icons/file-browser.webp',
      author: 'File Browser',
      dockerImage: 'docker.io/filebrowser/filebrowser:v2.27.0',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/filebrowser/filebrowser'
    },
    {
      id: 'nginx-proxy-manager',
      title: 'Nginx Proxy Manager',
      version: '2.11.0',
      description: 'Easy proxy management with SSL. Beautiful web interface for managing reverse proxies.',
      icon: '/assets/img/app-icons/nginx.svg',
      author: 'Nginx Proxy Manager',
      dockerImage: 'docker.io/jc21/nginx-proxy-manager:2.12.1',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/NginxProxyManager/nginx-proxy-manager'
    },
    {
      id: 'portainer',
      title: 'Portainer',
      version: '2.19.0',
      description: 'Container management UI. Manage Docker containers through a beautiful web interface.',
      icon: '/assets/img/app-icons/portainer.webp',
      author: 'Portainer',
      dockerImage: 'docker.io/portainer/portainer-ce:2.19.4',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/portainer/portainer'
    },
    {
      id: 'uptime-kuma',
      title: 'Uptime Kuma',
      version: '1.23.0',
      description: 'Self-hosted monitoring tool. Monitor uptime for HTTP(s), TCP, DNS, and more.',
      icon: '/assets/img/app-icons/uptime-kuma.webp',
      author: 'Uptime Kuma',
      dockerImage: 'docker.io/louislam/uptime-kuma:1',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/louislam/uptime-kuma'
    },
    {
      id: 'tailscale',
      title: 'Tailscale',
      version: '1.78.0',
      description: 'Zero-config VPN for secure remote access. Connect all your devices with WireGuard mesh network.',
      icon: '/assets/img/app-icons/tailscale.webp',
      author: 'Tailscale',
      dockerImage: 'docker.io/tailscale/tailscale:stable',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/tailscale/tailscale'
    },
    {
      id: 'fedimint',
      title: 'Fedimint',
      version: '0.10.0',
      description: 'Federated Bitcoin mint with built-in Guardian UI. Private, scalable Bitcoin through federated guardians.',
      icon: '/assets/img/app-icons/fedimint.png',
      author: 'Fedimint',
      dockerImage: 'docker.io/fedimint/fedimintd:v0.10.0',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/fedimint/fedimint'
    },
    {
      id: 'indeedhub',
      title: 'Indeehub',
      version: '0.1.0',
      description: 'Bitcoin documentary streaming platform. Stream God Bless Bitcoin and other educational content about Bitcoin, sovereignty, and decentralized technology.',
      icon: '/assets/img/app-icons/indeehub.ico',
      author: 'Indeehub Team',
      dockerImage: 'localhost/indeedhub:latest',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/indeedhub/indeedhub'
    },
    {
      id: 'dwn',
      title: 'Decentralized Web Node',
      version: '0.4.0',
      description: 'Store and sync your personal data across devices using decentralized web node protocols. Own your data with DID-based access control.',
      icon: '/assets/img/app-icons/dwn.svg',
      author: 'TBD',
      dockerImage: 'ghcr.io/tbd54566975/dwn-server:main',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/TBD54566975/dwn-server'
    },
    {
      id: 'nostrudel',
      title: 'noStrudel',
      version: '0.40.0',
      category: 'nostr',
      description: 'A feature-rich Nostr web client with NIP-07 signer support. Browse your feed, post notes, manage relays, and interact with the Nostr network — all signed with your node\'s Nostr identity.',
      icon: '/assets/img/app-icons/nostrudel.svg',
      author: 'hzrd149',
      dockerImage: '',
      manifestUrl: undefined,
      repoUrl: 'https://github.com/hzrd149/nostrudel',
      webUrl: 'https://nostrudel.ninja'
    },
    {
      id: 'nostr-rs-relay',
      title: 'Nostr Relay',
      version: '0.9.0',
      category: 'nostr',
      description: 'Run your own Nostr relay. Store your events locally, relay for friends, and publish over Tor. A sovereign relay for your sovereign node.',
      icon: '/assets/img/app-icons/nostr-rs-relay.svg',
      author: 'scsiblade',
      dockerImage: 'docker.io/scsiblade/nostr-rs-relay:0.9.0',
      manifestUrl: undefined,
      repoUrl: 'https://sr.ht/~gheartsfield/nostr-rs-relay/'
    },
    {
      id: 'botfights',
      title: 'BotFights',
      version: '1.0.0',
      description: 'AI bot arena — build, train, and battle autonomous agents. Compete in strategy tournaments with your own coded bots.',
      icon: '/assets/img/app-icons/botfights.svg',
      author: 'BotFights',
      dockerImage: '',
      manifestUrl: undefined,
      repoUrl: 'https://botfights.net',
      webUrl: 'https://botfights.net'
    },
    {
      id: 'nwnn',
      title: 'Next Web News Network',
      version: '1.0.0',
      category: 'l484',
      description: 'Decentralized news and link aggregator, synchronized from Telegram. Community-curated content on Bitcoin, sovereignty, and decentralized tech.',
      icon: '/assets/img/app-icons/nwnn.png',
      author: 'L484',
      dockerImage: '',
      manifestUrl: undefined,
      repoUrl: 'https://nwnn.l484.com',
      webUrl: 'https://nwnn.l484.com'
    },
    {
      id: '484-kitchen',
      title: '484 Kitchen',
      version: '1.0.0',
      category: 'l484',
      description: 'K484 application platform — an internal tool for the L484 network.',
      icon: '/assets/img/app-icons/484-kitchen.png',
      author: 'L484',
      dockerImage: '',
      manifestUrl: undefined,
      repoUrl: 'https://484.kitchen',
      webUrl: 'https://484.kitchen'
    },
    {
      id: 'call-the-operator',
      title: 'Call the Operator',
      version: '1.0.0',
      category: 'l484',
      description: 'Escape the Matrix — a portal for exploring decentralized alternatives and reclaiming digital sovereignty.',
      icon: '/assets/img/app-icons/call-the-operator.png',
      author: 'TX1138',
      dockerImage: '',
      manifestUrl: undefined,
      repoUrl: 'https://cta.tx1138.com',
      webUrl: 'https://cta.tx1138.com'
    },
    {
      id: 'arch-presentation',
      title: 'Arch Presentation',
      version: '1.0.0',
      category: 'l484',
      description: 'Archipelago: The Future of Decentralized Infrastructure — an interactive presentation about the Archipelago project vision.',
      icon: '/assets/img/app-icons/arch-presentation.png',
      author: 'L484',
      dockerImage: '',
      manifestUrl: undefined,
      repoUrl: 'https://present.l484.com',
      webUrl: 'https://present.l484.com'
    },
    {
      id: 'syntropy-institute',
      title: 'Syntropy Institute',
      version: '1.0.0',
      category: 'l484',
      description: 'Medicine Reimagined — Manual Kinetics, Syntropy Frequency analysis-therapy, digital homeopathy, and concierge protocols.',
      icon: '/assets/img/app-icons/syntropy-institute.png',
      author: 'Syntropy Institute',
      dockerImage: '',
      manifestUrl: undefined,
      repoUrl: 'https://syntropy.institute',
      webUrl: 'https://syntropy.institute'
    },
    {
      id: 't-zero',
      title: 'T-0',
      version: '1.0.0',
      category: 'l484',
      description: 'Documentary series exploring decentralization, Bitcoin, and the mavericks building the ungovernable future. Conversations with the builders, powered by Nostr.',
      icon: '/assets/img/app-icons/t-zero.png',
      author: 'T-0',
      dockerImage: '',
      manifestUrl: undefined,
      repoUrl: 'https://teeminuszero.net',
      webUrl: 'https://teeminuszero.net'
    }
  ]
}

function viewAppDetails(app: MarketplaceApp) {
  if (import.meta.env.DEV) console.log('[Marketplace] Navigating to app detail:', app)

  try {
    // If app is already installed, go directly to the installed app detail page
    if (isInstalled(app.id)) {
      if (import.meta.env.DEV) console.log('[Marketplace] App is installed, navigating to app details page')
      router.push({
        name: 'app-details',
        params: { id: app.id }
      })
    } else {
      // Store app data in composable for marketplace detail view
      setCurrentApp(app)
      if (import.meta.env.DEV) console.log('[Marketplace] App data stored in composable')
      
      // Navigate to marketplace detail page
      router.push({
        name: 'marketplace-app-detail',
        params: { id: app.id }
      })
    }
  } catch (e) {
    if (import.meta.env.DEV) console.error('[Marketplace] Navigation error:', e)
  }
}

const activeTimers: ReturnType<typeof setTimeout>[] = []
const activeIntervals: ReturnType<typeof setInterval>[] = []

function trackTimeout(fn: () => void, ms: number) {
  const id = setTimeout(() => {
    const idx = activeTimers.indexOf(id)
    if (idx !== -1) activeTimers.splice(idx, 1)
    fn()
  }, ms)
  activeTimers.push(id)
  return id
}

function trackInterval(fn: () => void, ms: number) {
  const id = setInterval(fn, ms)
  activeIntervals.push(id)
  return id
}

function clearTrackedInterval(id: ReturnType<typeof setInterval>) {
  clearInterval(id)
  const idx = activeIntervals.indexOf(id)
  if (idx !== -1) activeIntervals.splice(idx, 1)
}

onBeforeUnmount(() => {
  for (const t of activeTimers) clearTimeout(t)
  activeTimers.length = 0
  for (const i of activeIntervals) clearInterval(i)
  activeIntervals.length = 0
})

function startInstallPolling(appId: string, statusMessage: string) {
  const interval = trackInterval(() => {
    const current = installingApps.value.get(appId)
    if (!current) { clearTrackedInterval(interval); return }

    const newAttempt = current.attempt + 1
    installingApps.value.set(appId, {
      ...current,
      attempt: newAttempt,
      progress: Math.min(60 + (newAttempt * 0.5), 95),
      message: statusMessage
    })

    if (isInstalled(appId)) {
      clearTrackedInterval(interval)
      installingApps.value.set(appId, { ...current, status: 'complete', progress: 100, message: 'Installation complete!' })
      trackTimeout(() => { installingApps.value.delete(appId) }, 2000)
    } else if (newAttempt >= maxAttempts.value) {
      clearTrackedInterval(interval)
      installingApps.value.set(appId, { ...current, status: 'error', progress: 0, message: 'Installation timeout' })
      trackTimeout(() => { installingApps.value.delete(appId) }, 5000)
    }
  }, 1000)
}

async function installApp(app: MarketplaceApp) {
  if (installingApps.value.has(app.id) || isInstalled(app.id)) return

  installingApps.value.set(app.id, {
    id: app.id, title: app.title ?? app.id, status: 'downloading', progress: 10, message: 'Preparing installation...', attempt: 0
  })

  try {
    const installUrl = app.url || app.manifestUrl || app.s9pkUrl
    
    installingApps.value.set(app.id, { ...installingApps.value.get(app.id)!, status: 'downloading', progress: 30, message: 'Downloading package...' })
    
    await rpcClient.call({ method: 'package.install', params: { id: app.id, url: installUrl, version: app.version } })
    
    installingApps.value.set(app.id, { ...installingApps.value.get(app.id)!, status: 'installing', progress: 60, message: 'Installing package...' })
    
    startInstallPolling(app.id, 'Starting application...')
  } catch (err) {
    if (import.meta.env.DEV) console.error('Installation failed:', err)
    installingApps.value.set(app.id, { ...installingApps.value.get(app.id)!, status: 'error', progress: 0, message: `Failed: ${err}` })
    trackTimeout(() => { installingApps.value.delete(app.id) }, 5000)
  }
}

async function installCommunityApp(app: MarketplaceApp) {
  if (installingApps.value.has(app.id) || isInstalled(app.id) || !app.dockerImage) return

  installingApps.value.set(app.id, {
    id: app.id, title: app.title ?? app.id, status: 'downloading', progress: 10, message: 'Pulling Docker image...', attempt: 0
  })
  
  try {
    installingApps.value.set(app.id, { ...installingApps.value.get(app.id)!, status: 'downloading', progress: 20, message: 'Downloading container image...' })
    
    await rpcClient.call({
      method: 'package.install',
      params: { id: app.id, dockerImage: app.dockerImage, version: app.version },
      timeout: 180000
    })
    
    installingApps.value.set(app.id, { ...installingApps.value.get(app.id)!, status: 'installing', progress: 60, message: 'Starting container...' })
    
    startInstallPolling(app.id, 'Initializing application...')
  } catch (err) {
    if (import.meta.env.DEV) console.error('[Marketplace] Installation failed:', err)
    installingApps.value.set(app.id, { ...installingApps.value.get(app.id)!, status: 'error', progress: 0, message: `Failed: ${err}` })
    trackTimeout(() => { installingApps.value.delete(app.id) }, 5000)
  }
}

function handleImageError(event: Event) {
  const img = event.target as HTMLImageElement
  img.src = '/assets/img/logo-archipelago.svg'
}
</script>

<style scoped>
.line-clamp-3 {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Modal transition animations */
.modal-enter-active,
.modal-leave-active {
  transition: opacity 0.3s ease;
}

.modal-enter-from,
.modal-leave-to {
  opacity: 0;
}

.modal-enter-active .glass-card,
.modal-leave-active .glass-card {
  transition: transform 0.3s ease;
}

.modal-enter-from .glass-card {
  transform: scale(0.95);
}

.modal-leave-to .glass-card {
  transform: scale(0.95);
}

/* Custom scrollbar styling for apps section */
.marketplace-container ::-webkit-scrollbar {
  width: 8px;
}

.marketplace-container ::-webkit-scrollbar-track {
  background: rgba(255, 255, 255, 0.05);
  border-radius: 4px;
}

.marketplace-container ::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.2);
  border-radius: 4px;
  transition: background 0.3s ease;
}

.marketplace-container ::-webkit-scrollbar-thumb:hover {
  background: rgba(255, 255, 255, 0.3);
}

/* Firefox scrollbar */
.marketplace-container {
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.2) rgba(255, 255, 255, 0.05);
}
</style>
