<template>
  <div class="pb-6">
    <!-- Desktop: tabs + search in one row -->
    <div class="hidden md:flex items-center gap-4 mb-4">
      <div class="mode-switcher flex-shrink-0">
        <RouterLink to="/dashboard/apps" class="mode-switcher-btn mode-switcher-btn-active">My Apps</RouterLink>
        <RouterLink to="/dashboard/marketplace" class="mode-switcher-btn">App Store</RouterLink>
      </div>
      <input
        v-model="searchQuery"
        type="text"
        :placeholder="t('apps.searchPlaceholder')"
        :aria-label="t('apps.searchLabel')"
        class="flex-1 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 (tabs are in Dashboard tab bar) -->
    <div class="md:hidden mb-4">
      <input
        v-model="searchQuery"
        type="text"
        :placeholder="t('apps.searchPlaceholder')"
        :aria-label="t('apps.searchLabel')"
        class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-lg text-white placeholder-white/50 focus:outline-none focus:border-white/40 transition-colors"
      />
    </div>

    <!-- Loading Skeleton -->
    <div v-if="!store.isConnected && sortedPackageEntries.length === 0" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 pb-6">
      <div v-for="i in 3" :key="i" class="glass-card p-6 animate-pulse">
        <div class="flex items-start gap-4">
          <div class="w-16 h-16 rounded-lg bg-white/10"></div>
          <div class="flex-1">
            <div class="h-5 w-32 bg-white/10 rounded mb-2"></div>
            <div class="h-4 w-48 bg-white/5 rounded mb-3"></div>
            <div class="h-6 w-20 bg-white/5 rounded"></div>
          </div>
        </div>
        <div class="mt-4 flex gap-2">
          <div class="flex-1 h-9 bg-white/5 rounded-lg"></div>
        </div>
      </div>
    </div>

    <!-- Empty State -->
    <div v-else-if="sortedPackageEntries.length === 0 && !searchQuery" class="text-center py-16 pb-6">
      <div class="glass-card p-12 max-w-md mx-auto">
        <svg class="w-16 h-16 mx-auto text-white/40 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-2.586a1 1 0 00-.707.293l-2.414 2.414a1 1 0 01-.707.293h-3.172a1 1 0 01-.707-.293l-2.414-2.414A1 1 0 006.586 13H4" />
        </svg>
        <h3 class="text-xl font-semibold text-white mb-2">{{ t('apps.noAppsTitle') }}</h3>
        <p class="text-white/70 mb-6">{{ t('apps.noAppsMessage') }}</p>
        <RouterLink
          to="/dashboard/marketplace"
          class="inline-block glass-button px-6 py-3 rounded-lg font-medium transition-all hover:bg-black/70 hover:border-white/30"
        >
          {{ t('apps.browseAppStore') }}
        </RouterLink>
      </div>
    </div>

    <!-- No Results -->
    <div v-if="filteredPackageEntries.length === 0 && searchQuery" class="text-center py-12">
      <p class="text-white/70">{{ t('apps.noResults', { query: searchQuery }) }}</p>
    </div>

    <!-- Apps Grid (alphabetically by title, stable across run state) -->
    <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 pb-6">
      <div
        v-for="([id, pkg], index) in filteredPackageEntries"
        :key="id"
        data-controller-container
        :data-controller-launch="canLaunch(pkg) ? '' : undefined"
        tabindex="0"
        role="link"
        class="glass-card card-stagger p-6 transition-all hover:-translate-y-1 cursor-pointer relative min-w-0 overflow-hidden"
        :style="{ '--stagger-index': index }"
        @click="goToApp(id as string)"
        @keydown.enter="goToApp(id as string)"
      >
        <!-- Uninstall Icon (not for web-only apps) -->
        <button
          v-if="!isWebOnlyApp(id as string)"
          @click.stop="showUninstallModal(id as string, pkg)"
          class="absolute top-4 right-4 p-2 rounded-lg text-white/60 hover:text-red-400 hover:bg-red-500/20 transition-colors z-10"
          :aria-label="`${t('common.uninstall')} ${pkg.manifest?.title || id}`"
          :title="t('common.uninstall')"
        >
          <svg class="w-5 h-5" aria-hidden="true" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
          </svg>
        </button>

        <div class="flex items-start gap-4">
          <img
            :src="pkg['static-files']?.icon || `/assets/img/app-icons/${id}.png`"
            :alt="pkg.manifest?.title || String(id)"
            class="w-16 h-16 rounded-lg object-cover bg-white/10"
            @error="handleImageError"
          />
          <div class="flex-1 min-w-0 overflow-hidden">
            <h3 class="text-lg font-semibold text-white mb-1 truncate" :title="pkg.manifest.title">
              {{ pkg.manifest.title }}
            </h3>
            <p class="text-sm text-white/70 mb-2 truncate">
              {{ pkg.manifest?.description?.short || '' }}
            </p>
            <div class="flex items-center gap-2">
              <span
                class="inline-flex items-center px-2 py-1 rounded text-xs font-medium"
                :class="getStatusClass(pkg.state)"
              >
                {{ pkg.state }}
              </span>
              <span class="text-xs text-white/50">
                v{{ pkg.manifest.version }}
              </span>
            </div>
          </div>
        </div>

        <!-- Quick Actions -->
        <div class="mt-4 flex gap-2">
          <button
            v-if="canLaunch(pkg)"
            data-controller-launch-btn
            @click.stop="launchApp(id as string)"
            class="flex-1 px-4 py-2 glass-button glass-button-sm rounded-lg text-sm font-medium"
          >
            {{ t('common.launch') }}
          </button>
          <button
            v-if="!isWebOnlyApp(id as string) && (pkg.state === 'stopped' || pkg.state === 'exited')"
            @click.stop="startApp(id as string)"
            :disabled="loadingActions[id as string]"
            class="flex-1 px-4 py-2 bg-green-500/20 border border-green-500/40 rounded-lg text-green-200 text-sm font-medium hover:bg-green-500/30 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
          >
            <svg
              v-if="loadingActions[id as string]"
              class="animate-spin h-4 w-4"
              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>
            <span>{{ loadingActions[id as string] ? t('common.starting') : t('common.start') }}</span>
          </button>
          <button
            v-if="!isWebOnlyApp(id as string) && (pkg.state === 'running' || pkg.state === 'starting')"
            @click.stop="stopApp(id as string)"
            :disabled="loadingActions[id as string]"
            class="flex-1 px-4 py-2 bg-yellow-500/20 border border-yellow-500/40 rounded-lg text-yellow-200 text-sm font-medium hover:bg-yellow-500/30 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
          >
            <svg
              v-if="loadingActions[id as string]"
              class="animate-spin h-4 w-4"
              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>
            <span>{{ loadingActions[id as string] ? t('common.stopping') : t('common.stop') }}</span>
          </button>
        </div>
      </div>
    </div>

    <!-- Uninstall Confirmation Modal -->
    <Transition name="modal">
      <div
        v-if="uninstallModal.show"
        class="fixed inset-0 z-50 flex items-center justify-center p-4"
        @click="closeUninstallModal()"
      >
        <div class="absolute inset-0 bg-black/10 backdrop-blur-md"></div>
        <div
          ref="uninstallModalRef"
          @click.stop
          role="dialog"
          aria-modal="true"
          aria-labelledby="uninstall-dialog-title"
          class="glass-card p-6 max-w-md w-full relative z-10"
        >
          <div class="flex items-start gap-4 mb-4">
            <div class="p-3 bg-red-500/20 rounded-lg">
              <svg class="w-6 h-6 text-red-400" aria-hidden="true" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
              </svg>
            </div>
            <div class="flex-1">
              <h3 id="uninstall-dialog-title" class="text-xl font-semibold text-white mb-2">{{ t('apps.uninstallTitle') }}</h3>
              <p class="text-white/70">
                {{ t('apps.uninstallConfirm', { name: uninstallModal.appTitle }) }}
              </p>
            </div>
          </div>

          <div class="flex gap-3 justify-end">
            <button
              @click="closeUninstallModal()"
              class="px-4 py-2 glass-button rounded-lg text-sm font-medium"
            >
              {{ t('common.cancel') }}
            </button>
            <button
              @click="confirmUninstall"
              class="px-4 py-2 bg-red-600/80 hover:bg-red-600 rounded-lg text-white text-sm font-medium transition-colors"
            >
              {{ t('common.uninstall') }}
            </button>
          </div>
        </div>
      </div>
    </Transition>

    <!-- Action error toast -->
    <Transition name="fade">
      <div v-if="actionError" class="fixed bottom-20 left-1/2 -translate-x-1/2 z-50 max-w-md w-full px-4" role="alert" aria-live="assertive">
        <div class="bg-red-500/20 border border-red-500/40 backdrop-blur-sm rounded-lg px-4 py-3 text-red-200 text-sm flex items-center justify-between gap-3">
          <span>{{ actionError }}</span>
          <button @click="actionError = ''" :aria-label="t('apps.dismissError')" class="text-red-300 hover:text-white shrink-0">&times;</button>
        </div>
      </div>
    </Transition>
  </div>
