<script setup lang="ts">
// Pricing catalog editor. Operator-only. Each (plan, cycle) is a single row
// with three independent per-currency amounts (DKK / EUR / USD). Operator
// types clean round numbers in each currency — no FX derivation. Empty cells
// mean "we don't sell this plan/cycle in that currency."
//
// Backed by /prices in platform-api. Amounts are stored in MINOR units
// (4900 = 49.00); display uses major-unit strings with 2 decimals.

const CURRENCIES = ['DKK', 'EUR', 'USD'] as const
type Currency = (typeof CURRENCIES)[number]

interface PriceRow {
  _id: string
  plan: 'mvp' | 'pro' | 'enterprise'
  cycle: 'monthly' | 'quarterly' | 'yearly'
  amounts: Partial<Record<Currency, number>>
  active: boolean
  createdAt?: string
  updatedAt?: string
}

const toast = useToast()
const showInactive = ref(false)

const { data: prices, refresh } = await useFetch<PriceRow[]>(
  () => `/api/prices${showInactive.value ? '?includeInactive=true' : ''}`,
  { key: 'pricing-catalog', default: () => [], watch: [showInactive] },
)

const PLAN_LABEL: Record<PriceRow['plan'], string> = {
  mvp: 'Starter',
  pro: 'Business',
  enterprise: 'Enterprise',
}
const CYCLE_LABEL: Record<PriceRow['cycle'], string> = {
  monthly: 'Monthly',
  quarterly: 'Quarterly',
  yearly: 'Yearly',
}

function toMajor(minor?: number): string {
  if (typeof minor !== 'number') return ''
  return (minor / 100).toFixed(2)
}
function toMinor(major: string): number | undefined {
  if (!major.trim()) return undefined  // empty → unset that currency
  const cleaned = major.replace(/\s+/g, '').replace(',', '.')
  const n = Number(cleaned)
  if (!Number.isFinite(n) || n < 0) return NaN
  return Math.round(n * 100)
}

// Per-row draft. Keyed by row._id, holds one major-unit string per currency.
// undefined draft = not currently editing.
type Draft = Record<Currency, string>
const drafts = reactive<Record<string, Draft>>({})
const saving = ref<string | null>(null)

function startEdit(row: PriceRow) {
  drafts[row._id] = {
    DKK: toMajor(row.amounts.DKK),
    EUR: toMajor(row.amounts.EUR),
    USD: toMajor(row.amounts.USD),
  }
}
function cancelEdit(id: string) {
  delete drafts[id]
}

// A pending price change held back for operator confirmation, because it
// re-prices live customers. Resolved by confirmReprice() or dismissed.
interface PendingChange {
  row: PriceRow
  next: Partial<Record<Currency, number>>
  changes: { currency: Currency; from?: number; to: number; affected: number }[]
  totalAffected: number
}
const pending = ref<PendingChange | null>(null)
const confirmBusy = ref(false)

async function saveEdit(row: PriceRow) {
  const draft = drafts[row._id]
  if (!draft) return
  const next: Partial<Record<Currency, number>> = {}
  for (const c of CURRENCIES) {
    const parsed = toMinor(draft[c])
    if (parsed === undefined) continue  // empty input → currency stays unset
    if (Number.isNaN(parsed)) {
      toast.warn(`Invalid ${c} amount`, 'Enter a number ≥ 0, or blank to leave unset')
      return
    }
    next[c] = parsed
  }

  // Which currencies actually changed value? Only those re-price customers.
  const changedCurrencies = CURRENCIES.filter((c) => next[c] !== undefined && next[c] !== row.amounts[c])
  if (changedCurrencies.length === 0) {
    cancelEdit(row._id)  // nothing changed — just close the editor
    return
  }

  // Ask the backend how many live customers each changed currency touches.
  saving.value = row._id
  let impact: Record<Currency, number> = { DKK: 0, EUR: 0, USD: 0 }
  try {
    impact = await $fetch<Record<Currency, number>>(`/api/prices/${row._id}/impact`)
  } catch {
    // Impact lookup failed — fall through with zeros; the save still works,
    // we just can't show the affected-customer count.
  } finally {
    saving.value = null
  }

  const changes = changedCurrencies.map((c) => ({
    currency: c,
    from: row.amounts[c],
    to: next[c]!,
    affected: impact[c] ?? 0,
  }))
  const totalAffected = changes.reduce((sum, ch) => sum + ch.affected, 0)

  // No live customers on the changed currencies → commit straight away.
  if (totalAffected === 0) {
    await commitSave(row, next)
    return
  }
  pending.value = { row, next, changes, totalAffected }
}

