<template>
  <div class="app-icon-grid-wrap">
    <!-- Swipeable pages -->
    <div
      ref="scrollContainer"
      class="app-icon-pages"
      @scroll="onScroll"
    >
      <div
        v-for="(page, pageIndex) in pages"
        :key="pageIndex"
        class="app-icon-page"
      >
        <div
          v-for="([id, pkg]) in page"
          :key="id"
          class="app-icon-item"
          role="button"
          :tabindex="0"
          :aria-label="getTitle(id, pkg)"
          @pointerdown="startLongPress(id)"
          @pointerup="clearLongPress"
          @pointercancel="clearLongPress"
          @pointerleave="clearLongPress"
          @contextmenu.prevent="openAppOptions(id)"
          @click="handleTap(id, pkg)"
          @keydown.enter="handleTap(id, pkg)"
          @keydown.space.prevent="openAppOptions(id)"
        >
          <!-- Icon with status indicator -->
          <div class="app-icon-frame">
            <img
              :src="getIcon(id, pkg)"
              :alt="getTitle(id, pkg)"
              class="app-icon-img archy-app-icon"
              @error="handleImageError"
            />
            <!-- Status dot -->
            <span
              v-if="pkg.state === 'running'"
              class="app-icon-status app-icon-status-running"
            ></span>
            <span
              v-else-if="pkg.state === 'exited'"
              class="app-icon-status app-icon-status-error"
            ></span>
            <span
              v-else-if="pkg.state === 'starting' || pkg.state === 'stopping' || pkg.state === 'installing'"
              class="app-icon-status app-icon-status-transition"
            ></span>
            <!-- Installing overlay -->
            <div
              v-if="serverStore.isInstalling(id) || serverStore.uninstallingApps.has(id)"
              class="app-icon-installing"
            >
              <svg class="animate-spin h-5 w-5 text-white" 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>
            </div>
          </div>
          <!-- Label -->
          <span class="app-icon-label">{{ getTitle(id, pkg) }}</span>
          <span
            v-if="serverStore.isInstalling(id) || serverStore.uninstallingApps.has(id)"
            class="app-icon-progress-label"
            :title="progressLabel(id, pkg)"
          >
            {{ progressLabel(id, pkg) }}
          </span>
        </div>
      </div>
    </div>

    <!-- Page dots -->
    <div v-if="pages.length > 1" class="app-icon-dots">
      <button
        v-for="(_, i) in pages"
        :key="i"
        class="app-icon-dot"
        :class="{ 'app-icon-dot-active': i === activePage }"
        :aria-label="`Page ${i + 1}`"
        @click="scrollToPage(i)"
      ></button>
    </div>

    <Transition name="fade">
      <div v-if="credentialModal.show" class="credential-modal-overlay fixed inset-0 z-[2700] flex items-stretch justify-stretch bg-black/80 backdrop-blur-md p-0" @click.self="closeCredentialModal">
        <div class="credential-modal-panel">
          <div class="flex items-start justify-between gap-4 mb-5">
            <div>
              <h2 class="text-lg font-semibold text-white">{{ credentialModal.title }}</h2>
              <p class="text-sm text-white/55 mt-1">{{ credentialModal.description }}</p>
            </div>
            <button type="button" class="sideload-close-btn" aria-label="Close" @click="closeCredentialModal">&times;</button>
          </div>
          <div class="credential-modal-body space-y-3">
            <div v-for="cred in credentialModal.credentials" :key="cred.label" class="rounded-lg border border-white/10 bg-white/[0.04] p-3">
              <div class="flex items-center justify-between gap-3 mb-1">
                <span class="text-white/60 text-xs uppercase tracking-wide">{{ cred.label }}</span>
                <button type="button" class="text-xs text-orange-300 hover:text-orange-200" @click="copyModalCredential(cred.label, cred.value)">{{ credentialModal.copied === cred.label ? 'Copied' : 'Copy' }}</button>
              </div>
              <p class="font-mono text-sm text-white break-all">{{ cred.value }}</p>
            </div>
          </div>
          <div class="credential-modal-actions mt-5 flex flex-col sm:flex-row gap-3">
            <button type="button" class="w-full sm:flex-1 glass-button px-4 py-3 rounded-lg" @click="closeCredentialModal">Cancel</button>
            <button type="button" class="w-full sm:flex-1 glass-button px-4 py-3 rounded-lg font-semibold" @click="continueCredentialLaunch">Continue to app</button>
          </div>
        </div>
      </div>
    </Transition>
  </div>
