<script setup lang="ts">
import {
  Check,
  CircleAlert,
  ClipboardCopy,
  ExternalLink,
  Loader2,
  Play,
  RefreshCw,
  RotateCcw,
  ShieldCheck,
  XCircle,
} from '@lucide/vue'
import { computed, nextTick, ref, watch } from 'vue'
import { useOpenClawWizardStore } from '../../stores/openclawWizard'
import type {
  OpenClawWizardMode,
  OpenClawWizardStep,
} from '../../types/openclaw-wizard'

const props = withDefaults(defineProps<{
  available?: boolean
  unavailableReason?: string | null
}>(), {
  available: false,
  unavailableReason: null,
})

const wizard = useOpenClawWizardStore()
const mode = ref<OpenClawWizardMode>('local')
const startConfirmed = ref(false)
const textAnswer = ref('')
const selectedOptionIndex = ref<number | null>(null)
const selectedOptionIndices = ref<number[]>([])
const confirmAnswer = ref<boolean | null>(null)
const localError = ref('')
const copiedDeviceCode = ref(false)
const stepHeading = ref<HTMLElement | null>(null)
const errorAlert = ref<HTMLElement | null>(null)

const step = computed(() => wizard.step)
const result = computed(() => wizard.result)
const active = computed(() => wizard.active)
const combinedError = computed(() => localError.value || wizard.error)
const canStart = computed(() =>
  props.available
  && startConfirmed.value
  && !wizard.busy,
)
const textAnswerBytes = computed(() => new TextEncoder().encode(textAnswer.value).byteLength)
const canSubmit = computed(() => {
  const current = step.value
  if (!current || current.sensitive || !current.canAnswer || wizard.busy) return false

  switch (current.type) {
    case 'select':
      return selectedOptionIndex.value !== null
    case 'text':
      return textAnswerBytes.value <= 64 * 1024
    case 'confirm':
      return confirmAnswer.value !== null
    case 'multiselect':
    case 'note':
    case 'progress':
    case 'action':
      return true
    default:
      return false
  }
})
const continueLabel = computed(() => {
  switch (step.value?.type) {
    case 'progress':
      return 'Fortschritt abrufen'
    case 'action':
      return 'Aktion fortsetzen'
    default:
      return 'Weiter'
  }
})
const safeExternalUrl = computed(() => {
  const raw = step.value?.externalUrl?.trim()
  if (!raw || raw.length > 2048) return null
  try {
    const url = new URL(raw)
    if (!['https:', 'http:'].includes(url.protocol) || url.username || url.password) return null
    return {
      href: url.toString(),
      label: `${url.origin}${url.pathname === '/' ? '' : url.pathname}`,
    }
  } catch {
    return null
  }
})

function valuesEqual(left: unknown, right: unknown): boolean {
  if (Object.is(left, right)) return true
  try {
    return JSON.stringify(left) === JSON.stringify(right)
  } catch {
    return false
  }
}

function resetAnswer(current: OpenClawWizardStep | null) {
  textAnswer.value = ''
  selectedOptionIndex.value = null
  selectedOptionIndices.value = []
  confirmAnswer.value = null
  localError.value = ''
  copiedDeviceCode.value = false
  if (!current || current.sensitive) return
  const initialValue = current.initialValue

  if (current.type === 'text' && ['string', 'number'].includes(typeof initialValue)) {
    textAnswer.value = String(initialValue)
  } else if (current.type === 'confirm' && typeof initialValue === 'boolean') {
    confirmAnswer.value = initialValue
  } else if (current.type === 'select') {
    const index = current.options.findIndex(option =>
      valuesEqual(option.value, initialValue),
    )
    selectedOptionIndex.value = index >= 0 ? index : null
  } else if (current.type === 'multiselect' && Array.isArray(initialValue)) {
    selectedOptionIndices.value = current.options
      .map((option, index) =>
        initialValue.some((value: unknown) => valuesEqual(value, option.value))
          ? index
          : -1,
      )
      .filter(index => index >= 0)
  }
}

