<template>
  <div class="app-session-root">
  <Teleport to="body" :disabled="isInlinePanel && !isMobile">
  <div
    :class="backdropClasses"
    @click.self="handleBackdropClick"
  >
    <div
      ref="sessionRef"
      :class="panelClasses"
      @click.stop
    >
      <AppSessionHeader
        :app-title="appTitle"
        :is-refreshing="isRefreshing"
        :display-mode="displayMode"
        @go-back="iframeGoBack"
        @go-forward="iframeGoForward"
        @refresh="refresh"
        @open-new-tab="openNewTab"
        @close="closeSession"
        @set-mode="setMode"
      />

      <AppSessionFrame
        ref="frameRef"
        :app-url="appUrl"
        :app-id="appId"
        :app-title="appTitle"
        :app-icon="appIcon"
        :loading="loading"
        :iframe-blocked="iframeBlocked"
        :must-open-new-tab="mustOpenNewTab"
        :auto-retry-count="autoRetryCount"
        :refresh-key="refreshKey"
        :blocked-reason="blockedReason"
        :blocked-title="blockedTitle"
        :electrs-sync="electrsSync"
        @iframe-load="onLoad"
        @iframe-error="onError"
        @refresh="refresh"
        @open-new-tab-and-back="openNewTabAndBack"
      />

      <!-- Mobile: gamepad for botfights (with utility buttons), browser bar for everything else -->
      <MobileGamepad
        v-if="isMobile && appId === 'botfights'"
        :iframe-ref="iframeRef ?? null"
        :player="1"
        @refresh="refresh"
        @openBrowser="openNewTab"
        @close="closeSession"
      />
      <div v-else class="md:hidden app-session-mobile-bar">
        <button class="app-session-bar-btn" aria-label="Back" @click="iframeGoBack">
          <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
          </svg>
        </button>
        <button class="app-session-bar-btn" aria-label="Forward" @click="iframeGoForward">
          <svg 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>
        </button>
        <button class="app-session-bar-btn" aria-label="Refresh" @click="refresh">
          <svg fill="none" stroke="currentColor" viewBox="0 0 24 24" :class="{ 'animate-spin': isRefreshing }">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v6h6M20 20v-6h-6M5.64 15.36A8 8 0 0018.36 18M18.36 8.64A8 8 0 005.64 6" />
          </svg>
        </button>
        <button class="app-session-bar-btn" aria-label="Open in new tab" @click="openNewTab">
          <svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
          </svg>
        </button>
        <button class="app-session-bar-btn" aria-label="Close" @click="closeSession">
          <svg 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>
    </div>

    <NostrIdentityPicker
      :show="showIdentityPicker"
      :app-name="appTitle"
      @select="identity.onIdentitySelected"
      @cancel="showIdentityPicker = false"
    />
  </div>
  </Teleport>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAppLauncherStore } from '@/stores/appLauncher'
import { useAppStore } from '@/stores/app'
import { useScreensaverStore } from '@/stores/screensaver'
import NostrIdentityPicker from '@/components/NostrIdentityPicker.vue'
import AppSessionHeader from './appSession/AppSessionHeader.vue'
import AppSessionFrame from './appSession/AppSessionFrame.vue'
import MobileGamepad from './appSession/MobileGamepad.vue'
import {
  type DisplayMode, DISPLAY_MODE_KEY, NEW_TAB_APPS, IFRAME_BLOCKED_APPS,
  resolveAppUrl, resolveAppTitle,
} from './appSession/appSessionConfig'
import { launchBlockedReason, resolveAppIcon } from './apps/appsConfig'
import { useAppIdentity } from './appSession/useAppIdentity'
import { useNostrBridge } from './appSession/useNostrBridge'
import { openExternalUrl, openInAppOrNewTab } from '@/utils/openExternal'
import { useElectrsSync } from '@/composables/useElectrsSync'
import { IS_DEMO, isDemoExternal } from '@/composables/useDemoIntro'

const props = defineProps<{
  appIdProp?: string
}>()

const emit = defineEmits<{
  close: []
}>()

/** True when rendered inline via store (panel mode), false when route-based */
const isInlinePanel = computed(() => !!props.appIdProp)