// The actual PATCH. Backend mints new Stripe Prices for changed currencies and
// moves live subs onto them (effective next cycle).
async function commitSave(row: PriceRow, next: Partial<Record<Currency, number>>) {
  saving.value = row._id
  try {
    await $fetch(`/api/prices/${row._id}`, { method: 'PATCH', body: { amounts: next } })
    toast.ok('Prices updated', `${PLAN_LABEL[row.plan]} · ${CYCLE_LABEL[row.cycle]}`)
    cancelEdit(row._id)
    await refresh()
  } catch (err) {
    const e = err as { data?: { data?: { message?: string }; message?: string } }
    toast.warn('Update failed', e.data?.data?.message ?? e.data?.message ?? 'Try again')
  } finally {
    saving.value = null
  }
}

async function confirmReprice() {
  if (!pending.value) return
  const { row, next } = pending.value
  confirmBusy.value = true
  try {
    await commitSave(row, next)
  } finally {
    confirmBusy.value = false
    pending.value = null
  }
}

async function toggleActive(row: PriceRow) {
  saving.value = row._id
  try {
    await $fetch(`/api/prices/${row._id}`, { method: 'PATCH', body: { active: !row.active } })
    toast.ok(
      row.active ? 'Price deactivated' : 'Price reactivated',
      `${PLAN_LABEL[row.plan]} · ${CYCLE_LABEL[row.cycle]}`,
    )
    await refresh()
  } catch (err) {
    const e = err as { data?: { data?: { message?: string }; message?: string } }
    toast.warn('Toggle failed', e.data?.data?.message ?? e.data?.message ?? 'Try again')
  } finally {
    saving.value = null
  }
}

// Add-row form. Used to insert an Enterprise row or replace a deactivated one.
const addForm = reactive<{
  plan: PriceRow['plan']
  cycle: PriceRow['cycle']
  amounts: Record<Currency, string>
}>({
  plan: 'enterprise',
  cycle: 'monthly',
  amounts: { DKK: '', EUR: '', USD: '' },
})
const adding = ref(false)

async function addRow() {
  const amounts: Partial<Record<Currency, number>> = {}
  for (const c of CURRENCIES) {
    const parsed = toMinor(addForm.amounts[c])
    if (parsed === undefined) continue
    if (Number.isNaN(parsed)) {
      toast.warn(`Invalid ${c} amount`, 'Enter a number ≥ 0')
      return
    }
    amounts[c] = parsed
  }
  if (Object.keys(amounts).length === 0) {
    toast.warn('No prices entered', 'Set at least one currency amount')
    return
  }
  adding.value = true
  try {
    await $fetch('/api/prices', {
      method: 'POST',
      body: { plan: addForm.plan, cycle: addForm.cycle, amounts },
    })
    toast.ok('Row added', `${PLAN_LABEL[addForm.plan]} · ${CYCLE_LABEL[addForm.cycle]}`)
    for (const c of CURRENCIES) addForm.amounts[c] = ''
    await refresh()
  } catch (err) {
    const e = err as { data?: { data?: { message?: string }; message?: string } }
    toast.warn('Add failed', e.data?.data?.message ?? e.data?.message ?? 'Try again')
  } finally {
    adding.value = false
  }
}