</template>

<script setup lang="ts">
import { computed, ref, onBeforeUnmount } from 'vue'
import { useRouter, RouterLink } from 'vue-router'
import { useI18n } from 'vue-i18n'
import { useAppStore } from '../stores/app'

const { t } = useI18n()
import { useAppLauncherStore } from '../stores/appLauncher'
import { PackageState, type PackageDataEntry } from '../types/api'
import { useModalKeyboard } from '@/composables/useModalKeyboard'

const router = useRouter()
const store = useAppStore()

// Search
const searchQuery = ref('')

// Track loading states for each app action
const loadingActions = ref<Record<string, boolean>>({})

// Action error toast
const actionError = ref('')
let errorTimer: ReturnType<typeof setTimeout> | undefined

function showActionError(msg: string) {
  actionError.value = msg
  if (errorTimer) clearTimeout(errorTimer)
  errorTimer = setTimeout(() => { actionError.value = '' }, 5000)
}

// Web-only app IDs and their URLs
const WEB_ONLY_APP_URLS: Record<string, string> = {
  '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',
}

function isWebOnlyApp(id: string): boolean {
  return id in WEB_ONLY_APP_URLS
}

// Web-only apps (no container) — always show as installed bookmarks
const WEB_ONLY_APPS: Record<string, PackageDataEntry> = {
  'botfights': {
    state: 'running' as PackageState,
    manifest: { id: 'botfights', title: 'BotFights', version: '1.0.0', description: { short: 'AI bot arena — build, train, and battle autonomous agents', long: '' }, 'release-notes': '', license: '', 'wrapper-repo': '', 'upstream-repo': '', 'support-site': '', 'marketing-site': '', 'donation-url': null },
    'static-files': { license: '', instructions: '', icon: '/assets/img/app-icons/botfights.svg' },
  },
  'nwnn': {
    state: 'running' as PackageState,
    manifest: { id: 'nwnn', title: 'Next Web News Network', version: '1.0.0', description: { short: 'Decentralized news aggregator, synced from Telegram', long: '' }, 'release-notes': '', license: '', 'wrapper-repo': '', 'upstream-repo': '', 'support-site': '', 'marketing-site': '', 'donation-url': null },
    'static-files': { license: '', instructions: '', icon: '/assets/img/app-icons/nwnn.png' },
  },
  '484-kitchen': {
    state: 'running' as PackageState,
    manifest: { id: '484-kitchen', title: '484 Kitchen', version: '1.0.0', description: { short: 'K484 application platform', long: '' }, 'release-notes': '', license: '', 'wrapper-repo': '', 'upstream-repo': '', 'support-site': '', 'marketing-site': '', 'donation-url': null },
    'static-files': { license: '', instructions: '', icon: '/assets/img/app-icons/484-kitchen.png' },
  },
  'call-the-operator': {
    state: 'running' as PackageState,
    manifest: { id: 'call-the-operator', title: 'Call the Operator', version: '1.0.0', description: { short: 'Escape the Matrix — explore decentralized alternatives', long: '' }, 'release-notes': '', license: '', 'wrapper-repo': '', 'upstream-repo': '', 'support-site': '', 'marketing-site': '', 'donation-url': null },
    'static-files': { license: '', instructions: '', icon: '/assets/img/app-icons/call-the-operator.png' },
  },
  'arch-presentation': {
    state: 'running' as PackageState,
    manifest: { id: 'arch-presentation', title: 'Arch Presentation', version: '1.0.0', description: { short: 'Archipelago: The Future of Decentralized Infrastructure', long: '' }, 'release-notes': '', license: '', 'wrapper-repo': '', 'upstream-repo': '', 'support-site': '', 'marketing-site': '', 'donation-url': null },
    'static-files': { license: '', instructions: '', icon: '/assets/img/app-icons/arch-presentation.png' },
  },
  'syntropy-institute': {
    state: 'running' as PackageState,
    manifest: { id: 'syntropy-institute', title: 'Syntropy Institute', version: '1.0.0', description: { short: 'Medicine Reimagined — frequency analysis-therapy', long: '' }, 'release-notes': '', license: '', 'wrapper-repo': '', 'upstream-repo': '', 'support-site': '', 'marketing-site': '', 'donation-url': null },
    'static-files': { license: '', instructions: '', icon: '/assets/img/app-icons/syntropy-institute.png' },
  },
  't-zero': {
    state: 'running' as PackageState,
    manifest: { id: 't-zero', title: 'T-0', version: '1.0.0', description: { short: 'Documentary series on decentralization and Bitcoin', long: '' }, 'release-notes': '', license: '', 'wrapper-repo': '', 'upstream-repo': '', 'support-site': '', 'marketing-site': '', 'donation-url': null },
    'static-files': { license: '', instructions: '', icon: '/assets/img/app-icons/t-zero.png' },
  },
}

