<script setup lang="ts">
// Card-on-file update via Stripe Elements + a SetupIntent. The card field is a
// Stripe-hosted iframe (loaded from js.stripe.com) — raw card data never hits
// our origin. Flow: open → POST setup-intent (secret + publishable key) → mount
// Elements → confirmCardSetup client-side → POST the resulting PM as default.
//
// Stripe handles are untyped (`any`) on purpose — we don't bundle @stripe/stripe-js.

import { loadStripeJs } from '~/composables/useStripeJs'
import type { PaymentMethodCard } from '~/types/workspace'

const props = defineProps<{ open: boolean; slug: string }>()
const emit = defineEmits<{ close: []; saved: [card: PaymentMethodCard | null] }>()

const { request } = useApiFetch()

const cardMount = ref<HTMLElement | null>(null)
const status = ref<'idle' | 'loading' | 'ready' | 'submitting'>('idle')
const errorMsg = ref('')

/* eslint-disable @typescript-eslint/no-explicit-any */
let stripe: any = null
let cardEl: any = null
let clientSecret = ''

async function setup() {
  status.value = 'loading'
  errorMsg.value = ''
  try {
    const res = await request<{ clientSecret: string; publishableKey: string }>(
      `/api/tenants/${props.slug}/payment-method/setup-intent`,
      { method: 'POST' },
    )
    if (!res.publishableKey) throw new Error('Billing is not configured')
    clientSecret = res.clientSecret

    const StripeCtor = (await loadStripeJs()) as any
    if (!StripeCtor) throw new Error('Could not load Stripe')
    stripe = StripeCtor(res.publishableKey)

    // Pull theme colours so the iframe text matches light/dark.
    const cs = getComputedStyle(document.documentElement)
    const color = cs.getPropertyValue('--text').trim() || '#0A0A0A'
    const placeholder = cs.getPropertyValue('--text-mute').trim() || '#9b9b9b'

    const elements = stripe.elements()
    // hidePostalCode: the Card Element's bundled postal field validates against a
    // US 5-digit ZIP by default, so a valid 4-digit Danish postcode reads as
    // "incomplete". We don't need postal for a SetupIntent, so drop the field.
    cardEl = elements.create('card', {
      hidePostalCode: true,
      style: { base: { fontSize: '14px', color, fontFamily: 'inherit', '::placeholder': { color: placeholder } } },
    })
    await nextTick()
    if (cardMount.value) cardEl.mount(cardMount.value)
    cardEl.on('change', (e: any) => { errorMsg.value = e.error?.message ?? '' })
    status.value = 'ready'
  } catch (e: any) {
    errorMsg.value = e?.data?.message || e?.message || 'Could not start card update'
    status.value = 'idle'
  }
}

function teardown() {
  try { cardEl?.destroy() } catch { /* already gone */ }
  cardEl = null
  stripe = null
  clientSecret = ''
  status.value = 'idle'
  errorMsg.value = ''
}

watch(() => props.open, (open) => (open ? setup() : teardown()))

async function submit() {
  if (status.value !== 'ready' || !stripe || !clientSecret) return
  status.value = 'submitting'
  errorMsg.value = ''
  const { error, setupIntent } = await stripe.confirmCardSetup(clientSecret, { payment_method: { card: cardEl } })
  if (error) {
    errorMsg.value = error.message ?? 'Card could not be saved'
    status.value = 'ready'
    return
  }
  try {
    const card = await request<PaymentMethodCard | null>(
      `/api/tenants/${props.slug}/payment-method/default`,
      { method: 'POST', body: { paymentMethodId: setupIntent.payment_method } },
    )
    emit('saved', card)
    emit('close')
  } catch (e: any) {
    errorMsg.value = e?.data?.message || 'Card saved, but setting it as default failed'
    status.value = 'ready'
  }
}
/* eslint-enable @typescript-eslint/no-explicit-any */

onBeforeUnmount(teardown)
</script>

<template>
  <Modal :open="open" eyebrow="Billing · payment method" title="Update card" size="md" @close="emit('close')">
    <div class="pm">
      <label class="field">
        <Eyebrow>Card details</Eyebrow>
        <div ref="cardMount" class="card-input" />
      </label>
      <div v-if="status === 'loading'"><Mono dim>Loading secure card form…</Mono></div>
      <div v-if="errorMsg" class="err">{{ errorMsg }}</div>
      <div class="trust">
        <UiIcon name="shield" :size="14" stroke="var(--ok)" />
        <div>Card details go straight to Stripe — Dezky never sees your full card number or CVC. PCI DSS Level 1.</div>
      </div>
    </div>
    <template #footer>
      <UiButton variant="ghost" @click="emit('close')">Cancel</UiButton>
      <UiButton variant="primary" :disabled="status !== 'ready'" @click="submit">
        <template #leading><UiIcon name="check" :size="13" /></template>
        {{ status === 'submitting' ? 'Saving…' : 'Save card' }}
      </UiButton>
    </template>
  </Modal>
</template>

<style scoped>
.pm { display: flex; flex-direction: column; gap: 14px; }
.field { display: flex; flex-direction: column; gap: 6px; }
.card-input {
  min-height: 20px;
  padding: 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 6px;
}
.err { font-size: 12px; color: var(--bad); }
.trust {
  padding: 12px;
  background: var(--bg);
  border-radius: 6px;
  border: 1px solid var(--border);
  display: flex;
  gap: 10px;
  align-items: flex-start;
  font-size: 12px;
  color: var(--text-dim);
  line-height: 1.55;
}
</style>
