<template>
  <Teleport to="body">
    <Transition name="overlay-fade">
      <div
        v-if="visible"
        class="fixed inset-0 flex items-end sm:items-center justify-center p-4 z-[3000]"
        @click.self="dismiss"
      >
        <div class="absolute inset-0 bg-black/40 backdrop-blur-sm" />
        <div
          class="glass-card p-5 w-full max-w-sm relative z-10 mb-20 sm:mb-0 overflow-hidden"
          @click.stop
        >
          <button
            type="button"
            class="absolute right-3 top-3 h-8 w-8 rounded-full bg-white/5 border border-white/10 text-white/60 hover:text-white hover:bg-white/10 transition-colors z-10"
            aria-label="Close companion modal"
            @click="dismiss"
          >
            &times;
          </button>

          <Transition :name="slideName" mode="out-in">
            <!-- Screen 1: get the app -->
            <div v-if="step === 'download'" key="download">
              <div class="flex items-start gap-4 mb-4">
                <div class="w-12 h-12 rounded-xl bg-orange-500/15 border border-orange-500/30 flex items-center justify-center flex-shrink-0">
                  <svg class="w-7 h-7 text-orange-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
                    <rect x="3" y="7" width="18" height="11" rx="3" stroke-width="1.5" />
                    <rect x="7.5" y="10" width="2" height="5" rx="0.5" fill="currentColor" />
                    <rect x="6" y="11.5" width="5" height="2" rx="0.5" fill="currentColor" />
                    <circle cx="16" cy="11" r="1.2" fill="currentColor" />
                    <circle cx="14" cy="13.5" r="1.2" fill="currentColor" />
                  </svg>
                </div>
                <div class="min-w-0 flex-1">
                  <h3 class="text-lg font-semibold text-white mb-1">Remote Companion</h3>
                  <p class="text-sm text-white/60 leading-relaxed">
                    Install the Archipelago companion app on your phone, scan the code, and connect to the same node.
                  </p>
                </div>
              </div>

              <div class="hidden md:flex justify-center mb-4">
                <div class="w-[128px] rounded-2xl border border-white/10 bg-white/[0.03] p-1 overflow-hidden">
                  <img
                    v-if="qrDataUrl"
                    :src="qrDataUrl"
                    alt="Companion app download QR code"
                    class="block w-full max-w-full h-auto rounded-lg bg-white"
                  />
                  <div v-else class="w-full aspect-square rounded-lg bg-white/5"></div>
                </div>
              </div>

              <div class="flex gap-2">
                <a
                  :href="companionDownloadUrl"
                  class="flex-1 inline-flex items-center justify-center rounded-lg bg-orange-500/20 border border-orange-500/30 px-3 py-2.5 text-sm font-medium text-orange-400 hover:bg-orange-500/30 transition-colors text-center"
                  target="_blank"
                  rel="noopener noreferrer"
                  download
                >
                  Download app
                </a>
                <button
                  type="button"
                  class="flex-1 rounded-lg bg-white/5 border border-white/15 px-3 py-2.5 text-sm font-medium text-white/80 hover:bg-white/10 hover:text-white transition-colors"
                  @click="advanceFromDownload"
                >
                  I've installed it
                </button>
              </div>
            </div>

            <!-- Screen 2 (remote access): install WireGuard -->
            <div v-else-if="step === 'wireguard'" key="wireguard">
              <div class="flex items-start gap-4 mb-4">
                <div class="w-12 h-12 rounded-xl bg-orange-500/15 border border-orange-500/30 flex items-center justify-center flex-shrink-0">
                  <svg class="w-7 h-7 text-orange-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 3l7 3v5c0 4.5-3 8.5-7 10-4-1.5-7-5.5-7-10V6l7-3z" />
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9.5 12l1.8 1.8L14.8 10" />
                  </svg>
                </div>
                <div class="min-w-0 flex-1">
                  <h3 class="text-lg font-semibold text-white mb-1">Install WireGuard for remote access</h3>
                  <p class="text-sm text-white/60 leading-relaxed">
                    WireGuard gives your phone a private tunnel to this node, so the companion app works away from home too.
                  </p>
                </div>
              </div>

              <div class="hidden md:flex justify-center mb-4">
                <div class="w-[128px] rounded-2xl border border-white/10 bg-white/[0.03] p-1 overflow-hidden">
                  <img
                    v-if="wgApkQrDataUrl"
                    :src="wgApkQrDataUrl"
                    alt="WireGuard app download QR code"
                    class="block w-full max-w-full h-auto rounded-lg bg-white"
                  />
                  <div v-else class="w-full aspect-square rounded-lg bg-white/5"></div>
                </div>
              </div>

              <div class="flex gap-2">
                <a
                  :href="wireguardDownloadUrl"
                  class="flex-1 inline-flex items-center justify-center rounded-lg bg-orange-500/20 border border-orange-500/30 px-3 py-2.5 text-sm font-medium text-orange-400 hover:bg-orange-500/30 transition-colors text-center"
                  target="_blank"
                  rel="noopener noreferrer"
                  download
                >
                  Download WireGuard
                </a>
                <button
                  type="button"
                  class="flex-1 rounded-lg bg-white/5 border border-white/15 px-3 py-2.5 text-sm font-medium text-white/80 hover:bg-white/10 hover:text-white transition-colors"
                  @click="showWgQrScreen"
                >
                  I've installed it
                </button>
              </div>

              <button
                type="button"
                class="w-full mt-2 py-2.5 rounded-lg bg-white/5 border border-white/15 text-white/80 text-sm font-medium hover:bg-white/10 hover:text-white transition-colors"
                @click="showDownloadScreen"
              >
                Back
              </button>
            </div>

            <!-- Screen 3 (remote access): scan the tunnel config in WireGuard -->
            <div v-else-if="step === 'wgqr'" key="wgqr">
              <div class="flex items-start gap-4 mb-4">
                <div class="w-12 h-12 rounded-xl bg-orange-500/15 border border-orange-500/30 flex items-center justify-center flex-shrink-0">
                  <svg class="w-7 h-7 text-orange-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 8v8m-4-4h8" />
                    <rect x="3.5" y="3.5" width="17" height="17" rx="4" stroke-width="1.5" />
                  </svg>
                </div>
                <div class="min-w-0 flex-1">
                  <h3 class="text-lg font-semibold text-white mb-1">Connect the tunnel</h3>
                  <p class="text-sm text-white/60 leading-relaxed">
                    In WireGuard, tap <strong>+</strong>, scan this code, then switch the new tunnel on.
                  </p>
                </div>
              </div>

              <div class="flex justify-center mb-3">
                <div class="w-[192px] rounded-2xl border border-white/10 bg-white/[0.03] p-1 overflow-hidden">
                  <img
                    v-if="wgQrDataUrl"
                    :src="wgQrDataUrl"
                    alt="WireGuard tunnel config QR code"
                    class="block w-full max-w-full h-auto rounded-lg bg-white"
                  />
                  <div v-else class="w-full aspect-square rounded-lg bg-white/5 flex items-center justify-center">
                    <svg v-if="wgLoading" class="w-6 h-6 animate-spin text-white/40" fill="none" viewBox="0 0 24 24"><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 12h4z" /></svg>
                  </div>
                </div>
              </div>
              <p v-if="wgError" class="text-xs text-red-400 text-center mb-3">{{ wgError }}</p>

              <!-- Same-device path: a phone can't scan its own screen, so offer
                   the config as a file WireGuard can import. -->
              <button
                v-if="wgConfig"
                type="button"
                class="md:hidden inline-flex w-full items-center justify-center rounded-lg bg-white/5 border border-white/15 px-4 py-2.5 text-sm font-medium text-white/80 hover:bg-white/10 hover:text-white transition-colors mb-2"
                @click="downloadWgConfig"
              >
                Download tunnel config
              </button>

              <button
                type="button"
                class="w-full py-2.5 rounded-lg bg-orange-500/20 border border-orange-500/30 text-orange-400 text-sm font-medium hover:bg-orange-500/30 transition-colors mb-2"
                @click="showPairScreen"
              >
                I'm connected
              </button>

              <button
                type="button"
                class="w-full py-2.5 rounded-lg bg-white/5 border border-white/15 text-white/80 text-sm font-medium hover:bg-white/10 hover:text-white transition-colors"
                @click="showWireguardScreen('back')"
              >
                Back
              </button>
            </div>

            <!-- Screen 2: pair the app with this node -->
            <div v-else key="pair">
              <div class="flex items-start gap-4 mb-4">
                <div class="w-12 h-12 rounded-xl bg-orange-500/15 border border-orange-500/30 flex items-center justify-center flex-shrink-0">
                  <svg class="w-7 h-7 text-orange-400" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
                    <rect x="4" y="4" width="7" height="7" rx="1.5" stroke-width="1.5" />
                    <rect x="13" y="4" width="7" height="7" rx="1.5" stroke-width="1.5" />
                    <rect x="4" y="13" width="7" height="7" rx="1.5" stroke-width="1.5" />
                    <path d="M13 13h3v3h-3zM17 17h3v3h-3z" fill="currentColor" stroke="none" />
                  </svg>
                </div>
                <div class="min-w-0 flex-1">
                  <h3 class="text-lg font-semibold text-white mb-1">Connect your app</h3>
                  <p class="text-sm text-white/60 leading-relaxed">
                    In the companion app, choose “Scan Node's QR” and point your phone here — the server details fill in automatically.
                  </p>
                </div>
              </div>

              <div class="flex justify-center mb-4">
                <!-- Bigger than the download QR: this one is scanned by the
                     companion app camera, and physical size drives decode. -->
                <div class="w-[192px] rounded-2xl border border-white/10 bg-white/[0.03] p-1 overflow-hidden">
                  <img
                    v-if="pairQrDataUrl"
                    :src="pairQrDataUrl"
                    alt="Companion app pairing QR code"
                    class="block w-full max-w-full h-auto rounded-lg bg-white"
                  />
                  <div v-else class="w-full aspect-square rounded-lg bg-white/5"></div>
                </div>
              </div>

              <!-- Same-device path: a QR on the phone's own screen can't be
                   scanned, so offer the deep link directly on small screens. -->
              <a
                v-if="pairingUrl"
                :href="pairingUrl"
                class="md:hidden inline-flex w-full items-center justify-center rounded-lg bg-orange-500/20 border border-orange-500/30 px-4 py-2.5 text-sm font-medium text-orange-400 hover:bg-orange-500/30 transition-colors mb-2"
              >
                Open in companion app
              </a>

              <button
                type="button"
                class="w-full py-2.5 rounded-lg bg-white/5 border border-white/15 text-white/80 text-sm font-medium hover:bg-white/10 hover:text-white transition-colors"
                @click="backFromPair"
              >
                Back
              </button>
            </div>
          </Transition>
        </div>
      </div>
    </Transition>
  </Teleport>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch } from 'vue'
