<template>
  <div class="app-session-root">
  <Teleport to="body" :disabled="isInlinePanel">
  <div
    :class="backdropClasses"
    @click.self="handleBackdropClick"
  >
    <div
      ref="sessionRef"
      :class="panelClasses"
      @click.stop
    >
      <!-- Header bar -->
      <div class="sticky top-0 z-10 flex items-center gap-3 border-b border-white/10 px-4 py-3 bg-black/60 backdrop-blur-md md:bg-transparent md:backdrop-blur-none">
        <!-- Back / Forward navigation -->
        <div class="flex items-center gap-0.5">
          <button class="app-session-btn" aria-label="Back" title="Go back" @click="iframeGoBack">
            <svg class="w-4 h-4" 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-btn" aria-label="Forward" title="Go forward" @click="iframeGoForward">
            <svg class="w-4 h-4" 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>
        </div>

        <span class="flex-1 truncate text-sm font-medium text-white/90">{{ appTitle }}</span>

        <button class="app-session-btn" aria-label="Refresh" :disabled="isRefreshing" @click="refresh">
          <svg class="w-5 h-5 transition-transform duration-300" :class="{ 'animate-spin': isRefreshing }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
          </svg>
        </button>

        <!-- Display mode selector -->
        <div class="relative" ref="modeMenuRef">
          <button
            class="app-session-btn"
            aria-label="Display mode"
            title="Display mode"
            @click="showModeMenu = !showModeMenu"
          >
            <!-- Panel icon -->
            <svg v-if="displayMode === 'panel'" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v18m12-18H3a1 1 0 00-1 1v16a1 1 0 001 1h18a1 1 0 001-1V4a1 1 0 00-1-1z" />
            </svg>
            <!-- Overlay icon -->
            <svg v-else-if="displayMode === 'overlay'" class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v14a1 1 0 01-1 1H5a1 1 0 01-1-1V5z" />
            </svg>
            <!-- Fullscreen icon -->
            <svg v-else class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5v-4m0 4h-4m4 0l-5-5" />
            </svg>
          </button>

          <!-- Dropdown -->
          <Transition name="menu-fade">
            <div v-if="showModeMenu" class="absolute right-0 top-full mt-1 w-48 bg-black/90 border border-white/10 rounded-lg backdrop-blur-xl shadow-2xl overflow-hidden z-50">
              <button
                class="mode-option"
                :class="{ 'mode-option-active': displayMode === 'panel' }"
                @click="setMode('panel')"
              >
                <svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v18m12-18H3a1 1 0 00-1 1v16a1 1 0 001 1h18a1 1 0 001-1V4a1 1 0 00-1-1z" />
                </svg>
                <span>Right panel</span>
              </button>
              <button
                class="mode-option"
                :class="{ 'mode-option-active': displayMode === 'overlay' }"
                @click="setMode('overlay')"
              >
                <svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v14a1 1 0 01-1 1H5a1 1 0 01-1-1V5z" />
                </svg>
                <span>Over whole app</span>
              </button>
              <button
                class="mode-option"
                :class="{ 'mode-option-active': displayMode === 'fullscreen' }"
                @click="setMode('fullscreen')"
              >
                <svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8V4m0 0h4M4 4l5 5m11-1V4m0 0h-4m4 0l-5 5M4 16v4m0 0h4m-4 0l5-5m11 5v-4m0 4h-4m4 0l-5-5" />
                </svg>
                <span>Open fullscreen</span>
              </button>
            </div>
          </Transition>
        </div>

        <button class="app-session-btn" aria-label="Open in new tab" title="Open in new tab" @click="openNewTab">
          <svg class="w-5 h-5" 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-btn" aria-label="Close" @click="closeSession">
          <svg class="w-5 h-5" 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>

        <kbd class="hidden sm:inline-flex px-2 py-1 text-xs text-white/50 bg-white/10 rounded">Esc</kbd>
      </div>

      <!-- App frame -->
      <div class="relative flex-1 min-h-0 bg-black/40 overflow-hidden">
        <Transition name="content-fade">
          <div v-if="loading" class="absolute inset-0 z-10 flex items-center justify-center bg-black/40">
            <svg class="animate-spin h-8 w-8 text-blue-400" viewBox="0 0 24 24" fill="none">
              <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" />
              <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" />
            </svg>
          </div>
        </Transition>

        <iframe
          v-if="appUrl && !iframeBlocked"
          ref="iframeRef"
          :key="refreshKey"
          :src="appUrl"
          class="absolute inset-0 w-full h-full border-0 iframe-scrollbar-hide"
          title="App content"
          @load="onLoad"
          @error="onError"
        />

        <!-- Iframe blocked fallback -->
        <Transition name="content-fade">
          <div v-if="iframeBlocked" class="absolute inset-0 z-10 flex flex-col items-center justify-center">
            <div class="text-center px-8">
              <div class="w-16 h-16 mx-auto mb-4 rounded-2xl bg-white/5 border border-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="1.5" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
                </svg>
              </div>
              <h3 class="text-lg font-semibold text-white mb-2">{{ mustOpenNewTab ? 'This app opens in a new tab' : 'App not reachable' }}</h3>
              <p class="text-white/50 text-sm mb-6">
                <template v-if="mustOpenNewTab">{{ appTitle }} sets security headers that prevent iframe embedding.<br>Open it in a new browser tab instead.</template>
                <template v-else>{{ appTitle }} may still be starting up or the container is stopped.<br><span v-if="autoRetryCount > 0" class="text-yellow-400/70">Retrying automatically ({{ autoRetryCount }})...</span></template>
              </p>
              <div class="flex items-center gap-3">
                <button
                  v-if="!mustOpenNewTab"
                  @click="refresh"
                  class="glass-button px-6 py-3 rounded-lg text-sm font-semibold inline-flex items-center gap-2"
                >
                  <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
                  </svg>
                  Retry now
                </button>
                <button
                  @click="openNewTabAndBack"
                  class="glass-button px-6 py-3 rounded-lg text-sm font-semibold inline-flex items-center gap-2"
                >
                  <svg class="w-4 h-4" 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>
                  Open in new tab
                </button>
              </div>
            </div>
          </div>
        </Transition>

        <div v-if="!appUrl" class="absolute inset-0 flex items-center justify-center">
          <div class="text-center px-8">
            <h3 class="text-lg font-semibold text-white mb-2">App not configured</h3>
            <p class="text-white/50 text-sm">No URL found for {{ appId }}</p>
          </div>
        </div>
      </div>
    </div>

    <NostrIdentityPicker
      :show="showIdentityPicker"
      :app-name="appTitle"
      @select="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 { rpcClient } from '@/api/rpc-client'
