<script setup lang="ts">
import { computed, getCurrentInstance, nextTick, onBeforeUnmount, onMounted, ref, watch, type Slot } from 'vue'
import FdsIcon from '../FdsIcon/FdsIcon.vue'
import FdsLead from '../Typography/FdsLead/FdsLead.vue'
import type { FdsWizardGoPayload, FdsWizardProps, FdsWizardSchema } from './types'
import { hasStepLabelOverlap } from './utils/stepLabelOverlap'

const props = withDefaults(defineProps<FdsWizardProps>(), {
  current: undefined,
  hideSteps: false,
  stepWidth: undefined,
  disableStepJump: false,
  activeRouteName: undefined,
  dataTestid: undefined,
  wizardClass: 'max-w-[480px]',
})

const emit = defineEmits<{
  (e: 'go', payload: FdsWizardGoPayload): void
}>()

const instance = getCurrentInstance()

const mainHeading = ref('')
const mainLead = ref('')
const subCategories = ref<any[]>([])
const contentClass = ref('max-w-screen-sm')
const internalCompletedTo = ref(0)
const internalMaxStep = ref(0)
const wizardData = ref<Record<string, unknown>>({})
const completedRouteNamesState = ref<Record<string, boolean>>({})
const wizardSchema = ref<FdsWizardSchema | null>(null)
const stepperListRef = ref<HTMLElement | null>(null)
const stepperWidthPx = ref(0)
const stepLabelTotalWidthPx = ref(0)
const stepLabelRefs = ref<(HTMLElement | null)[]>([])
const hasOverlappingStepLabels = ref(false)
let stepperResizeObserver: ResizeObserver | null = null

const router = computed(() => {
  try {
    return instance?.appContext.config.globalProperties.$router as
      | {
          push: (payload: {
            name?: string
            path?: string
            params?: Record<string, unknown>
            query?: Record<string, unknown>
          }) => void
        }
      | undefined
  } catch {
    return undefined
  }
})

const currentRouteFromRouter = computed(() => {
  try {
    const resolved = instance?.appContext.config.globalProperties.$router?.currentRoute?.value
    return resolved as { name?: string | symbol | null } | undefined
  } catch {
    return undefined
  }
})

const routeName = computed(() => {
  if (props.activeRouteName) return props.activeRouteName
  const currentName = currentRouteFromRouter.value?.name
  return typeof currentName === 'string' ? currentName : undefined
})

const routeIndexFromName = computed(() => {
  if (!routeName.value) return -1
  return props.routes.findIndex((route) => route.name === routeName.value)
})

const currentStepIndex = computed(() => {
  if (props.current !== undefined) return props.current
  if (routeIndexFromName.value >= 0) return routeIndexFromName.value
  return 0
})

const progressWidth = computed(() => {
  const entries = visibleStepEntries.value ?? []
  const stepsCount = entries.length
  if (stepsCount <= 1) return '0%'

  const targetFullIndex = Math.max(currentStepIndex.value, maxCompletedStepIndex.value, 0)

  const targetVisibleEntries = entries
    .map(({ index }, visibleIndex) => ({ fullIndex: index, visibleIndex }))
    .filter(({ fullIndex }) => fullIndex <= targetFullIndex)

  const lastTargetEntry =
    targetVisibleEntries.length > 0 ? targetVisibleEntries[targetVisibleEntries.length - 1] : undefined
  const targetVisibleIndex = lastTargetEntry?.visibleIndex ?? 0

  const targetStep = Math.min(targetVisibleIndex, stepsCount - 1)
  return `${(targetStep / (stepsCount - 1)) * 100}%`
})

const stepWidthStyle = computed(() => (props.stepWidth ? `width: ${props.stepWidth}px` : ''))

const isStepperHidden = computed(() => {
  if (props.hideSteps === true) return true
  const currentRoute = props.routes[currentStepIndex.value]
  return currentRoute?.meta?.wizard?.includeInStepper === false
})