watch(
  () => step.value?.id ?? null,
  async () => {
    resetAnswer(step.value)
    await nextTick()
    stepHeading.value?.focus()
  },
  { immediate: true },
)

watch(combinedError, async value => {
  if (!value) return
  await nextTick()
  errorAlert.value?.focus()
})

async function startWizard() {
  if (!canStart.value) return
  localError.value = ''
  await wizard.start(mode.value).catch(() => undefined)
}

async function submitStep() {
  const current = step.value
  if (!current || !canSubmit.value) return
  localError.value = ''

  let hasAnswer = true
  let value: unknown
  switch (current.type) {
    case 'select':
      value = current.options[selectedOptionIndex.value!]?.value
      break
    case 'text':
      if (textAnswerBytes.value > 64 * 1024) {
        localError.value = 'Die Antwort darf höchstens 64 KiB groß sein.'
        return
      }
      value = textAnswer.value
      break
    case 'confirm':
      value = confirmAnswer.value
      break
    case 'multiselect':
      value = selectedOptionIndices.value.map(index => current.options[index]?.value)
      break
    case 'note':
    case 'progress':
    case 'action':
      hasAnswer = false
      break
  }

  await wizard.next({
    stepId: current.id,
    value,
    hasAnswer,
  }).catch(() => undefined)
}

async function refreshWizard() {
  localError.value = ''
  await wizard.refresh().catch(() => undefined)
}

async function cancelWizard() {
  localError.value = ''
  await wizard.cancel().catch(() => undefined)
}

async function copyDeviceCode() {
  const code = step.value?.deviceCode?.code
  if (!code) return
  try {
    if (!navigator.clipboard || !window.isSecureContext) {
      throw new Error('Clipboard API is unavailable.')
    }
    await navigator.clipboard.writeText(code)
    copiedDeviceCode.value = true
    wizard.announcement = 'Device-Code wurde in die Zwischenablage kopiert.'
  } catch {
    localError.value = 'Device-Code konnte nicht kopiert werden. Markiere den Code und kopiere ihn manuell.'
  }
}
</script>