// Sort: active first, then plan order (mvp/pro/enterprise), then cycle order.
const PLAN_ORDER: Record<PriceRow['plan'], number> = { mvp: 0, pro: 1, enterprise: 2 }
const CYCLE_ORDER: Record<PriceRow['cycle'], number> = { monthly: 0, quarterly: 1, yearly: 2 }
const sortedPrices = computed<PriceRow[]>(() =>
  [...(prices.value ?? [])].sort((a, b) => {
    if (a.active !== b.active) return a.active ? -1 : 1
    if (a.plan !== b.plan) return PLAN_ORDER[a.plan] - PLAN_ORDER[b.plan]
    return CYCLE_ORDER[a.cycle] - CYCLE_ORDER[b.cycle]
  }),
)
</script>

<template>
  <div>
    <PageHeader
      eyebrow="Operator · operator.dezky.local"
      title="Pricing catalog"
      subtitle="One row per plan + cycle, with independent prices per currency. Editing an amount re-prices live customers on that currency at their next billing cycle (no mid-cycle charge) and applies to all new subscriptions."
    >
      <template #actions>
        <label class="toggle">
          <input v-model="showInactive" type="checkbox" />
          Show inactive
        </label>
      </template>
    </PageHeader>

    <div class="stage">
      <Card :pad="0">
      <table>
        <thead>
          <tr>
            <th>Plan</th>
            <th>Cycle</th>
            <th v-for="c in CURRENCIES" :key="c" class="th-amount">{{ c }} / seat</th>
            <th>Status</th>
            <th class="th-actions">Actions</th>
          </tr>
        </thead>
        <tbody>
          <tr v-if="sortedPrices.length === 0" class="empty">
            <td :colspan="4 + CURRENCIES.length">
              <span class="empty-inner">No prices yet — add one below.</span>
            </td>
          </tr>
          <tr v-for="row in sortedPrices" :key="row._id" :class="{ inactive: !row.active }">
            <td>
              <div class="cell-name">{{ PLAN_LABEL[row.plan] }}</div>
              <Mono dim>{{ row.plan }}</Mono>
            </td>
            <td>{{ CYCLE_LABEL[row.cycle] }}</td>
            <td v-for="c in CURRENCIES" :key="c" class="cell-amount">
              <template v-if="drafts[row._id]">
                <input
                  v-model="drafts[row._id]![c]"
                  type="text"
                  inputmode="decimal"
                  class="amount-input"
                  :placeholder="`— ${c}`"
                  :disabled="saving === row._id"
                  @keydown.enter="saveEdit(row)"
                  @keydown.escape="cancelEdit(row._id)"
                />
              </template>
              <template v-else>
                <template v-if="row.amounts[c] !== undefined">
                  <span class="amount">{{ toMajor(row.amounts[c]) }}</span>
                </template>
                <Mono v-else dim>—</Mono>
              </template>
            </td>
            <td>
              <Badge :tone="row.active ? 'ok' : 'neutral'" dot>{{ row.active ? 'active' : 'inactive' }}</Badge>
            </td>
            <td class="cell-actions">
              <div class="actions">
                <template v-if="drafts[row._id]">
                  <UiButton size="sm" variant="primary" :disabled="saving === row._id" @click="saveEdit(row)">
                    {{ saving === row._id ? 'Saving…' : 'Save' }}
                  </UiButton>
                  <UiButton size="sm" variant="ghost" :disabled="saving === row._id" @click="cancelEdit(row._id)">
                    Cancel
                  </UiButton>
                </template>
                <template v-else>
                  <UiButton size="sm" variant="secondary" @click="startEdit(row)">Edit</UiButton>
                  <UiButton size="sm" variant="ghost" :disabled="saving === row._id" @click="toggleActive(row)">
                    {{ row.active ? 'Deactivate' : 'Reactivate' }}
                  </UiButton>
                </template>
              </div>
            </td>
          </tr>
        </tbody>
      </table>
    </Card>

    <Card>
      <h2>Add a row</h2>
      <p class="hint">
        Used for missing Enterprise rows or to replace a deactivated row.
        Fill in only the currencies you want to offer; blank cells mean
        "not sold in this currency."
      </p>
      <div class="add-form">
        <label class="field">
          <Eyebrow>Plan</Eyebrow>
          <select v-model="addForm.plan">
            <option value="mvp">Starter</option>
            <option value="pro">Business</option>
            <option value="enterprise">Enterprise</option>
          </select>
        </label>
        <label class="field">
          <Eyebrow>Cycle</Eyebrow>
          <select v-model="addForm.cycle">
            <option value="monthly">Monthly</option>
            <option value="quarterly">Quarterly</option>
            <option value="yearly">Yearly</option>
          </select>
        </label>
        <label v-for="c in CURRENCIES" :key="c" class="field">
          <Eyebrow>{{ c }} / seat</Eyebrow>
          <input
            v-model="addForm.amounts[c]"
            type="text"
            inputmode="decimal"
            :placeholder="`e.g. ${c === 'DKK' ? '49.00' : '7.00'}`"
          />
        </label>
        <UiButton variant="primary" :disabled="adding" @click="addRow">
          {{ adding ? 'Adding…' : 'Add row' }}
        </UiButton>
      </div>
    </Card>
    </div>

    <ConfirmDialog
      :open="pending !== null"
      eyebrow="Pricing catalog"
      :title="pending ? `Change ${PLAN_LABEL[pending.row.plan]} · ${CYCLE_LABEL[pending.row.cycle]} pricing?` : ''"
      confirm-label="Apply change"
      :busy="confirmBusy"
      @close="pending = null"
      @confirm="confirmReprice"
    >
      <template v-if="pending">
        <p>
          <strong>{{ pending.totalAffected }}</strong>
          active {{ pending.totalAffected === 1 ? 'customer' : 'customers' }} will move to the
          new price at their <strong>next billing cycle</strong>. The current period and past
          invoices are unaffected — no mid-cycle charge or credit.
        </p>
        <ul class="reprice-list">
          <li v-for="ch in pending.changes" :key="ch.currency">
            <Mono>{{ ch.currency }}</Mono>
            <span class="reprice-amounts">
              {{ ch.from !== undefined ? toMajor(ch.from) : '—' }} →
              <strong>{{ toMajor(ch.to) }}</strong>
            </span>
            <span class="reprice-count">
              {{ ch.affected }} {{ ch.affected === 1 ? 'customer' : 'customers' }}
            </span>
          </li>
        </ul>
      </template>
    </ConfirmDialog>
  </div>