// Merge real packages from store with web-only app bookmarks
const packages = computed(() => {
  const realPackages = store.packages || {}
  return { ...WEB_ONLY_APPS, ...realPackages }
})

// Web-only apps first (alphabetically), then all other apps (alphabetically)
const sortedPackageEntries = computed(() => {
  const entries = Object.entries(packages.value)
  return entries.sort(([idA, a], [idB, b]) => {
    const aWeb = isWebOnlyApp(idA) ? 0 : 1
    const bWeb = isWebOnlyApp(idB) ? 0 : 1
    if (aWeb !== bWeb) return aWeb - bWeb
    return (a.manifest?.title ?? '').localeCompare(b.manifest?.title ?? '', undefined, { sensitivity: 'base' })
  })
})

const filteredPackageEntries = computed(() => {
  if (!searchQuery.value) return sortedPackageEntries.value
  const q = searchQuery.value.toLowerCase()
  return sortedPackageEntries.value.filter(([id, pkg]) =>
    (pkg.manifest?.title ?? '').toLowerCase().includes(q) ||
    (pkg.manifest?.description?.short ?? '').toLowerCase().includes(q) ||
    id.toLowerCase().includes(q)
  )
})

const uninstallModal = ref({
  show: false,
  appId: '',
  appTitle: ''
})
const uninstallModalRef = ref<HTMLElement | null>(null)
const uninstallRestoreFocusRef = ref<HTMLElement | null>(null)
function closeUninstallModal() {
  uninstallRestoreFocusRef.value?.focus?.()
  uninstallModal.value.show = false
}
useModalKeyboard(
  uninstallModalRef,
  computed(() => uninstallModal.value.show),
  closeUninstallModal,
  { restoreFocusRef: uninstallRestoreFocusRef }
)