<template>
  <section
    class="wizard-panel"
    aria-labelledby="openclaw-wizard-title"
    :aria-busy="wizard.busy"
  >
    <header class="wizard-header">
      <div class="wizard-title">
        <span class="wizard-icon"><ShieldCheck :size="18" aria-hidden="true" /></span>
        <div>
          <span class="eyebrow">OFFICIAL OPENCLAW FLOW</span>
          <h3 id="openclaw-wizard-title">Neues OpenClaw einrichten</h3>
          <p>
            Nexus rendert den offiziellen Gateway-Wizard live. OpenClaw führt die
            Schritte aus; Provider-Secrets werden im Browser weder abgefragt noch gespeichert.
          </p>
        </div>
      </div>
      <span
        class="nexus-status"
        :class="available ? 'nexus-status--success' : 'nexus-status--warning'"
      >
        <Check v-if="available" :size="12" aria-hidden="true" />
        <CircleAlert v-else :size="12" aria-hidden="true" />
        {{ available ? 'Bereit' : 'Gesperrt' }}
      </span>
    </header>

    <div class="wizard-boundary" role="note">
      <strong>Ausführungsgrenze</strong>
      <span>
        Live-Gateway · kein Daemon-Install · keine Browser-Secrets · Sitzung nur im Arbeitsspeicher
      </span>
    </div>

    <div
      v-if="!available && !active"
      class="wizard-notice wizard-notice--warning"
      role="status"
    >
      <CircleAlert :size="17" aria-hidden="true" />
      <div>
        <strong>Wizard noch nicht verfügbar</strong>
        <p>{{ unavailableReason || 'Gateway-Verbindung, Wizard-Methoden und operator.admin werden benötigt.' }}</p>
      </div>
    </div>

    <form
      v-if="!result"
      class="wizard-start"
      @submit.prevent="startWizard"
    >
      <fieldset :disabled="wizard.busy">
        <legend>OpenClaw-Ziel</legend>
        <label class="wizard-choice">
          <input v-model="mode" type="radio" value="local" />
          <span>
            <strong>Lokal</strong>
            <small>Gateway und Workspace auf demselben OpenClaw-Host einrichten.</small>
          </span>
        </label>
        <label class="wizard-choice">
          <input v-model="mode" type="radio" value="remote" />
          <span>
            <strong>Remote</strong>
            <small>Eine erreichbare Remote-Konfiguration über den offiziellen Flow vorbereiten.</small>
          </span>
        </label>
      </fieldset>

      <label class="wizard-confirm">
        <input v-model="startConfirmed" type="checkbox" />
        <span>
          Ich bestätige, dass der offizielle OpenClaw-Wizard Konfigurationen
          verändern kann. Nexus installiert keine Pakete und startet keinen Daemon.
        </span>
      </label>

      <button
        type="submit"
        class="nexus-button nexus-button--primary wizard-primary"
        :disabled="!canStart"
      >
        <Loader2 v-if="wizard.action === 'start'" :size="14" class="spin" aria-hidden="true" />
        <Play v-else :size="14" aria-hidden="true" />
        Offiziellen Wizard starten
      </button>
    </form>

    <template v-else>
      <div class="wizard-session" aria-label="OpenClaw Wizard-Sitzung">
        <span>
          <strong>Status</strong>
          <code>{{ result.status || result.state }}</code>
        </span>
        <span v-if="result.sessionId">
          <strong>Session</strong>
          <code>{{ result.sessionId }}</code>
        </span>
      </div>

      <article v-if="active && step" class="wizard-step">
        <header>
          <span class="wizard-step-type">{{ step.type }}</span>
          <h4 ref="stepHeading" tabindex="-1">
            {{ step.title || 'OpenClaw Einrichtungsschritt' }}
          </h4>
          <p v-if="step.message">{{ step.message }}</p>
        </header>

        <a
          v-if="safeExternalUrl"
          class="wizard-external nexus-button"
          :href="safeExternalUrl.href"
          target="_blank"
          rel="noopener noreferrer nofollow"
        >
          <ExternalLink :size="14" aria-hidden="true" />
          Externe OpenClaw-Seite öffnen
          <span>{{ safeExternalUrl.label }}</span>
        </a>

        <div v-if="step.deviceCode?.code" class="wizard-device" role="group" aria-label="OpenClaw Device-Code">
          <div>
            <span>DEVICE CODE</span>
            <code tabindex="0">{{ step.deviceCode.code }}</code>
            <small v-if="step.deviceCode.message">{{ step.deviceCode.message }}</small>
            <small v-if="step.deviceCode.expiresInMinutes">
              Gültig für ungefähr {{ step.deviceCode.expiresInMinutes }} Minuten.
            </small>
          </div>
          <button
            type="button"
            class="nexus-button"
            :aria-label="copiedDeviceCode ? 'Device-Code kopiert' : 'Device-Code kopieren'"
            @click="copyDeviceCode"
          >
            <Check v-if="copiedDeviceCode" :size="14" aria-hidden="true" />
            <ClipboardCopy v-else :size="14" aria-hidden="true" />
            {{ copiedDeviceCode ? 'Kopiert' : 'Kopieren' }}
          </button>
        </div>

        <div
          v-if="step.sensitive || !step.canAnswer"
          class="wizard-notice wizard-notice--blocked"
          role="alert"
        >
          <ShieldCheck :size="18" aria-hidden="true" />
          <div>
            <strong>Serverseitiger Secret-Schritt</strong>
            <p>
              {{ step.blockedReason || 'Dieser Schritt kann aus Sicherheitsgründen nicht im Browser beantwortet werden.' }}
            </p>
            <p>Lege das Secret direkt in OpenClaw oder als serverseitigen SecretRef an.</p>
          </div>
        </div>

        <fieldset
          v-else-if="step.type === 'select'"
          class="wizard-options"
          :disabled="wizard.busy"
        >
          <legend>Eine Option auswählen</legend>
          <label
            v-for="(option, index) in step.options"
            :key="`${step.id}-${index}`"
            class="wizard-choice"
          >
            <input v-model="selectedOptionIndex" type="radio" :value="index" />
            <span>
              <strong>{{ option.label }}</strong>
              <small v-if="option.hint">{{ option.hint }}</small>
            </span>
          </label>
        </fieldset>

        <label v-else-if="step.type === 'text'" class="wizard-text">
          <span>Antwort</span>
          <input
            v-model="textAnswer"
            type="text"
            autocomplete="off"
            :placeholder="step.placeholder || undefined"
            :aria-describedby="textAnswerBytes > 64 * 1024 ? 'wizard-text-error' : undefined"
          />
          <small
            id="wizard-text-error"
            :class="{ 'wizard-text-error': textAnswerBytes > 64 * 1024 }"
          >
            {{ textAnswerBytes.toLocaleString('de-DE') }} / 65.536 Bytes
          </small>
        </label>

        <fieldset
          v-else-if="step.type === 'confirm'"
          class="wizard-options wizard-options--inline"
          :disabled="wizard.busy"
        >
          <legend>Entscheidung bestätigen</legend>
          <label class="wizard-choice">
            <input v-model="confirmAnswer" type="radio" :value="true" />
            <span><strong>Ja</strong></span>
          </label>
          <label class="wizard-choice">
            <input v-model="confirmAnswer" type="radio" :value="false" />
            <span><strong>Nein</strong></span>
          </label>
        </fieldset>

        <fieldset
          v-else-if="step.type === 'multiselect'"
          class="wizard-options"
          :disabled="wizard.busy"
        >
          <legend>Optionen auswählen</legend>
          <label
            v-for="(option, index) in step.options"
            :key="`${step.id}-${index}`"
            class="wizard-choice"
          >
            <input v-model="selectedOptionIndices" type="checkbox" :value="index" />
            <span>
              <strong>{{ option.label }}</strong>
              <small v-if="option.hint">{{ option.hint }}</small>
            </span>
          </label>
        </fieldset>

        <div
          v-else-if="step.type === 'progress'"
          class="wizard-progress"
          role="status"
        >
          <Loader2 :size="18" class="spin" aria-hidden="true" />
          <span>OpenClaw führt den aktuellen Schritt aus.</span>
        </div>

        <div v-else-if="step.type === 'action'" class="wizard-action-note" role="note">
          <strong>{{ step.executor === 'client' ? 'Aktion im Browser' : 'Aktion im Gateway' }}</strong>
          <span>
            {{ safeExternalUrl ? 'Führe die verlinkte Aktion aus und setze den Wizard danach fort.' : 'OpenClaw führt die Aktion beim Fortsetzen aus.' }}
          </span>
        </div>

        <div class="wizard-actions">
          <button
            type="button"
            class="nexus-button"
            :disabled="wizard.busy"
            @click="refreshWizard"
          >
            <Loader2 v-if="wizard.action === 'refresh'" :size="14" class="spin" aria-hidden="true" />
            <RefreshCw v-else :size="14" aria-hidden="true" />
            Status prüfen
          </button>
          <button
            type="button"
            class="nexus-button nexus-button--danger"
            :disabled="wizard.busy"
            @click="cancelWizard"
          >
            <Loader2 v-if="wizard.action === 'cancel'" :size="14" class="spin" aria-hidden="true" />
            <XCircle v-else :size="14" aria-hidden="true" />
            Abbrechen
          </button>
          <button
            v-if="!step.sensitive && step.canAnswer"
            type="button"
            class="nexus-button nexus-button--primary wizard-primary"
            :disabled="!canSubmit"
            @click="submitStep"
          >
            <Loader2 v-if="wizard.action === 'next'" :size="14" class="spin" aria-hidden="true" />
            <Check v-else :size="14" aria-hidden="true" />
            {{ continueLabel }}
          </button>
        </div>
      </article>

      <div v-else-if="result.done" class="wizard-finished" role="status">
        <Check v-if="result.status === 'done'" :size="20" aria-hidden="true" />
        <XCircle v-else :size="20" aria-hidden="true" />
        <div>
          <strong>{{ result.status === 'done' ? 'OpenClaw-Wizard abgeschlossen' : 'OpenClaw-Wizard beendet' }}</strong>
          <p>{{ result.error || result.message }}</p>
          <p v-if="result.recovery">{{ result.recovery }}</p>
        </div>
        <button type="button" class="nexus-button" @click="wizard.clearCompleted()">
          <RotateCcw :size="14" aria-hidden="true" />
          Zur Startansicht
        </button>
      </div>
    </template>

    <div
      v-if="combinedError"
      ref="errorAlert"
      class="wizard-notice wizard-notice--error"
      role="alert"
      tabindex="-1"
    >
      <CircleAlert :size="17" aria-hidden="true" />
      <div>
        <strong>Wizard-Aktion fehlgeschlagen</strong>
        <p>{{ combinedError }}</p>
      </div>
    </div>

    <p class="nexus-visually-hidden" aria-live="polite" aria-atomic="true">
      {{ wizard.announcement }}
    </p>
  </section>