import { useAppLauncherStore } from '@/stores/appLauncher'
import NostrIdentityPicker from '@/components/NostrIdentityPicker.vue'

type DisplayMode = 'panel' | 'overlay' | 'fullscreen'

const DISPLAY_MODE_KEY = 'archipelago_app_display_mode'

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 sessionRef = ref<HTMLElement | null>(null)
const iframeRef = ref<HTMLIFrameElement | null>(null)
const modeMenuRef = ref<HTMLElement | null>(null)
const loading = ref(true)
const isRefreshing = ref(false)
const iframeBlocked = ref(false)
const refreshKey = ref(0)
const showIdentityPicker = ref(false)
const showModeMenu = ref(false)
const autoRetryCount = ref(0)
let loadTimeoutId: ReturnType<typeof setTimeout> | null = null
let autoRetryId: ReturnType<typeof setTimeout> | null = null

/** Sites known to block iframes — skip the timeout and go straight to fallback */
const IFRAME_BLOCKED_APPS = new Set<string>([])

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

function setMode(mode: DisplayMode) {
  // Exit fullscreen first if switching away
  if (displayMode.value === 'fullscreen' && document.fullscreenElement) {
    document.exitFullscreen().catch(() => {})
  }
  displayMode.value = mode
  localStorage.setItem(DISPLAY_MODE_KEY, mode)
  showModeMenu.value = false

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

  // Switch from route-based → inline panel
  if (!isInlinePanel.value && mode === 'panel') {
    const id = appId.value
    const launcher = useAppLauncherStore()
    router.push({ name: 'apps' }).then(() => {
      launcher.panelAppId = id
    })
    return
  }

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

// Reactive classes based on display mode
const backdropClasses = computed(() => {
  if (isInlinePanel.value) return 'app-session-backdrop-inline'
  return 'app-session-backdrop-overlay'
})

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

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('/apps')
    return ''
  }
  return id
})