/** Steps to show in the nav: all routes, or all except those whose routeName is in hideSteps array. */
const visibleStepEntries = computed(() => {
  const includeInStepper = (route: FdsWizardProps['routes'][number]) => route.meta?.wizard?.includeInStepper !== false

  const hide = props.hideSteps
  if (hide === true) return []
  if (Array.isArray(hide) && hide.length > 0) {
    return props.routes
      .map((route, index) => ({ route, index }))
      .filter(({ route }) => !hide.includes(route.name) && includeInStepper(route))
  }
  return props.routes.map((route, index) => ({ route, index })).filter(({ route }) => includeInStepper(route))
})

const updateStepperWidth = () => {
  stepperWidthPx.value = stepperListRef.value?.clientWidth ?? 0
}

const setStepLabelRef = (el: unknown, visibleIndex: number) => {
  stepLabelRefs.value[visibleIndex] = el instanceof HTMLElement ? el : null
}

const updateStepLabelWidths = () => {
  const labels = stepLabelRefs.value.filter((el): el is HTMLElement => !!el)
  stepLabelTotalWidthPx.value = labels.reduce((sum, el) => sum + el.offsetWidth, 0)
  hasOverlappingStepLabels.value = hasStepLabelOverlap(labels)
}
const onWindowResize = () => {
  updateStepperWidth()
  updateStepLabelWidths()
}

const showStepLabels = computed(() => {
  if (visibleStepEntries.value.length <= 0) return false
  if (stepperWidthPx.value <= 0) return true
  return !hasOverlappingStepLabels.value
})

const rootAttrs = computed(() => {
  const attrs: Record<string, string> = {}
  if (props.dataTestid) {
    attrs['data-testid'] = props.dataTestid
  }
  return attrs
})

const setStepClasses = (step: number) => {
  const classes: string[] = []
  const isCurrent = currentStepIndex.value === step
  const isCompleted = internalCompletedTo.value >= step
  const isAllowedByMax = step <= internalMaxStep.value

  if (isCurrent) {
    classes.push('bg-blue-600!', 'text-white-solid', 'border-blue-600', 'fill-white')
  } else if (isCompleted || isAllowedByMax) {
    classes.push('bg-white-solid', 'text-blue-700', 'border-blue-600', 'fill-blue-700')
  }

  if (isCompleted) {
    classes.push('border-2')
  }

  return classes.join(' ')
}

const maxCompletedStepIndex = computed(() => {
  let maxIndex = -1
  props.routes.forEach((route, index) => {
    if (completedRouteNamesState.value[route.name]) {
      maxIndex = index
    }
  })
  return maxIndex
})

const completedRouteNames = computed(() =>
  props.routes.filter((route) => completedRouteNamesState.value[route.name]).map((route) => route.name),
)

const canGoToStep = (from: number, to: number) => {
  const toRoute = props.routes[to]
  if (!toRoute) return false

  // Hårddisabla steg från konsumenten
  if (toRoute.disabled === true) return false

  // Navigering till nuvarande steg är inte meningsfull
  if (to === from) return false

  // Tillåt alltid navigering bakåt (< current), oavsett validering
  if (to < from) return true

  // Framåt: om inga steg är markerade som completed, tillåt inget
  if (maxCompletedStepIndex.value < 0) return false

  // Framåt: tillåt bara navigation till steg upp till senaste completed
  return to <= maxCompletedStepIndex.value
}

const allowNextStep = () => {
  const nextIndex = currentStepIndex.value + 1
  const nextRoute = props.routes[nextIndex]

  if (!nextRoute) return

  completedRouteNamesState.value = {
    ...completedRouteNamesState.value,
    [nextRoute.name]: true,
  }
}

const setWizardSchema = (schema: FdsWizardSchema) => {
  wizardSchema.value = schema
}

const setStepData = (patch: Record<string, unknown>) => {
  const base = {
    ...wizardData.value,
    ...patch,
  }
  wizardData.value = base
}