</template>

<style scoped>
.wizard-panel {
  display: grid;
  gap: 14px;
  padding: 15px;
  border: 1px solid var(--line);
  border-radius: var(--r);
  background: var(--accent-wash);
}

.wizard-header,
.wizard-title,
.wizard-actions,
.wizard-finished,
.wizard-device {
  display: flex;
  align-items: center;
  gap: 12px;
}

.wizard-header {
  align-items: flex-start;
  justify-content: space-between;
}

.wizard-title {
  align-items: flex-start;
  min-width: 0;
}

.wizard-icon {
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  flex: 0 0 38px;
  border: 1px solid var(--line-2);
  border-radius: var(--r-sm);
  background: var(--grad-soft);
  color: var(--a-mid);
}

.wizard-title h3,
.wizard-step h4 {
  margin: 3px 0 0;
  color: var(--tx);
  font-family: var(--font-display);
}

.wizard-title h3 {
  font-size: 15px;
}

.wizard-title p,
.wizard-step header p,
.wizard-notice p,
.wizard-finished p {
  margin: 5px 0 0;
  color: var(--tx-2);
  line-height: 1.55;
}

.wizard-boundary,
.wizard-action-note,
.wizard-progress {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 40px;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--glass);
  color: var(--tx-2);
}

.wizard-boundary strong,
.wizard-step-type,
.wizard-session strong,
.wizard-device > div > span {
  color: var(--tx-3);
  font-family: var(--font-mono-v2);
  font-size: 10px;
  letter-spacing: .05em;
  text-transform: uppercase;
}