const route = useRoute()
const router = useRouter()
const store = useAppStore()
const screensaverStore = useScreensaverStore()

const sessionRef = ref<HTMLElement | null>(null)
const frameRef = ref<InstanceType<typeof AppSessionFrame> | null>(null)
const loading = ref(true)
const isRefreshing = ref(false)
const iframeBlocked = ref(false)
const refreshKey = ref(0)
const showIdentityPicker = ref(false)
const autoRetryCount = ref(0)
let loadTimeoutId: ReturnType<typeof setTimeout> | null = null
let autoRetryId: ReturnType<typeof setTimeout> | null = null
let iframeCheckId: ReturnType<typeof setTimeout> | null = null

// Display mode -- persisted in localStorage
const displayMode = ref<DisplayMode>(
  (localStorage.getItem(DISPLAY_MODE_KEY) as DisplayMode) || 'panel'
)

const appId = computed(() => {
  const id = props.appIdProp || (route.params.appId as string)
  if (typeof id !== 'string' || !/^[a-z0-9][a-z0-9._-]*$/.test(id) || id.length > 64) {
    router.replace('/dashboard/apps')
    return ''
  }
  return id
})

const appTitle = computed(() => resolveAppTitle(appId.value))
const packageEntry = computed(() => store.data?.['package-data']?.[appId.value] || null)
const appIcon = computed(() =>
  packageEntry.value
    ? resolveAppIcon(appId.value, packageEntry.value)
    : `/assets/img/app-icons/${appId.value}.png`
)
const blockedReason = computed(() => launchBlockedReason(appId.value, packageEntry.value))
const blockedTitle = computed(() => appId.value === 'fedimint' || appId.value === 'fedimintd' ? 'Waiting for Bitcoin sync' : 'App not ready')
// Reactive so the overlay/teleport/footer/animation decisions track the live
// viewport (and match the CSS `md` breakpoint) instead of a stale one-shot read.
const isMobile = ref(typeof window !== 'undefined' && window.innerWidth < 768)
function updateIsMobile() { isMobile.value = window.innerWidth < 768 }
// In the demo, apps backed by a real external site that blocks iframing
// (mempool.space) open in a new tab rather than the in-app session frame.
const mustOpenNewTab = computed(() =>
  NEW_TAB_APPS.has(appId.value) || (IS_DEMO && isDemoExternal(appId.value))
)

// ElectrumX shows a sync screen before its real UI (the Electrum server only
// serves clients once its index is built). Poll /electrs-status while this is
// the Electrum app; pass the status to the frame only while still syncing.
const isElectrsApp = computed(() =>
  ['electrumx', 'electrs-ui', 'archy-electrs-ui'].includes(appId.value)
)
const { status: electrsStatus, syncing: electrsSyncing, start: startElectrsPoll, stop: stopElectrsPoll } =
  useElectrsSync()
const electrsSync = computed(() =>
  isElectrsApp.value && electrsSyncing.value ? electrsStatus.value : null
)
watch(
  isElectrsApp,
  (on) => { if (on) startElectrsPoll(); else stopElectrsPoll() },
  { immediate: true }
)
const screensaverReason = computed(() => `app-session:${appId.value}`)
const screensaverSuppressedApps = new Set([
  'indeedhub',
  'jellyfin',
  'immich',
  'photoprism',
  'filebrowser',
])

const appUrl = computed(() => {
  const runtimeUrl = store.data?.['package-data']?.[appId.value]?.installed?.['interface-addresses']?.main?.['lan-address'] || undefined
  return resolveAppUrl(appId.value, route.query.path as string | undefined, runtimeUrl)
})

function closeRouteSession() {
  const fallback = route.query.returnTo
  const fallbackPath = typeof fallback === 'string' && fallback.startsWith('/dashboard')
    ? fallback
    : '/dashboard/apps'
  router.replace(fallbackPath).catch(() => {})
}

// --- Identity & Nostr bridge ---

const iframeRef = computed(() => frameRef.value?.iframeRef ?? null)

const identity = useAppIdentity(appId, iframeRef, showIdentityPicker)
const nostrBridge = useNostrBridge(identity.getStoredIdentity, () => appUrl.value)

// --- Display mode ---