const resetDataFromStep = (routeName: string) => {
  if (!wizardSchema.value) return

  const startIndex = props.routes.findIndex((route) => route.name === routeName)
  if (startIndex < 0) return

  // Behåll data upp till och med routeName oförändrad
  const base = { ...wizardData.value }

  const laterRoutes = props.routes.slice(startIndex + 1)
  let nextData = base

  laterRoutes.forEach((route) => {
    const resetFn = wizardSchema.value?.[route.name]
    if (resetFn) {
      nextData = resetFn(nextData)
    }
  })
  wizardData.value = nextData

  // Förbjud navigering till efterföljande steg genom att ta bort deras completed-markeringar
  const newCompletedState: Record<string, boolean> = {}
  props.routes.forEach((route, index) => {
    if (index <= startIndex && completedRouteNamesState.value[route.name]) {
      newCompletedState[route.name] = true
    }
  })
  completedRouteNamesState.value = newCompletedState
}

const resolveStepIndex = (step: number | string): number => {
  if (typeof step === 'number') return step
  return props.routes.findIndex((route) => route.name === step || route.routeName === step)
}

const setCompletedTo = (step: number | string) => {
  const resolvedIndex = resolveStepIndex(step)
  if (!Number.isFinite(resolvedIndex)) return

  // Supportera "inget completed alls"
  if (resolvedIndex < 0) {
    internalCompletedTo.value = -1
    internalMaxStep.value = 0
    completedRouteNamesState.value = {}
    return
  }

  const safeIndex = Math.max(0, Math.min(Math.floor(resolvedIndex), props.routes.length - 1))
  internalCompletedTo.value = safeIndex
  internalMaxStep.value = safeIndex

  const nextCompletedState: Record<string, boolean> = {}
  props.routes.forEach((route, routeIndex) => {
    if (routeIndex <= safeIndex) nextCompletedState[route.name] = true
  })
  completedRouteNamesState.value = nextCompletedState
}

const goToStep = (step: number | string) => {
  const stepIndex = resolveStepIndex(step)
  if (stepIndex < 0) return

  const fromIndex = currentStepIndex.value
  const route = props.routes[stepIndex]

  if (!route) return
  const r = router.value
  if (!r) return
  if (!canGoToStep(fromIndex, stepIndex)) return

  const toLocation =
    route.name != null
      ? {
          name: route.name,
          params: route.params as Record<string, unknown> | undefined,
          query: route.query as Record<string, unknown> | undefined,
        }
      : route.to
        ? {
            path: route.to,
            query: route.query as Record<string, unknown> | undefined,
          }
        : null

  if (!toLocation) return

  r.push(toLocation)
  emit('go', { index: stepIndex, route })
}

const nextStep = () => {
  const next = currentStepIndex.value + 1
  if (next < props.routes.length) {
    goToStep(next)
  }
}

const previousStep = () => {
  const prev = currentStepIndex.value - 1
  if (prev >= 0) {
    goToStep(prev)
  }
}

const setHeading = (heading: string) => {
  mainHeading.value = heading
}

const setLead = (lead: string) => {
  mainLead.value = lead
}

const setSubCategories = (subCat: any) => {
  subCategories.value = subCat
}

const setContentClass = (classes: string) => {
  contentClass.value = classes
}

const wizardStepProps = computed(() => ({
  currentStepIndex,
  goToStep,
  nextStep,
  previousStep,
  wizardData,
  setStepData,
  resetDataFromStep,
  setWizardSchema,
  allowNextStep,
  completedRouteNames,
  setCompletedTo,
  setHeading,
  setLead,
  setSubCategories,
  setContentClass,
}))

defineExpose({
  goToStep,
  nextStep,
  previousStep,
  currentStep: currentStepIndex,
  completedTo: internalCompletedTo,
  maxStep: internalMaxStep,
  wizardData,
  setStepData,
  resetDataFromStep,
  setWizardSchema,
  allowNextStep,
  completedRouteNames,
  setCompletedTo,
  setHeading,
  setLead,
  setSubCategories,
  setContentClass,
})

// Reset content class when current step index changes
watch(currentStepIndex, () => {
  contentClass.value = 'max-w-screen-sm'
})

watch(
  visibleStepEntries,
  async () => {
    stepLabelRefs.value = []
    await nextTick()
    updateStepperWidth()
    updateStepLabelWidths()
  },
  { immediate: true },
)