import * as QRCode from 'qrcode'
import { IS_DEMO, DEMO_PASSWORD } from '@/composables/useDemoIntro'
import { companionIntroRequested } from '@/composables/useCompanionIntro'
import { useLoginTransitionStore } from '@/stores/loginTransition'
import { rpcClient } from '@/api/rpc-client'

const STORAGE_KEY = 'neode_companion_intro_seen'
// Absolute URL so the QR works when scanned by a phone (a relative path has no
// host to resolve). Points at the companion APK hosted on the 146 release server
// (publicly reachable) rather than the local node's /packages copy.
// The demo serves the APK from its own public origin instead, so the QR never
// exposes the release-server address.
const DEFAULT_DOWNLOAD_URL = IS_DEMO
  ? `${window.location.origin}/packages/archipelago-companion.apk`
  : 'http://146.59.87.168:3000/lfg2025/archy/raw/branch/main/neode-ui/public/packages/archipelago-companion.apk'

// Deep-link scheme the companion app registers; carries the server entry the
// app should create (see docs/companion-pairing-qr.md for the contract).
const PAIR_SCHEME = 'archipelago://pair'
const DEMO_SERVER_URL = 'https://demo.archipelago-foundation.org'

// Remote-access onboarding: inserts the WireGuard install + tunnel-QR steps
// between "I've installed it" and the pairing QR. Real nodes pair against the
// node's VPN address instead of the LAN origin; the demo walks the same steps
// with a mock tunnel config (its pairing QR still carries the public demo
// URL). Set to false to restore the original two-screen flow (instant revert).
const REMOTE_ACCESS_STEPS = true