function setMode(mode: DisplayMode) {
  if (displayMode.value === 'fullscreen' && document.fullscreenElement) {
    document.exitFullscreen().catch(() => {})
  }
  displayMode.value = mode
  localStorage.setItem(DISPLAY_MODE_KEY, mode)

  // Switch from inline panel to route-based overlay/fullscreen
  if (isInlinePanel.value && mode !== 'panel') {
    const id = appId.value
    emit('close')
    const returnTo = route.fullPath.startsWith('/dashboard') ? route.fullPath : '/dashboard/apps'
    router.push({ name: 'app-session', params: { appId: id }, query: { returnTo } })
    return
  }

  // Switch from route-based to inline panel
  if (!isInlinePanel.value && mode === 'panel') {
    const id = appId.value
    const launcher = useAppLauncherStore()
    const fallback = route.query.returnTo
    const fallbackPath = typeof fallback === 'string' && fallback.startsWith('/dashboard')
      ? fallback
      : '/dashboard/apps'
    router.push(fallbackPath).then(() => {
      launcher.panelAppId = id
    })
    return
  }

  if (mode === 'fullscreen' && sessionRef.value && !document.fullscreenElement) {
    sessionRef.value.requestFullscreen().catch(() => {})
  }
}

// Reactive classes based on display mode. On mobile the store-driven panel
// renders as a full-screen overlay (teleported to body) so it covers the nav
// and the underlying page never changes — desktop keeps the inline panel.
const backdropClasses = computed(() => {
  if (isInlinePanel.value && !isMobile.value) return 'app-session-backdrop-inline'
  return 'app-session-backdrop-overlay'
})

const panelClasses = computed(() => {
  const base = 'app-session-panel glass-card'
  if (isInlinePanel.value && !isMobile.value) return `${base} app-session-inline`
  if (displayMode.value === 'fullscreen' && !isMobile.value) return `${base} app-session-fullscreen`
  return `${base} app-session-overlay`
})

// --- Lifecycle handlers ---

function onLoad() {
  if (loadTimeoutId) { clearTimeout(loadTimeoutId); loadTimeoutId = null }
  if (autoRetryId) { clearTimeout(autoRetryId); autoRetryId = null }
  loading.value = false
  isRefreshing.value = false
  autoRetryCount.value = 0
  // Check if iframe actually loaded content (same-origin only)
  iframeCheckId = setTimeout(() => {
    try {
      const iframe = frameRef.value?.iframeRef
      const doc = iframe?.contentDocument
      if (doc) {
        const body = doc.body
        if (!body || (body.children.length === 0 && body.innerText.trim() === '')) {
          iframeBlocked.value = true
        }
      }
    } catch {
      // Cross-origin -- can't check, assume OK
    }
  }, 1000)
  identity.onIframeLoadIdentity()
}

function onError() {
  if (loadTimeoutId) { clearTimeout(loadTimeoutId); loadTimeoutId = null }
  loading.value = false
  isRefreshing.value = false
  iframeBlocked.value = true
  // Auto-retry up to 6 times (60s total) for apps that are still starting
  if (!mustOpenNewTab.value && autoRetryCount.value < 6) {
    autoRetryId = setTimeout(() => {
      autoRetryCount.value++
      refresh()
    }, 10000)
  }
}

function refresh() {
  if (autoRetryId) { clearTimeout(autoRetryId); autoRetryId = null }
  isRefreshing.value = true
  loading.value = true
  iframeBlocked.value = false
  refreshKey.value++
  startLoadTimeout()
}

function startLoadTimeout() {
  if (loadTimeoutId) clearTimeout(loadTimeoutId)
  loadTimeoutId = setTimeout(() => {
    if (loading.value) {
      loading.value = false
      iframeBlocked.value = true
    }
  }, 12000)
}

function openNewTabAndBack() {
  if (appUrl.value) openExternalUrl(appUrl.value)
  closeSession()
}

function openNewTab() {
  if (appUrl.value) openExternalUrl(appUrl.value)
}

function iframeGoBack() {
  try { frameRef.value?.iframeRef?.contentWindow?.history.back() } catch {}
}

function iframeGoForward() {
  try { frameRef.value?.iframeRef?.contentWindow?.history.forward() } catch {}
}