</template>

<style scoped>
.stage { padding: 24px 40px 64px; display: flex; flex-direction: column; gap: 18px; }

.toggle {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-mute);
}

table { width: 100%; border-collapse: collapse; table-layout: fixed; }
th, td { padding: 12px 16px; text-align: left; border-bottom: 1px solid var(--border); font-size: 13px; vertical-align: middle; }
th { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-mute); font-weight: 500; }
.cell-name { font-size: 13px; font-weight: 500; }
.empty .empty-inner { display: block; padding: 24px 0; text-align: center; color: var(--text-mute); }
tr.inactive { opacity: 0.55; }

.th-amount, .cell-amount { text-align: right; width: 130px; white-space: nowrap; }
.amount { font-family: var(--font-mono); font-size: 13px; font-variant-numeric: tabular-nums; }
.amount-input {
  width: 96px;
  padding: 6px 8px;
  text-align: right;
  background: var(--surface);
  border: 1px solid var(--border-hi);
  border-radius: 4px;
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--text);
}

.th-actions, .cell-actions { text-align: right; width: 220px; }
.actions { display: inline-flex; gap: 6px; align-items: center; justify-content: flex-end; }

.add-form { display: grid; grid-template-columns: repeat(5, 1fr) auto; gap: 12px; align-items: end; }
.field { display: flex; flex-direction: column; gap: 6px; }
.field select, .field input {
  height: 34px;
  padding: 0 12px;
  background: var(--input-bg);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text);
  font-family: inherit;
  font-size: 13px;
  outline: 0;
}
.hint { font-size: 13px; color: var(--text-dim); margin: 0 0 12px; }

.reprice-list { list-style: none; margin: 14px 0 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.reprice-list li {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: 13px;
}
.reprice-amounts { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.reprice-count { margin-left: auto; color: var(--text-mute); font-size: 12px; }
</style>