// The WireGuard peer config only routes the VPN subnet (AllowedIPs
// 10.44.0.0/16 — core/archipelago/src/api/rpc/vpn.rs), so away from the LAN
// the node is reachable exclusively at its tunnel address. The pairing QR
// must therefore advertise this, not the browser's LAN origin. The flow has
// the user switch the tunnel on right before pairing, so it validates.
const VPN_NODE_URL = 'http://10.44.0.1'

// Official WireGuard Android app (com.wireguard.android 1.0.20260315,
// sha256 f92971bc…, mirrored from download.wireguard.com), served like the
// companion APK: gitea raw-on-main for nodes, own origin for the demo.
const WIREGUARD_DOWNLOAD_URL = IS_DEMO
  ? `${window.location.origin}/packages/wireguard.apk`
  : 'http://146.59.87.168:3000/lfg2025/archy/raw/branch/main/neode-ui/public/packages/wireguard.apk'

// Name of the auto-created VPN peer for the phone running the companion.
const WG_PEER_NAME = 'companion-phone'

const visible = ref(false)
const step = ref<'download' | 'wireguard' | 'wgqr' | 'pair'>('download')
const slideName = ref('slide-forward')
const qrDataUrl = ref('')
const pairQrDataUrl = ref('')
const pairingUrl = ref('')
const wgApkQrDataUrl = ref('')
const wgQrDataUrl = ref('')
const wgConfig = ref('')
const wgLoading = ref(false)
const wgError = ref('')
const companionDownloadUrl = import.meta.env.VITE_COMPANION_APK_URL || DEFAULT_DOWNLOAD_URL
const wireguardDownloadUrl = import.meta.env.VITE_WIREGUARD_APK_URL || WIREGUARD_DOWNLOAD_URL