watch(
  currentStepIndex,
  (newIndex, oldIndex) => {
    if (oldIndex === undefined) {
      internalMaxStep.value = Math.max(internalMaxStep.value, newIndex)
      internalCompletedTo.value = Math.max(internalCompletedTo.value, newIndex - 1)
      return
    }
    if (newIndex > oldIndex) {
      internalMaxStep.value = Math.max(internalMaxStep.value, newIndex)
      internalCompletedTo.value = Math.max(internalCompletedTo.value, newIndex - 1)
    }
  },
  { immediate: true },
)

onMounted(() => {
  nextTick(() => {
    updateStepperWidth()
    updateStepLabelWidths()
  })
  if (typeof window !== 'undefined') {
    window.addEventListener('resize', onWindowResize)
  }
  if (typeof ResizeObserver !== 'undefined') {
    stepperResizeObserver = new ResizeObserver(() => {
      updateStepperWidth()
      updateStepLabelWidths()
    })
    if (stepperListRef.value) stepperResizeObserver.observe(stepperListRef.value)
  }

  if (currentStepIndex.value > 0) {
    goToStep(0)
  }
})

onBeforeUnmount(() => {
  if (typeof window !== 'undefined') {
    window.removeEventListener('resize', onWindowResize)
  }
  if (stepperResizeObserver) {
    stepperResizeObserver.disconnect()
    stepperResizeObserver = null
  }
})

defineSlots<{
  default?: Slot
}>()
</script>

<template>
  <div v-bind="rootAttrs">
    <nav :class="wizardClass" class="mx-auto">
      <header class="mb-6">
        <h1 ref="mainHeader" tabindex="-1">
          {{ mainHeading }}
        </h1>
        <FdsLead v-if="mainLead || subCategories.length > 0">
          <p v-if="mainLead" class="mb-0">
            {{ mainLead }}
          </p>
          <ul v-if="subCategories.length > 0" class="mt-2 mb-0">
            <li v-for="subCat in subCategories" :key="subCat.id" class="list-inside list-disc mb-2">
              <FdsLead class="inline!">{{ subCat.name }}</FdsLead>
            </li>
          </ul>
        </FdsLead>
      </header>
      <div v-if="!isStepperHidden" :style="stepWidthStyle" class="relative">
        <span
          class="h-[2px] absolute top-[22px] left-[50%] bg-white-solid transform -translate-x-1/2 w-[calc(100%-50px)]"
        >
          <span class="block h-full max-w-full bg-blue-600" :style="{ width: progressWidth }"></span>
        </span>
        <div class="flex justify-between list-none mb-8 p-0">
          <ol ref="stepperListRef" class="flex justify-between w-full">
            <li
              class="flex flex-col justify-center items-center mb-0"
              v-for="(entry, visibleIndex) in visibleStepEntries"
              :key="entry.route.id"
            >
              <button
                type="button"
                class="relative flex flex-col items-center focus-visible:outline-2 focus-visible:outline-dashed focus-visible:outline-offset-4 focus-visible:outline-blue-500"
                @click="disableStepJump ? null : goToStep(entry.index)"
                :disabled="!canGoToStep(currentStepIndex, entry.index)"
                :class="{ 'cursor-not-allowed': !canGoToStep(currentStepIndex, entry.index) }"
              >
                <span
                  class="relative mb-6 flex flex-col items-center text-blue-600 font-bold justify-center transition-all duration-200 ease-in-out rounded-full w-[44px] h-[44px] bg-white-solid"
                  :class="setStepClasses(entry.index)"
                >
                  <span v-if="entry.index >= internalCompletedTo + 1">{{ entry.index + 1 }}</span>
                  <FdsIcon v-else name="checkmark" :size="24" />
                </span>
                <span
                  :ref="(el) => setStepLabelRef(el, visibleIndex)"
                  class="absolute bottom-0 text-blue-600 font-bold text-sm whitespace-nowrap"
                  :class="{ invisible: !showStepLabels }"
                >
                  {{ entry.route.meta.wizard.name }}
                </span>
              </button>
            </li>
          </ol>
        </div>
      </div>
    </nav>
    <div :class="contentClass" class="mx-auto">
      <component :is="'router-view'" v-slot="{ Component }">
        <component :is="Component" v-if="Component" v-bind="wizardStepProps" />
      </component>
    </div>
  </div>
</template>
