<script setup lang="ts">
import { computed } from 'vue'
import {
  AlertTriangle,
  CircleOff,
  CloudOff,
  FileQuestion,
  Loader2,
  RefreshCw,
} from '@lucide/vue'
import { toAppProblem, type AppProblem } from '../../api/contracts'

export type AsyncStateKind = 'loading' | 'empty' | 'error' | 'offline' | 'stale' | 'partial'

const props = withDefaults(defineProps<{
  state: AsyncStateKind
  title?: string
  message?: string
  problem?: unknown
  actionLabel?: string
  busy?: boolean
  compact?: boolean
  inline?: boolean
}>(), {
  title: '',
  message: '',
  problem: undefined,
  actionLabel: '',
  busy: false,
  compact: false,
  inline: false,
})

defineEmits<{ action: [] }>()

const normalizedProblem = computed<AppProblem | null>(() =>
  props.problem === undefined ? null : toAppProblem(props.problem),
)

const resolvedState = computed<AsyncStateKind>(() =>
  props.state === 'error' && normalizedProblem.value?.kind === 'offline'
    ? 'offline'
    : props.state,
)

const problemLabel = computed(() => {
  const kind = normalizedProblem.value?.kind
  if (kind === 'authentication') return 'Sitzung abgelaufen'
  if (kind === 'permission') return 'Keine Berechtigung'
  if (kind === 'conflict') return 'Versionskonflikt'
  if (kind === 'unsupported') return 'Capability fehlt'
  if (kind === 'offline') return 'Abhängigkeit nicht erreichbar'
  if (kind === 'timeout') return 'Zeitüberschreitung'
  if (kind === 'rate-limit') return 'Rate Limit'
  if (kind === 'not-found') return 'Nicht gefunden'
  if (kind === 'validation') return 'Eingabe ungültig'
  if (kind === 'internal') return 'Interner Fehler'
  return ''
})

const presentation = computed(() => {
  const problem = normalizedProblem.value
  const defaults = {
    loading: ['Daten werden geladen', 'Nexus synchronisiert den aktuellen Stand.'],
    empty: ['Noch keine Daten', 'Für diesen Bereich liegen derzeit keine Einträge vor.'],
    error: ['Daten konnten nicht geladen werden', problem?.message || 'Die Anfrage ist fehlgeschlagen.'],
    offline: ['Verbindung nicht verfügbar', problem?.message || 'Nexus kann den abhängigen Dienst derzeit nicht erreichen.'],
    stale: ['Daten möglicherweise veraltet', 'Die letzte bestätigte Momentaufnahme bleibt sichtbar.'],
    partial: ['Daten teilweise verfügbar', 'Nexus zeigt den bestätigten Teilbestand und hält fehlende Bereiche sichtbar.'],
  } satisfies Record<AsyncStateKind, [string, string]>

  return {
    title: props.title || defaults[props.state][0],
    message: props.message || defaults[props.state][1],
  }
})

const resolvedActionLabel = computed(() => {
  if (props.actionLabel) return props.actionLabel
  const action = normalizedProblem.value?.recoveryAction
  if (action === 'reauthenticate') return 'Neu anmelden'
  if (action === 'reload-conflict') return 'Aktuellen Stand laden'
  if (action === 'inspect-connection') return 'Erneut prüfen'
  if (action === 'retry') return 'Erneut versuchen'
  return ''
})

const icon = computed(() => {
  if (resolvedState.value === 'loading') return Loader2
  if (resolvedState.value === 'empty') return FileQuestion
  if (resolvedState.value === 'offline') return CloudOff
  if (resolvedState.value === 'stale' || resolvedState.value === 'partial') return CircleOff
  return AlertTriangle
})

const liveRole = computed(() =>
  resolvedState.value === 'error' || resolvedState.value === 'offline' ? 'alert' : 'status',
)
</script>

<template>
  <section
    class="async-state nexus-state"
    :class="[`nexus-state--${resolvedState}`, problemLabel ? `nexus-state--problem-${normalizedProblem?.kind}` : '', { 'async-state--compact': compact, 'async-state--inline': inline }]"
    :role="liveRole"
    :aria-busy="resolvedState === 'loading' || busy"
    :data-state="resolvedState"
    :data-problem-kind="normalizedProblem?.kind"
  >
    <component
      :is="icon"
      :size="inline ? 14 : compact ? 18 : 22"
      :class="{ spin: resolvedState === 'loading' || busy }"
      aria-hidden="true"
    />
    <div class="async-state__body">
      <span v-if="problemLabel" class="async-state__kind">{{ problemLabel }}</span>
      <strong>{{ presentation.title }}</strong>
      <p>{{ presentation.message }}</p>
      <details v-if="normalizedProblem?.traceId || normalizedProblem?.operationId" class="async-state__details">
        <summary>Technische Details</summary>
        <span v-if="normalizedProblem.traceId">Trace {{ normalizedProblem.traceId }}</span>
        <span v-if="normalizedProblem.operationId">Operation {{ normalizedProblem.operationId }}</span>
      </details>
    </div>
    <button
      v-if="resolvedActionLabel"
      type="button"
      class="nexus-button"
      :disabled="busy"
      @click="$emit('action')"
    >
      <Loader2 v-if="busy" :size="14" class="spin" aria-hidden="true" />
      <RefreshCw v-else :size="14" aria-hidden="true" />
      {{ resolvedActionLabel }}
    </button>
  </section>
</template>

<style scoped>
.async-state {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 112px;
  padding: 18px;
  text-align: left;
}

.async-state--compact {
  min-height: 72px;
  padding: 12px 14px;
}

.async-state--inline {
  min-height: 32px;
  padding: 4px 8px;
  gap: 7px;
}

.async-state--inline .async-state__body strong {
  overflow: hidden;
  font-family: var(--font-body, 'Manrope', sans-serif);
  font-size: 11px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.async-state--inline .async-state__body p,
.async-state--inline .async-state__details,
.async-state--inline .async-state__kind {
  display: none;
}

.async-state--inline .nexus-button {
  min-height: 24px;
  padding: 3px 7px;
  font-size: 10px;
}

.async-state__body {
  min-width: 0;
}

.async-state__body strong {
  display: block;
  color: var(--tx);
  font-family: 'Space Grotesk', sans-serif;
  font-size: 14px;
  line-height: 1.35;
}

.async-state__kind {
  display: block;
  margin-bottom: 3px;
  color: var(--tx-3);
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
}

.async-state__body p {
  margin: 4px 0 0;
  color: var(--tx-2);
  font-family: 'Manrope', sans-serif;
  font-size: 12px;
  line-height: 1.55;
}

.async-state__details {
  margin-top: 8px;
  color: var(--tx-3);
  font-family: 'JetBrains Mono', monospace;
  font-size: 11px;
}

.async-state__details summary {
  cursor: pointer;
}

.async-state__details span {
  display: block;
  overflow-wrap: anywhere;
  margin-top: 4px;
}

@media (max-width: 560px) {
  .async-state {
    grid-template-columns: auto minmax(0, 1fr);
  }

  .async-state .nexus-button {
    grid-column: 1 / -1;
    justify-self: start;
  }
}
</style>