/** Container apps: direct port access (avoids root-relative asset breakage under /app/xxx/ proxy) */
const APP_PORTS: Record<string, number> = {
  'bitcoin-knots': 8334,
  'bitcoin-ui':    8334,
  'electrumx':     50002,
  'electrs':       50002,
  'archy-electrs-ui': 50002,
  'mempool-electrs': 50002,
  'btcpay-server': 23000,
  'lnd':           8081,
  'archy-lnd-ui':  8081,
  'mempool':       4080,
  'mempool-web':   4080,
  'archy-mempool-web': 4080,
  'homeassistant': 8123,
  'grafana':       3000,
  'searxng':       8888,
  'ollama':        11434,
  'onlyoffice':    8044,
  'penpot':        9001,
  'nextcloud':     8085,
  'vaultwarden':   8082,
  'jellyfin':      8096,
  'photoprism':    2342,
  'immich':        2283,
  'immich_server': 2283,
  'filebrowser':   8083,
  'nginx-proxy-manager': 8181,
  'portainer':     9000,
  'uptime-kuma':   3001,
  'fedimint':      8175,
  'fedimintd':     8175,
  'fedimint-gateway': 8176,
  'nostr-rs-relay': 18081,
  'indeedhub':     7777,
  'dwn':           3100,
  'endurain':      8080,
}

/** Apps that need nginx proxy for iframe embedding.
 *  IndeedHub loads via direct port 7777 — deploy script removes X-Frame-Options
 *  from the container's internal nginx so iframe works on all servers. */
const PROXY_APPS: Record<string, string> = {}

/** Nginx proxy paths — used on HTTPS to avoid mixed content (HTTPS parent + HTTP port iframe).
 *  On HTTP, direct port access is used instead (faster, no proxy). */
const HTTPS_PROXY_PATHS: Record<string, string> = {
  'bitcoin-knots': '/app/bitcoin-ui/',
  'bitcoin-ui': '/app/bitcoin-ui/',
  'lnd': '/app/lnd/',
  'electrumx': '/app/electrs/',
  'electrs': '/app/electrs/',
  'mempool-electrs': '/app/electrs/',
  'mempool': '/app/mempool/',
  'mempool-web': '/app/mempool/',
  'archy-mempool-web': '/app/mempool/',
  'fedimint': '/app/fedimint/',
  'fedimintd': '/app/fedimint/',
  'fedimint-gateway': '/app/fedimint-gateway/',
  'jellyfin': '/app/jellyfin/',
  'searxng': '/app/searxng/',
  'filebrowser': '/app/filebrowser/',
  'ollama': '/app/ollama/',
  'onlyoffice': '/app/onlyoffice/',
  'immich': '/app/immich/',
  'immich_server': '/app/immich/',
  'portainer': '/app/portainer/',
  'nginx-proxy-manager': '/app/nginx-proxy-manager/',
  'uptime-kuma': '/app/uptime-kuma/',
  'homeassistant': '/app/homeassistant/',
  'vaultwarden': '/app/vaultwarden/',
  'photoprism': '/app/photoprism/',
  'endurain': '/app/endurain/',
  'dwn': '/app/dwn/',
}

/** External HTTPS apps — always loaded directly */
const EXTERNAL_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': hidden until X-Frame-Options fixed on present.l484.com
  'syntropy-institute': 'https://syntropy.institute',
  't-zero': 'https://teeminuszero.net',
  'nostrudel': 'https://nostrudel.ninja',
}

const APP_TITLES: Record<string, string> = {
  'bitcoin-knots': 'Bitcoin', 'btcpay-server': 'BTCPay Server', 'indeedhub': 'Indeehub',
  'botfights': 'BotFights', '484-kitchen': '484 Kitchen', 'arch-presentation': 'Presentation',
  'homeassistant': 'Home Assistant', 'uptime-kuma': 'Uptime Kuma',
  'nginx-proxy-manager': 'Nginx Proxy Manager', 'nostr-rs-relay': 'Nostr Relay',
  'call-the-operator': 'Call The Operator', 'syntropy-institute': 'Syntropy Institute',
  't-zero': 'T-Zero', 'nostrudel': 'noStrudel',
}

const appTitle = computed(() => APP_TITLES[appId.value] || appId.value.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase()))

/** Apps that set X-Frame-Options and MUST open in a new tab (can't iframe) */
const NEW_TAB_APPS = new Set([
  'btcpay-server',      // X-Frame-Options: DENY
  'grafana',            // X-Frame-Options: deny
  'photoprism',         // X-Frame-Options: DENY
  'homeassistant',      // X-Frame-Options: SAMEORIGIN
  'vaultwarden',        // X-Frame-Options: SAMEORIGIN
  'nextcloud',          // X-Frame-Options: SAMEORIGIN
  'uptime-kuma',        // X-Frame-Options: SAMEORIGIN
  'penpot',             // Blocks iframe
  'portainer',          // X-Frame-Options: deny
  'onlyoffice',         // X-Frame-Options: SAMEORIGIN
  'nginx-proxy-manager', // X-Frame-Options blocks
])

const mustOpenNewTab = computed(() => NEW_TAB_APPS.has(appId.value))