const loginTransition = useLoginTransitionStore()

// Base delay before the popup may appear, and extra breathing room after the
// dashboard entrance cinematic ends so the popup never cuts into the reveal.
const BASE_DELAY_MS = 5000
const POST_INTRO_GRACE_MS = 2000

let calmTicker: ReturnType<typeof setInterval> | null = null

onMounted(() => {
  try {
    if (localStorage.getItem(STORAGE_KEY) !== '1') {
      setTimeout(maybeShow, BASE_DELAY_MS)
    }
  } catch {
    // localStorage unavailable
  }
})

onUnmounted(() => {
  if (calmTicker) clearInterval(calmTicker)
})

function maybeShow() {
  // Show only after the scene has been CONTINUOUSLY calm (no reveal
  // cinematic) for the full grace window. The previous point-in-time check
  // raced a slow-starting reveal — on a cold cache the entrance video can
  // begin buffering after the 5s base delay, so the flag was still false
  // when sampled and the popup cut straight into the cinematic.
  let calmSince = loginTransition.introCinematicPlaying ? null : Date.now()
  calmTicker = setInterval(() => {
    if (loginTransition.introCinematicPlaying) {
      calmSince = null
      return
    }
    if (calmSince === null) calmSince = Date.now()
    if (Date.now() - calmSince >= POST_INTRO_GRACE_MS) {
      if (calmTicker) clearInterval(calmTicker)
      calmTicker = null
      visible.value = true
    }
  }, 250)
}

// Manual open (App Store banner etc.) — ignores the once-per-browser gate.
watch(companionIntroRequested, (requested) => {
  if (!requested) return
  companionIntroRequested.value = false
  if (calmTicker) {
    clearInterval(calmTicker)
    calmTicker = null
  }
  step.value = 'download'
  visible.value = true
})

watch(visible, async (isVisible) => {
  if (!isVisible) return
  // Generate large and let CSS scale down — at 112px source a ~45-module QR
  // is 2.5px/module, which camera decoders (the companion app included)
  // routinely fail on. 512px keeps every module crisp.
  qrDataUrl.value = await QRCode.toDataURL(companionDownloadUrl, {
    width: 512,
    margin: 2,
    errorCorrectionLevel: 'M',
    color: {
      dark: '#111111',
      light: '#ffffff',
    },
  })
  if (REMOTE_ACCESS_STEPS && !wgApkQrDataUrl.value) {
    wgApkQrDataUrl.value = await QRCode.toDataURL(wireguardDownloadUrl, {
      width: 512,
      margin: 2,
      errorCorrectionLevel: 'M',
      color: {
        dark: '#111111',
        light: '#ffffff',
      },
    })
  }
}, { immediate: true, flush: 'post' })

/**
 * The server URL the companion app should connect to. The demo advertises its
 * public https origin; a real node advertises whatever address the browser is
 * already using — except on the kiosk, where that is localhost and useless to
 * a phone, so fall back to the node's mDNS .local name.
 */
async function resolveServerUrl(): Promise<string> {
  if (IS_DEMO) return DEMO_SERVER_URL
  // Remote-access flow: the app must connect through the tunnel the user just
  // switched on — the LAN origin is unreachable from outside AllowedIPs.
  if (REMOTE_ACCESS_STEPS) return VPN_NODE_URL
  const { hostname, origin } = window.location
  if (hostname !== 'localhost' && hostname !== '127.0.0.1') return origin
  try {
    const res = await rpcClient.call<{ mdns_hostname?: string }>({ method: 'system.get-hostname' })
    if (res?.mdns_hostname) return `http://${res.mdns_hostname}`
  } catch {
    // RPC unavailable — fall through to the (localhost) origin
  }
  return origin
}