function canLaunch(pkg: PackageDataEntry): boolean {
  // Web-only apps are always launchable
  if (isWebOnlyApp(pkg.manifest.id)) return true
  // For real apps, check for UI interface
  const hasUI = pkg.manifest.interfaces?.main?.ui || pkg.installed?.['interface-addresses']?.main
  const canLaunchState = pkg.state === 'running' || pkg.state === 'starting'
  return !!hasUI && canLaunchState
}

function launchApp(id: string) {
  const isDev = import.meta.env.DEV
  const pkg = packages.value[id]

  // Web-only apps — use their external URL directly
  const webOnlyUrl = WEB_ONLY_APP_URLS[id]
  if (webOnlyUrl) {
    useAppLauncherStore().open({ url: webOnlyUrl, title: pkg?.manifest?.title || id })
    return
  }

  // Explicit URLs for apps that need them (checked first to avoid package data issues)
  const appUrls: Record<string, { dev: string, prod: string }> = {
    'lorabell': {
      dev: 'http://192.168.1.166',
      prod: 'http://192.168.1.166'
    },
    'atob': {
      dev: 'http://localhost:8102',
      prod: 'https://app.atobitcoin.io'
    },
    'k484': {
      dev: 'http://localhost:8103',
      prod: 'http://localhost:8103' // Self-hosted splash screen
    },
  }
  
  if (appUrls[id]) {
    let url = isDev ? appUrls[id].dev : appUrls[id].prod
    // Replace localhost with current hostname for remote access (not for external IPs like LoraBell)
    if (url.includes('localhost')) {
      const currentHost = window.location.hostname
      url = url.replace('localhost', currentHost)
    }
    useAppLauncherStore().open({ url, title: pkg?.manifest?.title || id })
    return
  }
  
  // Get the LAN address from the package
  let lanAddress = pkg?.installed?.['interface-addresses']?.main?.['lan-address']
  
  // Replace localhost with the current hostname (for remote access)
  if (lanAddress && lanAddress.includes('localhost')) {
    const currentHost = window.location.hostname
    lanAddress = lanAddress.replace('localhost', currentHost)
  }
  
  if (lanAddress) {
    useAppLauncherStore().open({ url: lanAddress, title: pkg?.manifest?.title || id })
    return
  }
  
  // For other apps, navigate to app details which has launch functionality
  router.push(`/dashboard/apps/${id}`).catch(() => {})
}