</template>

<script setup lang="ts">
import { computed, ref } from 'vue'
import { useServerStore } from '@/stores/server'
import { useAppLauncherStore } from '@/stores/appLauncher'
import type { AppCredential, AppCredentialsResponse, PackageDataEntry } from '@/types/api'
import { rpcClient } from '@/api/rpc-client'
import { resolveAppUrl } from '@/views/appSession/appSessionConfig'
import { resolveAppCredentials } from './appCredentials'
import { canLaunch, handleImageError, isWebsitePackage, opensInTab, resolveAppIcon, resolveRuntimeLaunchUrl, WEB_ONLY_APP_URLS } from './appsConfig'
import { getCuratedAppList } from '../discover/curatedApps'

const ITEMS_PER_PAGE = 16 // 4 columns x 4 rows

const serverStore = useServerStore()
const appLauncher = useAppLauncherStore()

const curatedMap = new Map(getCuratedAppList().map(a => [a.id, a]))
const credentialModal = ref({
  show: false,
  appId: '',
  title: '',
  description: '',
  credentials: [] as AppCredential[],
  copied: '',
})

const props = defineProps<{
  apps: [string, PackageDataEntry][]
}>()

const emit = defineEmits<{
  goToApp: [id: string]
}>()

const scrollContainer = ref<HTMLElement | null>(null)
const activePage = ref(0)
const longPressTriggered = ref(false)
let longPressTimer: ReturnType<typeof setTimeout> | null = null

const pages = computed(() => {
  const result: [string, PackageDataEntry][][] = []
  for (let i = 0; i < props.apps.length; i += ITEMS_PER_PAGE) {
    result.push(props.apps.slice(i, i + ITEMS_PER_PAGE))
  }
  return result.length ? result : [[]]
})

function getTitle(id: string, pkg: PackageDataEntry): string {
  const t = pkg.manifest?.title
  if (t && t !== id) return t
  return curatedMap.get(id)?.title || t || id
}

function getIcon(id: string, pkg: PackageDataEntry): string {
  return resolveAppIcon(id, pkg, curatedMap.get(id)?.icon)
}

function progressLabel(id: string, pkg: PackageDataEntry): string {
  const install = serverStore.installingApps.get(id)
  if (install) {
    return `${install.message || 'Installing...'} ${Math.round(install.progress || 0)}%`
  }
  if (serverStore.uninstallingApps.has(id)) {
    return pkg['uninstall-stage'] || ((pkg as unknown as Record<string, unknown>).uninstall_stage as string | undefined) || 'Removing...'
  }
  return ''
}

async function handleTap(id: string, pkg: PackageDataEntry) {
  if (longPressTriggered.value) {
    longPressTriggered.value = false
    return
  }
  if (canLaunch(pkg)) {
    const shown = await maybeShowCredentialsBeforeLaunch(id, pkg)
    if (shown) return
    launchNow(id, pkg)
  } else {
    emit('goToApp', id)
  }
}

function startLongPress(id: string) {
  clearLongPress()
  longPressTriggered.value = false
  longPressTimer = setTimeout(() => {
    longPressTriggered.value = true
    openAppOptions(id)
  }, 550)
}

function clearLongPress() {
  if (!longPressTimer) return
  clearTimeout(longPressTimer)
  longPressTimer = null
}

function openAppOptions(id: string) {
  clearLongPress()
  emit('goToApp', id)
}