async function buildPairingUrl(): Promise<string> {
  const params = new URLSearchParams({ v: '1', url: await resolveServerUrl() })
  // Only the shared demo password ever rides in the QR; real node passwords
  // are never available to the frontend and stay typed-by-hand in the app.
  if (IS_DEMO) params.set('pw', DEMO_PASSWORD)
  return `${PAIR_SCHEME}?${params.toString()}`
}

async function showPairScreen() {
  slideName.value = 'slide-forward'
  step.value = 'pair'
  if (!pairQrDataUrl.value) {
    pairingUrl.value = await buildPairingUrl()
    // Large source + a real quiet zone; this QR is scanned by the companion
    // app's camera, so give it every advantage (see download QR note above).
    pairQrDataUrl.value = await QRCode.toDataURL(pairingUrl.value, {
      width: 512,
      margin: 3,
      errorCorrectionLevel: 'M',
      color: {
        dark: '#111111',
        light: '#ffffff',
      },
    })
  }
}

function showDownloadScreen() {
  slideName.value = 'slide-back'
  step.value = 'download'
}

function advanceFromDownload() {
  if (REMOTE_ACCESS_STEPS) showWireguardScreen('forward')
  else void showPairScreen()
}

function showWireguardScreen(direction: 'forward' | 'back' = 'forward') {
  slideName.value = direction === 'forward' ? 'slide-forward' : 'slide-back'
  step.value = 'wireguard'
}

function showWgQrScreen() {
  slideName.value = 'slide-forward'
  step.value = 'wgqr'
  void loadWgPeer()
}

function backFromPair() {
  if (REMOTE_ACCESS_STEPS) {
    slideName.value = 'slide-back'
    step.value = 'wgqr'
  } else {
    showDownloadScreen()
  }
}

// Create (or fetch) the phone's VPN peer and render its config as a QR.
// Reuses the same RPCs as the Server page's Add Device modal; the peer is
// looked up first so reopening the modal never duplicates it.
async function loadWgPeer() {
  if (wgQrDataUrl.value || wgLoading.value) return
  wgLoading.value = true
  wgError.value = ''
  try {
    const listed = await rpcClient
      .call<{ peers: { name: string }[] }>({ method: 'vpn.list-peers' })
      .catch(() => ({ peers: [] as { name: string }[] }))
    const exists = (listed.peers || []).some((p) => p.name === WG_PEER_NAME)
    const res = await rpcClient.call<{ config: string; peer_ip: string }>({
      method: exists ? 'vpn.peer-config' : 'vpn.create-peer',
      params: { name: WG_PEER_NAME },
    })
    wgConfig.value = res.config
    wgQrDataUrl.value = await QRCode.toDataURL(res.config, {
      width: 512,
      margin: 3,
      errorCorrectionLevel: 'M',
      color: {
        dark: '#111111',
        light: '#ffffff',
      },
    })
  } catch (e) {
    wgError.value = e instanceof Error ? e.message : 'Failed to generate the tunnel config'
  } finally {
    wgLoading.value = false
  }
}

// Same-device path: hand the config to the WireGuard app as an importable
// .conf file (a phone can't scan its own screen).
function downloadWgConfig() {
  if (!wgConfig.value) return
  const blob = new Blob([wgConfig.value], { type: 'application/octet-stream' })
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = 'archipelago.conf'
  a.click()
  URL.revokeObjectURL(url)
}

function dismiss() {
  visible.value = false
  step.value = 'download'
  try {
    localStorage.setItem(STORAGE_KEY, '1')
  } catch {
    // ignore
  }
}
</script>

<style scoped>
.overlay-fade-enter-active { transition: opacity 0.3s ease; }
.overlay-fade-leave-active { transition: opacity 0.2s ease; }
.overlay-fade-enter-from,
.overlay-fade-leave-to { opacity: 0; }
.overlay-fade-enter-active .glass-card { transition: transform 0.3s ease; }
.overlay-fade-enter-from .glass-card { transform: translateY(20px); }

/* Horizontal slide between the download and pairing screens */
.slide-forward-enter-active,
.slide-forward-leave-active,
.slide-back-enter-active,
.slide-back-leave-active { transition: transform 0.25s ease, opacity 0.25s ease; }
.slide-forward-enter-from { transform: translateX(40px); opacity: 0; }
.slide-forward-leave-to { transform: translateX(-40px); opacity: 0; }
.slide-back-enter-from { transform: translateX(-40px); opacity: 0; }
.slide-back-leave-to { transform: translateX(40px); opacity: 0; }
</style>