function handleBackdropClick() {
  closeSession()
}

function closeSession() {
  if (document.fullscreenElement) document.exitFullscreen().catch(() => {})
  if (isInlinePanel.value) emit('close')
  else closeRouteSession()
}

function onKeyDown(e: KeyboardEvent) {
  if (e.key === 'Escape') {
    if (document.fullscreenElement) document.exitFullscreen().catch(() => {})
    else closeSession()
    e.preventDefault()
  }
}

function onFullscreenChange() {
  if (!document.fullscreenElement && displayMode.value === 'fullscreen') {
    displayMode.value = 'overlay'
    localStorage.setItem(DISPLAY_MODE_KEY, 'overlay')
  }
}

function onMessage(e: MessageEvent) {
  if (e.data?.type === 'nostr-request') nostrBridge.handleNostrRequest(e)
  if (e.data?.type === 'archipelago:identity:request') identity.handleIdentityRequest()
  if (e.data?.type === 'archipelago:media:playing') screensaverStore.suppress(screensaverReason.value)
  if (e.data?.type === 'archipelago:media:idle') screensaverStore.resume(screensaverReason.value)
}

// Enter fullscreen on mount if mode is fullscreen
watch(displayMode, (mode) => {
  if (mode === 'fullscreen' && sessionRef.value && !document.fullscreenElement) {
    sessionRef.value.requestFullscreen().catch(() => {})
  }
})

onMounted(() => {
  // Apps that block iframes (X-Frame-Options) can't be shown in the session.
  // Open them directly instead of showing a "this app opens in a tab"
  // interstitial: desktop → new browser tab; mobile → in-app WebView (companion)
  // or new tab (PWA). Then dismiss the (empty) session surface.
  if (mustOpenNewTab.value && appUrl.value) {
    if (isMobile.value) openInAppOrNewTab(appUrl.value)
    else window.open(appUrl.value, '_blank', 'noopener,noreferrer')
    if (isInlinePanel.value) emit('close')
    else closeRouteSession()
    return
  }

  window.addEventListener('keydown', onKeyDown, true)
  window.addEventListener('message', onMessage)
  window.addEventListener('resize', updateIsMobile)
  document.addEventListener('fullscreenchange', onFullscreenChange)
  if (IFRAME_BLOCKED_APPS.has(appId.value)) {
    loading.value = false
    iframeBlocked.value = true
  } else {
    startLoadTimeout()
  }
  if (displayMode.value === 'fullscreen') {
    requestAnimationFrame(() => {
      sessionRef.value?.requestFullscreen().catch(() => {})
    })
  }
  if (screensaverSuppressedApps.has(appId.value)) {
    screensaverStore.suppress(screensaverReason.value)
  }
})

onBeforeUnmount(() => {
  if (loadTimeoutId) clearTimeout(loadTimeoutId)
  if (autoRetryId) clearTimeout(autoRetryId)
  if (iframeCheckId) clearTimeout(iframeCheckId)
  window.removeEventListener('keydown', onKeyDown, true)
  window.removeEventListener('message', onMessage)
  window.removeEventListener('resize', updateIsMobile)
  document.removeEventListener('fullscreenchange', onFullscreenChange)
  screensaverStore.resume(screensaverReason.value)
  if (document.fullscreenElement) document.exitFullscreen().catch(() => {})
})
</script>

<style>
/* Unscoped so children can use these classes */
.app-session-root {
  width: 100%;
  height: 100%;
}
/* Inline panel mode -- fills content area, no blur, original layout */
.app-session-backdrop-inline {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.4);
  padding: 0;
}

.app-session-inline {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  width: 100%;
  height: 100%;
  border-radius: 0;
  border: none;
}

@media (min-width: 768px) {
  .app-session-backdrop-inline {
    padding: 1.5rem;
  }
  .app-session-inline {
    border-radius: 1rem;
    max-width: calc(100% - 1rem);
    max-height: calc(100vh - 6rem);
    box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
  }
}

/* Overlay mode -- covers entire viewport including sidebar */
.app-session-backdrop-overlay {
  position: fixed;
  inset: 0;
  z-index: 2400;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  background: rgba(0, 0, 0, 0.6);
  backdrop-filter: blur(12px);
}