const appUrl = computed(() => {
  const id = appId.value

  // External HTTPS apps — iframe overlay
  const ext = EXTERNAL_URLS[id]
  if (ext) return ext

  // Apps that need nginx proxy (nostr-provider.js injection for NIP-07)
  const proxyPath = PROXY_APPS[id]
  if (proxyPath) return `${window.location.origin}${proxyPath}`

  // IndeedHub: always direct port (X-Frame-Options removed by deploy script)
  if (id === 'indeedhub') {
    const port = APP_PORTS[id]
    if (port) {
      let base = `${window.location.protocol}//${window.location.hostname}:${port}`
      const subpath = route.query.path as string | undefined
      if (subpath) base += subpath
      return base
    }
  }

  // HTTPS: use nginx proxy to avoid mixed content (browser blocks HTTP iframes in HTTPS pages)
  if (window.location.protocol === 'https:') {
    const httpsProxy = HTTPS_PROXY_PATHS[id]
    if (httpsProxy) return `${window.location.origin}${httpsProxy}`
  }

  // HTTP: direct port access (faster, no proxy overhead)
  const port = APP_PORTS[id]
  if (!port) return ''
  let base = `http://${window.location.hostname}:${port}`

  // Append sub-path from query param (e.g. ?path=/tx/abc123)
  const subpath = route.query.path as string | undefined
  if (subpath) base += subpath

  return base
})

// --- Identity ---

function isIdentityAwareApp(id: string): boolean {
  return id === 'indeedhub' || id === 'nostrudel'
}

const IDENTITY_KEY = 'archipelago_app_identity_'

interface SelectedIdentity {
  id: string; name: string; did: string; pubkey: string
  nostr_pubkey?: string; nostr_npub?: string
}

function getStoredIdentity(): SelectedIdentity | null {
  try {
    const stored = localStorage.getItem(IDENTITY_KEY + appId.value)
    return stored ? JSON.parse(stored) as SelectedIdentity : null
  } catch { return null }
}

function storeIdentity(identity: SelectedIdentity) {
  try { localStorage.setItem(IDENTITY_KEY + appId.value, JSON.stringify(identity)) } catch {}
}

function onIdentitySelected(identity: SelectedIdentity) {
  showIdentityPicker.value = false
  storeIdentity(identity)
  sendIdentity(identity)
  // NIP-98 auto-login disabled — apps like IndeedHub have their own login flow
  // that properly sets up internal account state. We provide window.nostr via
  // nostr-provider.js so the app's built-in "Sign In" button works.
}

async function sendIdentity(identity: SelectedIdentity) {
  try {
    const challenge = `archipelago-identity:${Date.now()}`
    const sigRes = await rpcClient.call<{ signature: string }>({ method: 'identity.sign', params: { id: identity.id, message: challenge } })
    iframeRef.value?.contentWindow?.postMessage({
      type: 'archipelago:identity', did: identity.did, name: identity.name,
      pubkey: identity.pubkey, nostr_pubkey: identity.nostr_pubkey || null,
      nostr_npub: identity.nostr_npub || null, challenge, signature: sigRes.signature
    }, '*')
  } catch {}
}

// NIP-98 auto-login removed — apps handle their own login via window.nostr (NIP-07)

// --- Lifecycle ---

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)
  setTimeout(() => {
    try {
      const doc = iframeRef.value?.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)
  if (isIdentityAwareApp(appId.value)) {
    const stored = getStoredIdentity()
    if (stored) sendIdentity(stored)
    else showIdentityPicker.value = true
  }
}

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) window.open(appUrl.value, '_blank', 'noopener,noreferrer')
  closeSession()
}

function openNewTab() {
  if (appUrl.value) window.open(appUrl.value, '_blank', 'noopener,noreferrer')
}

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

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

function handleBackdropClick() {
  closeSession()
}

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

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

// Close dropdown on outside click
function onClickOutside(e: MouseEvent) {
  if (showModeMenu.value && modeMenuRef.value && !modeMenuRef.value.contains(e.target as Node)) {
    showModeMenu.value = false
  }
}

function onFullscreenChange() {
  if (!document.fullscreenElement && displayMode.value === 'fullscreen') {
    // User exited fullscreen via browser UI — switch to overlay
    displayMode.value = 'overlay'
    localStorage.setItem(DISPLAY_MODE_KEY, 'overlay')
  }
}

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

// --- NIP-07 ---

function onMessage(e: MessageEvent) {
  if (e.data?.type === 'nostr-request') handleNostrRequest(e)
  if (e.data?.type === 'archipelago:identity:request') {
    const stored = getStoredIdentity()
    if (stored) sendIdentity(stored)
    else showIdentityPicker.value = true
  }
}