function getStatusClass(state: PackageState): string {
  switch (state) {
    case PackageState.Running:
      return 'bg-green-500/20 text-green-200'
    case PackageState.Stopped:
      return 'bg-gray-500/20 text-gray-200'
    case PackageState.Starting:
    case PackageState.Stopping:
    case PackageState.Restarting:
      return 'bg-yellow-500/20 text-yellow-200'
    case PackageState.Installing:
      return 'bg-blue-500/20 text-blue-200'
    default:
      return 'bg-gray-500/20 text-gray-200'
  }
}

function goToApp(id: string) {
  router.push(`/dashboard/apps/${id}`).catch(() => {})
}

const actionTimers = new Map<string, ReturnType<typeof setTimeout>>()

async function startApp(id: string) {
  loadingActions.value[id] = true
  try {
    await store.startPackage(id)
    if (actionTimers.has(id)) clearTimeout(actionTimers.get(id)!)
    actionTimers.set(id, setTimeout(() => {
      loadingActions.value[id] = false
      actionTimers.delete(id)
    }, 5000))
  } catch (err) {
    if (import.meta.env.DEV) console.error('Failed to start app:', err)
    showActionError(`Failed to start app: ${err instanceof Error ? err.message : 'Unknown error'}`)
    loadingActions.value[id] = false
  }
}

async function stopApp(id: string) {
  loadingActions.value[id] = true
  try {
    await store.stopPackage(id)
    if (actionTimers.has(id)) clearTimeout(actionTimers.get(id)!)
    actionTimers.set(id, setTimeout(() => {
      loadingActions.value[id] = false
      actionTimers.delete(id)
    }, 5000))
  } catch (err) {
    if (import.meta.env.DEV) console.error('Failed to stop app:', err)
    showActionError(`Failed to stop app: ${err instanceof Error ? err.message : 'Unknown error'}`)
    loadingActions.value[id] = false
  }
}

onBeforeUnmount(() => {
  for (const t of actionTimers.values()) clearTimeout(t)
  actionTimers.clear()
})


function showUninstallModal(id: string, pkg: PackageDataEntry) {
  uninstallModal.value = {
    show: true,
    appId: id,
    appTitle: pkg.manifest.title
  }
}

async function confirmUninstall() {
  const { appId } = uninstallModal.value
  uninstallModal.value.show = false
  
  try {
    await store.uninstallPackage(appId)
  } catch (err) {
    if (import.meta.env.DEV) console.error('Failed to uninstall app:', err)
    showActionError(`Failed to uninstall app: ${err instanceof Error ? err.message : 'Unknown error'}`)
  }
}

function handleImageError(e: Event) {
  const target = e.target as HTMLImageElement
  const currentSrc = target.src
  
  // Try fallback icon - use a simple placeholder SVG
  // Create a data URI for a simple icon placeholder
  const placeholderSvg = `data:image/svg+xml,${encodeURIComponent(`
    <svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
      <rect width="64" height="64" rx="12" fill="rgba(255,255,255,0.1)"/>
      <path d="M32 20L40 28H36V40H28V28H24L32 20Z" fill="rgba(255,255,255,0.6)"/>
      <path d="M20 44H44V48H20V44Z" fill="rgba(255,255,255,0.4)"/>
    </svg>
  `)}`
  
  // Only set fallback if we haven't already tried it
  if (!currentSrc.includes('data:image')) {
    target.src = placeholderSvg
  }
}

</script>

<style scoped>
.modal-enter-active,
.modal-leave-active {
  transition: opacity 0.3s ease;
}

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

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

.modal-enter-from .glass-card,
.modal-leave-to .glass-card {
  transform: scale(0.95);
  opacity: 0;
}
</style>