@media (min-width: 768px) {
  .app-session-backdrop-overlay {
    padding: 2.5rem;
  }
}

.app-session-overlay {
  position: relative;
  z-index: 10;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  width: 100%;
  height: 100%;
  border-radius: 0;
  border: none;
  box-shadow: none;
}

@media (min-width: 768px) {
  .app-session-overlay {
    max-width: calc(100vw - 5rem);
    max-height: calc(100vh - 5rem);
    border-radius: 1rem;
  }
}

/* Fullscreen mode */
.app-session-fullscreen {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  width: 100%;
  height: 100%;
  border-radius: 0 !important;
  max-width: none !important;
  max-height: none !important;
}

/* Shared */
.app-session-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 8px;
  color: rgba(255, 255, 255, 0.7);
  transition: all 0.15s ease;
  flex-shrink: 0;
}
.app-session-btn:hover {
  background: rgba(255, 255, 255, 0.15);
  color: white;
}
.app-session-btn:disabled {
  opacity: 0.5;
}

/* Mode dropdown */
.mode-option {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 10px 14px;
  font-size: 13px;
  color: rgba(255, 255, 255, 0.7);
  transition: all 0.15s ease;
  text-align: left;
}
.mode-option:hover {
  background: rgba(255, 255, 255, 0.08);
  color: white;
}
.mode-option-active {
  color: #fb923c;
  background: rgba(251, 146, 60, 0.08);
}

.menu-fade-enter-active,
.menu-fade-leave-active {
  transition: opacity 0.15s ease, transform 0.15s ease;
}
.menu-fade-enter-from,
.menu-fade-leave-to {
  opacity: 0;
  transform: translateY(-4px);
}

.content-fade-enter-active,
.content-fade-leave-active {
  transition: opacity 0.2s ease;
}
.content-fade-enter-from,
.content-fade-leave-to {
  opacity: 0;
}

.app-session-frame-scroll-host {
  overflow: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

/* Mobile: full-bleed app sessions — no border, no radius, no shadow */
@media (max-width: 767px) {
  .app-session-root {
    height: 100%;
  }
  .app-session-inline {
    height: 100%;
  }
  .app-session-overlay,
  .app-session-fullscreen {
    height: 100vh;
    height: 100dvh;
    padding-top: var(--safe-area-top, env(safe-area-inset-top, 0px));
    background: black;
  }
  .app-session-panel.glass-card {
    border: none !important;
    border-radius: 0 !important;
    box-shadow: none !important;
  }
  .app-session-backdrop-overlay {
    padding: 0;
    backdrop-filter: none;
    background: black;
  }
  .app-session-frame-safe {
    flex: none !important;
    height: calc(100vh - var(--app-session-mobile-bar-height, 84px) - var(--safe-area-top, env(safe-area-inset-top, 0px)));
    height: calc(100dvh - var(--app-session-mobile-bar-height, 84px) - var(--safe-area-top, env(safe-area-inset-top, 0px)));
    padding-bottom: 0;
  }
}

/* Mobile bottom browser bar — sized like the main tab bar.
   Uses !important-free display so Tailwind md:hidden can override. */
@media (min-width: 768px) {
  .app-session-mobile-bar { display: none !important; }
}
.app-session-mobile-bar {
  display: flex;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2600;
  justify-content: space-around;
  align-items: center;
  flex-shrink: 0;
  min-height: var(--app-session-mobile-bar-height, 84px);
  padding: 10px 16px;
  padding-bottom: calc(10px + max(var(--safe-area-bottom, 0px), env(safe-area-inset-bottom, 0px), 10px));
  background: rgba(0, 0, 0, 0.25);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  border-top: 1px solid rgba(255, 255, 255, 0.06);
  transform: translateZ(0);
}

.app-session-inline .app-session-mobile-bar {
  position: absolute;
  z-index: 20;
}


.app-session-bar-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  min-height: 52px;
  border-radius: 13px;
  color: rgba(255, 255, 255, 0.65);
  transition: color 0.15s ease, background 0.15s ease;
}
.app-session-bar-btn svg {
  width: 24px;
  height: 24px;
}
.app-session-bar-btn:active {
  color: white;
  background: rgba(255, 255, 255, 0.12);
}
</style>