function launchNow(id: string, pkg: PackageDataEntry) {
  const isMobile = typeof window !== 'undefined' && window.innerWidth < 768
  const webOnlyUrl = WEB_ONLY_APP_URLS[id]
  if (webOnlyUrl) {
    appLauncher.open({ url: webOnlyUrl, title: getTitle(id, pkg), openInNewTab: !isMobile })
    return
  }
  if (isWebsitePackage(id, pkg)) {
    const url = resolveRuntimeLaunchUrl(pkg)
    if (url) {
      appLauncher.open({ url, title: getTitle(id, pkg), openInNewTab: !isMobile })
      return
    }
  }
  if (!isMobile && opensInTab(id)) {
    const appUrl = resolveRuntimeLaunchUrl(pkg) || resolveAppUrl(id)
    if (appUrl) {
      window.open(appUrl, '_blank', 'noopener,noreferrer')
      return
    }
  }
  appLauncher.openSession(id)
}

async function maybeShowCredentialsBeforeLaunch(id: string, pkg: PackageDataEntry): Promise<boolean> {
  try {
    const result = await rpcClient.call<AppCredentialsResponse>({ method: 'package.credentials', params: { app_id: id }, timeout: 5000 })
    const credentials = resolveAppCredentials(id, result)
    if (!credentials) return false
    credentialModal.value = {
      show: true,
      appId: id,
      title: credentials.title || `${getTitle(id, pkg)} credentials`,
      description: credentials.description || 'Use these credentials when the app asks you to sign in.',
      credentials: credentials.credentials,
      copied: '',
    }
    return true
  } catch {
    const credentials = resolveAppCredentials(id, null)
    if (!credentials) return false
    credentialModal.value = {
      show: true,
      appId: id,
      title: credentials.title || `${getTitle(id, pkg)} credentials`,
      description: credentials.description || 'Use these credentials when the app asks you to sign in.',
      credentials: credentials.credentials,
      copied: '',
    }
    return true
  }
}

function closeCredentialModal() { credentialModal.value.show = false }

function continueCredentialLaunch() {
  const id = credentialModal.value.appId
  const entry = props.apps.find(([appId]) => appId === id)
  closeCredentialModal()
  if (entry) launchNow(entry[0], entry[1])
}

async function copyModalCredential(label: string, value: string) {
  try { await navigator.clipboard.writeText(value) } catch {
    const textarea = document.createElement('textarea')
    textarea.value = value
    document.body.appendChild(textarea)
    textarea.select()
    document.execCommand('copy')
    document.body.removeChild(textarea)
  }
  credentialModal.value.copied = label
}

function onScroll() {
  const el = scrollContainer.value
  if (!el) return
  const pageWidth = el.clientWidth
  if (pageWidth === 0) return
  activePage.value = Math.round(el.scrollLeft / pageWidth)
}

function scrollToPage(index: number) {
  const el = scrollContainer.value
  if (!el) return
  el.scrollTo({ left: index * el.clientWidth, behavior: 'smooth' })
}
</script>

<style scoped>
.sideload-modal {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 34rem;
  max-height: calc(100dvh - var(--safe-area-top, env(safe-area-inset-top, 0px)) - 12px);
  overflow: hidden;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 1.5rem 1.5rem 0 0;
  background: rgba(8, 10, 18, 0.94);
  padding: 1.25rem;
  padding-bottom: calc(1.25rem + var(--safe-area-bottom, env(safe-area-inset-bottom, 0px)));
  box-shadow: 0 -24px 70px rgba(0, 0, 0, 0.55);
}
.sideload-close-btn {
  width: 2.25rem;
  height: 2.25rem;
  flex-shrink: 0;
  border-radius: 0.75rem;
  color: rgba(255, 255, 255, 0.55);
  background: rgba(255, 255, 255, 0.06);
}
.credential-modal-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.credential-modal-panel {
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
  min-height: 0;
  max-width: none;
  max-height: none;
  overflow: hidden;
  border: 0;
  border-radius: 0;
  background: rgba(8, 10, 18, 0.98);
  padding: 1.25rem;
  padding-bottom: calc(1.25rem + var(--safe-area-bottom, env(safe-area-inset-bottom, 0px)));
  box-shadow: none;
}
.credential-modal-actions {
  flex-shrink: 0;
}
</style>