.wizard-start,
.wizard-step,
.wizard-options {
  display: grid;
  gap: 12px;
}

.wizard-start fieldset,
.wizard-options {
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

.wizard-start legend,
.wizard-options legend,
.wizard-text > span {
  margin-bottom: 7px;
  color: var(--tx-3);
  font-weight: 700;
}

.wizard-choice,
.wizard-confirm {
  min-height: 44px;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--glass);
  color: var(--tx-2);
  cursor: pointer;
}

.wizard-choice + .wizard-choice {
  margin-top: 7px;
}

.wizard-choice:has(input:checked) {
  border-color: color-mix(in srgb, var(--a-blue) 48%, var(--line));
  background: var(--accent-wash-strong);
}

.wizard-choice input,
.wizard-confirm input {
  margin: 3px 0 0;
  flex: 0 0 auto;
}

.wizard-choice span {
  display: grid;
  gap: 3px;
}

.wizard-choice small,
.wizard-text small,
.wizard-device small {
  color: var(--tx-3);
  line-height: 1.45;
}

.wizard-primary {
  justify-self: start;
  min-height: 44px;
}

.wizard-session {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
}

.wizard-session span {
  min-width: 0;
  display: grid;
  gap: 4px;
  padding: 9px 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--glass);
}

.wizard-session code {
  overflow-wrap: anywhere;
  color: var(--tx-2);
  font-family: var(--font-mono-v2);
  font-size: 11px;
}

