<script setup lang="ts">
// Operator-only modal that creates a new platform admin in Authentik and
// surfaces the recovery link for the operator to share manually. Once
// outbound SMTP is wired, Authentik can email the link directly and we'll
// drop the "share manually" affordance from this modal.

interface InviteResult {
  subject: string
  userId: string
  // Exactly one is set — recovery link when Authentik has a recovery flow,
  // temp password as a fallback when it doesn't.
  link?: string
  tempPassword?: string
}

const props = defineProps<{ open: boolean }>()
const emit = defineEmits<{ close: []; invited: [InviteResult] }>()

const name = ref('')
const email = ref('')
const busy = ref(false)
const error = ref<string | null>(null)
const result = ref<InviteResult | null>(null)
const copied = ref(false)

// Reset state every time the modal opens so previous results don't linger.
watch(
  () => props.open,
  (v) => {
    if (v) {
      name.value = ''
      email.value = ''
      busy.value = false
      error.value = null
      result.value = null
      copied.value = false
    }
  },
)

onMounted(() => {
  const onKey = (e: KeyboardEvent) => {
    if (e.key === 'Escape' && props.open && !busy.value) emit('close')
  }
  document.addEventListener('keydown', onKey)
  onBeforeUnmount(() => document.removeEventListener('keydown', onKey))
})

const canSubmit = computed(() => {
  return (
    !busy.value &&
    name.value.trim().length >= 2 &&
    /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value.trim())
  )
})

async function submit() {
  if (!canSubmit.value) return
  busy.value = true
  error.value = null
  try {
    result.value = await $fetch<InviteResult>('/api/users/invite', {
      method: 'POST',
      body: { name: name.value.trim(), email: email.value.trim() },
    })
    emit('invited', result.value)
  } catch (e) {
    const err = e as { data?: { data?: { message?: string }; message?: string; statusMessage?: string } }
    error.value =
      err.data?.data?.message ||
      err.data?.message ||
      err.data?.statusMessage ||
      'Invite failed'
  } finally {
    busy.value = false
  }
}

async function copyToClipboard(value: string) {
  try {
    await navigator.clipboard.writeText(value)
    copied.value = true
    setTimeout(() => (copied.value = false), 2000)
  } catch {
    // Some browsers reject clipboard in non-secure contexts. Fall back to
    // user manually selecting the text in the readonly input below.
  }
}

function done() {
  emit('close')
}
</script>