async function handleNostrRequest(event: MessageEvent) {
  const { id, method, params } = event.data
  const source = event.source as Window | null
  if (!source) return
  const storedIdentity = getStoredIdentity()
  const identityId = storedIdentity?.id || null
  if (import.meta.env.DEV) console.log(`[NIP-07] ${method} identityId=${identityId} storedPubkey=${storedIdentity?.nostr_pubkey?.slice(0, 12) || 'none'}`)

  try {
    let result: unknown
    if (method === 'getPublicKey') {
      // Use stored nostr_pubkey directly if available (avoids RPC call that may 401)
      if (storedIdentity?.nostr_pubkey) {
        result = storedIdentity.nostr_pubkey
        if (import.meta.env.DEV) console.log('[NIP-07] getPublicKey from stored identity:', (result as string).slice(0, 12))
      } else if (identityId) {
        const res = await rpcClient.call<{ nostr_pubkey: string }>({ method: 'identity.get', params: { id: identityId } })
        result = res.nostr_pubkey
      } else {
        const res = await rpcClient.call<{ nostr_pubkey: string }>({ method: 'node.nostr-pubkey' })
        result = res.nostr_pubkey
      }
    } else if (method === 'signEvent') {
      if (import.meta.env.DEV) console.log(`[NIP-07] signEvent kind=${params.event?.kind} using identity=${identityId || 'node-default'}`)
      if (identityId) {
        result = await rpcClient.call<unknown>({ method: 'identity.nostr-sign', params: { id: identityId, event: params.event } })
      } else {
        result = await rpcClient.call<unknown>({ method: 'node.nostr-sign', params: { event: params.event } })
      }
      if (import.meta.env.DEV) console.log('[NIP-07] signEvent OK')
    } else if (method === 'getRelays') { result = {} }
    else if (method === 'nip04.encrypt') { result = (await rpcClient.call<{ ciphertext: string }>({ method: 'identity.nostr-encrypt-nip04', params: { id: identityId || undefined, pubkey: params.pubkey, plaintext: params.plaintext } })).ciphertext }
    else if (method === 'nip04.decrypt') { result = (await rpcClient.call<{ plaintext: string }>({ method: 'identity.nostr-decrypt-nip04', params: { id: identityId || undefined, pubkey: params.pubkey, ciphertext: params.ciphertext } })).plaintext }
    else if (method === 'nip44.encrypt') { result = (await rpcClient.call<{ ciphertext: string }>({ method: 'identity.nostr-encrypt-nip44', params: { id: identityId || undefined, pubkey: params.pubkey, plaintext: params.plaintext } })).ciphertext }
    else if (method === 'nip44.decrypt') { result = (await rpcClient.call<{ plaintext: string }>({ method: 'identity.nostr-decrypt-nip44', params: { id: identityId || undefined, pubkey: params.pubkey, ciphertext: params.ciphertext } })).plaintext }
    else { throw new Error(`Unsupported NIP-07 method: ${method}`) }
    const targetOrigin = appUrl.value ? new URL(appUrl.value).origin : '*'
    source.postMessage({ type: 'nostr-response', id, result }, targetOrigin)
  } catch (err) {
    if (import.meta.env.DEV) console.error(`[NIP-07] ${method} FAILED:`, err instanceof Error ? err.message : err)
    const targetOrigin = appUrl.value ? new URL(appUrl.value).origin : '*'
    source.postMessage({ type: 'nostr-response', id, error: err instanceof Error ? err.message : 'Unknown error' }, targetOrigin)
  }
}

onMounted(() => {
  // Apps that block iframes (X-Frame-Options) — open in new tab, close session
  if (mustOpenNewTab.value && appUrl.value) {
    window.open(appUrl.value, '_blank', 'noopener,noreferrer')
    if (isInlinePanel.value) emit('close')
    else router.back()
    return
  }

  window.addEventListener('keydown', onKeyDown, true)
  window.addEventListener('message', onMessage)
  document.addEventListener('click', onClickOutside)
  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(() => {})
    })
  }
})

onBeforeUnmount(() => {
  if (loadTimeoutId) clearTimeout(loadTimeoutId)
  if (autoRetryId) clearTimeout(autoRetryId)
  window.removeEventListener('keydown', onKeyDown, true)
  window.removeEventListener('message', onMessage)
  document.removeEventListener('click', onClickOutside)
  document.removeEventListener('fullscreenchange', onFullscreenChange)
  if (document.fullscreenElement) document.exitFullscreen().catch(() => {})
})
</script>

<style scoped>
.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;
}

@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;
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.6);
}

@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;
}
</style>