.wizard-step {
  padding: 14px;
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  background: var(--glass);
}

.wizard-step h4 {
  font-size: 17px;
  outline: none;
}

.wizard-step h4:focus-visible {
  border-radius: 4px;
  box-shadow: var(--focus-ring);
}

.wizard-external {
  min-height: 44px;
  justify-self: start;
  max-width: 100%;
}

.wizard-external span {
  max-width: 280px;
  overflow: hidden;
  color: var(--tx-3);
  font-family: var(--font-mono-v2);
  font-size: 10px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.wizard-device {
  justify-content: space-between;
  padding: 12px;
  border: 1px solid var(--status-queue-line);
  border-radius: var(--r-sm);
  background: var(--status-queue-bg);
}

.wizard-device > div {
  min-width: 0;
  display: grid;
  gap: 5px;
}

.wizard-device code {
  overflow-wrap: anywhere;
  color: var(--tx);
  font-family: var(--font-mono-v2);
  font-size: 17px;
  letter-spacing: .08em;
  user-select: all;
}

.wizard-text {
  display: grid;
  gap: 6px;
}

.wizard-text input {
  width: 100%;
  min-height: 44px;
}

.wizard-text-error {
  color: var(--st-block) !important;
}

.wizard-options--inline {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.wizard-options--inline legend {
  grid-column: 1 / -1;
}

.wizard-options--inline .wizard-choice {
  margin: 0;
}

.wizard-progress,
.wizard-action-note {
  align-items: flex-start;
}

.wizard-action-note {
  display: grid;
  gap: 4px;
}

.wizard-actions {
  justify-content: flex-end;
  flex-wrap: wrap;
  padding-top: 2px;
}

.wizard-actions .nexus-button {
  min-height: 44px;
}

.wizard-notice {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 10px;
  padding: 11px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
}

.wizard-notice--warning {
  border-color: var(--status-queue-line);
  background: var(--status-queue-bg);
  color: var(--st-queue);
}

.wizard-notice--blocked,
.wizard-notice--error {
  border-color: var(--status-block-line);
  background: var(--status-block-bg);
  color: var(--st-block);
}

.wizard-notice:focus {
  outline: 2px solid var(--a-blue);
  outline-offset: 2px;
}

.wizard-finished {
  align-items: flex-start;
  padding: 13px;
  border: 1px solid var(--status-work-line);
  border-radius: var(--r-sm);
  background: var(--status-work-bg);
  color: var(--st-work);
}

.wizard-finished > div {
  min-width: 0;
  flex: 1;
}

.spin {
  animation: wizard-spin 1s linear infinite;
}

@keyframes wizard-spin {
  to { transform: rotate(360deg); }
}

@media (max-width: 760px) {
  .wizard-header,
  .wizard-device,
  .wizard-finished {
    align-items: stretch;
    flex-direction: column;
  }

  .wizard-header .nexus-status,
  .wizard-device .nexus-button,
  .wizard-finished .nexus-button {
    align-self: flex-start;
  }

  .wizard-session,
  .wizard-options--inline {
    grid-template-columns: 1fr;
  }

  .wizard-actions {
    align-items: stretch;
    flex-direction: column-reverse;
  }

  .wizard-actions .nexus-button {
    width: 100%;
  }

  .wizard-external span {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .spin {
    animation: none;
  }
}
</style>