<template>
  <Teleport to="body">
    <div v-if="open" class="backdrop" @click="!busy && emit('close')">
      <div class="modal" role="dialog" aria-label="Invite operator" @click.stop>
        <header>
          <div>
            <Eyebrow>Platform</Eyebrow>
            <h2>Invite operator</h2>
          </div>
          <button class="x" type="button" aria-label="Close" :disabled="busy" @click="emit('close')">
            <UiIcon name="x" :size="12" />
          </button>
        </header>

        <!-- Step 1: collect details -->
        <div v-if="!result" class="body">
          <section>
            <label class="label">Name</label>
            <input v-model="name" type="text" placeholder="Anne Baslund" :disabled="busy" />
          </section>
          <section>
            <label class="label">Email</label>
            <input v-model="email" type="email" placeholder="anne@dezky.com" :disabled="busy" />
          </section>

          <div class="note">
            <UiIcon name="shield" :size="13" />
            <Mono dim>
              Adds the user to the <strong>dezky-platform-admins</strong> Authentik group.
              They'll receive a single-use recovery link to set their password + enroll MFA.
            </Mono>
          </div>

          <p v-if="error" class="err">{{ error }}</p>
        </div>

        <!-- Step 2: show the credential to share -->
        <div v-else class="body result">
          <Badge tone="ok" dot>invited</Badge>

          <!-- Preferred path: Authentik issued a recovery link -->
          <template v-if="result.link">
            <p class="success">
              {{ name }} (<Mono>{{ email }}</Mono>) was added to the
              <Mono>dezky-platform-admins</Mono> group. Share the link below — it's
              single-use and they'll set their own password + MFA.
            </p>
            <div class="cred-row">
              <input :value="result.link" readonly @focus="($event.target as HTMLInputElement).select()" />
              <UiButton variant="secondary" @click="copyToClipboard(result.link!)">
                {{ copied ? 'Copied' : 'Copy' }}
              </UiButton>
            </div>
          </template>

          <!-- Fallback path: no recovery flow configured in Authentik, share temp password -->
          <template v-else-if="result.tempPassword">
            <p class="success">
              {{ name }} (<Mono>{{ email }}</Mono>) was added to the
              <Mono>dezky-platform-admins</Mono> group. Authentik doesn't have a
              recovery flow configured yet, so we set a temporary password —
              share it with them out-of-band, they'll be prompted to change it
              on first login.
            </p>
            <section>
              <label class="label">Username / email</label>
              <div class="cred-row">
                <input :value="email" readonly @focus="($event.target as HTMLInputElement).select()" />
                <UiButton variant="secondary" @click="copyToClipboard(email)">
                  Copy
                </UiButton>
              </div>
            </section>
            <section>
              <label class="label">Temporary password</label>
              <div class="cred-row">
                <input :value="result.tempPassword" readonly @focus="($event.target as HTMLInputElement).select()" />
                <UiButton variant="secondary" @click="copyToClipboard(result.tempPassword!)">
                  {{ copied ? 'Copied' : 'Copy' }}
                </UiButton>
              </div>
            </section>
            <Mono dim>
              // configure a recovery flow in Authentik (Flows → recovery) to
              switch this to a self-service link · once SMTP is wired the
              credential gets emailed automatically
            </Mono>
          </template>
        </div>

        <footer v-if="!result">
          <UiButton variant="ghost" :disabled="busy" @click="emit('close')">Cancel</UiButton>
          <UiButton variant="primary" :disabled="!canSubmit" @click="submit">
            {{ busy ? 'Inviting…' : 'Send invite' }}
          </UiButton>
        </footer>
        <footer v-else>
          <UiButton variant="primary" @click="done">Done</UiButton>
        </footer>
      </div>
    </div>
  </Teleport>
</template>

<style scoped>
.backdrop {
  position: fixed; inset: 0;
  background: rgba(0, 0, 0, 0.55);
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  z-index: 180;
}
.modal {
  width: 100%; max-width: 520px;
  background: var(--elevated);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.5);
  display: flex; flex-direction: column;
  overflow: hidden;
}
header {
  padding: 16px 20px;
  display: flex; justify-content: space-between; align-items: flex-start;
  border-bottom: 1px solid var(--border);
}
h2 { margin: 4px 0 0 0; font-family: var(--font-display); font-weight: 600; font-size: 18px; }
.x {
  width: 26px; height: 26px;
  border: 0; border-radius: 6px; background: transparent;
  color: var(--text-mute); cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.x:hover:not(:disabled) { background: var(--surface); color: var(--text); }
.x:disabled { opacity: 0.4; cursor: not-allowed; }

.body {
  padding: 16px 20px;
  display: flex; flex-direction: column; gap: 14px;
}
section { display: flex; flex-direction: column; gap: 6px; }
.label {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-mute);
  font-weight: 500;
}
input {
  height: 34px;
  padding: 0 12px;
  background: var(--input-bg);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 6px;
  font-family: inherit;
  font-size: 13px;
  outline: 0;
}
input:focus { border-color: var(--border-hi); }
input:disabled { opacity: 0.6; }

.note {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text-mute);
}
.note strong { color: var(--text); font-family: var(--font-mono); font-weight: 600; }

.err {
  margin: 0;
  padding: 10px 12px;
  background: rgba(240, 88, 88, 0.08);
  border: 1px solid rgba(240, 88, 88, 0.24);
  color: var(--bad);
  border-radius: 6px;
  font-size: 12px;
}

.result .success {
  margin: 4px 0 0 0;
  font-size: 13px;
  color: var(--text-dim);
  line-height: 1.55;
}
.cred-row { display: flex; gap: 8px; align-items: center; }
.cred-row input {
  flex: 1;
  font-family: var(--font-mono);
  font-size: 11px;
}

footer {
  padding: 12px 20px;
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  border-top: 1px solid var(--border);
  background: var(--surface);
}
</style>
