<template>
  <div
    v-bind="{ ...$attrs, class: undefined }"
    :class="[
      'ivyforms-mixed-input ivyforms-align-items-center ivyforms-pl-12 ivyforms-py-0 ivyforms-border-radius-8',
      $attrs.class,
      {
        'is-filled': props.filled,
        'is-secondary': props.secondary,
        'is-disabled': props.disabled,
      },
    ]"
  >
    <!-- Prepend slot for icon or custom content -->
    <div
      v-if="!!slots['prepend'] || iconPrepend"
      class="ivyforms-mixed-input__prepend ivyforms-flex ivyforms-align-items-center ivyforms-mr-8 ivyforms-flex-shrink-0"
    >
      <slot name="prepend">
        <IvyIcon
          v-if="iconPrepend"
          :name="iconPrepend"
          :category="iconPrependCategory"
          :type="iconPrependType"
          class="ivyforms-mixed-input__icon-prepend ivyforms-flex-shrink-0"
          size="l"
          :color="iconColor"
        />
      </slot>
    </div>

    <!-- Number input section -->
    <div
      class="ivyforms-mixed-input__input ivyforms-flex ivyforms-align-items-center ivyforms-flex-1"
    >
      <input
        ref="inputRef"
        :value="localValue"
        type="text"
        inputmode="numeric"
        :placeholder="props.placeholder"
        :disabled="props.disabled"
        class="ivyforms-mixed-input__field ivyforms-width-100 ivyforms-p-0 ivyforms-m-0"
        @input="onInput"
        @focus="isFocused = true"
        @blur="isFocused = false"
      />
    </div>

    <!-- Append slot for unit select or custom content -->
    <div
      v-if="!!slots['append'] || hasUnitSelect"
      class="ivyforms-mixed-input__append ivyforms-flex ivyforms-align-items-center ivyforms-flex-shrink-0"
    >
      <slot name="append">
        <div
          v-if="hasUnitSelect"
          ref="unitSelectRef"
          class="ivyforms-mixed-input__unit-select ivyforms-flex ivyforms-align-items-center ivyforms-gap-4 ivyforms-border-radius-4 ivyforms-px-12 ivyforms-py-8"
        >
          <span class="ivyforms-mixed-input__unit-text regular-14">{{ localUnit }}</span>
          <IvyIcon
            name="chevron-down"
            category="arrows"
            type="line"
            class="ivyforms-mixed-input__unit-icon ivyforms-flex-shrink-0 ivyforms-cursor-pointer"
            size="s"
            color="var(--map-base-dusk-symbol-2)"
            @click="toggleUnitDropdown"
          />
        </div>
      </slot>
    </div>
  </div>

  <Teleport to="body">
    <div
      v-if="showUnitDropdown"
      class="ivyforms-mixed-input__unit-dropdown ivyfors-py-4 ivyforms-border-radius-8"
      :style="dropdownStyle"
      :data-secondary="props.secondary || undefined"
      @mousedown.prevent
    >
      <div
        v-for="unitOption in props.units"
        :key="unitOption"
        class="ivyforms-mixed-input__unit-option ivyforms-px-12 ivyforms-py-6 regular-14 ivyforms-cursor-pointer"
        :class="{ 'is-selected': localUnit === unitOption }"
        @click="selectUnit(unitOption)"
      >
        {{ unitOption }}
      </div>
    </div>
  </Teleport>
</template>

<script setup lang="ts">
import { ref, computed, watch, useSlots, onMounted, onUnmounted, type CSSProperties } from 'vue'
import type { IconCategory } from '@/types/icons/icon-category'
import type { IconType } from '@/types/icons/icon-type'

defineOptions({ inheritAttrs: false })

interface Props {
  modelValue?: number | string | null
  unit?: string
  units?: string[]
  placeholder?: string
  iconPrepend?: string
  iconPrependCategory?: IconCategory
  iconPrependType?: IconType
  iconColor?: string
  filled?: boolean
  secondary?: boolean
  disabled?: boolean
  min?: number
  max?: number
  step?: number | string
  allowNegative?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  modelValue: null,
  unit: 'px',
  units: () => ['px', 'rem', 'em', '%'],
  placeholder: '',
  iconPrepend: '',
  iconPrependCategory: 'global',
  iconPrependType: 'outline',
  iconColor: 'var(--map-base-dusk-symbol-2)',
  filled: false,
  secondary: true,
  disabled: false,
  min: undefined,
  max: undefined,
  step: 1,
  allowNegative: false,
})
// Expose iconColor for template
const iconColor = computed(() => props.iconColor)

const emit = defineEmits(['update:modelValue', 'update:unit'])

const slots = useSlots()
const inputRef = ref<HTMLInputElement | null>(null)
const unitSelectRef = ref<HTMLElement | null>(null)

const modelValueToString = (val: number | string | null | undefined): string => {
  if (val === null || val === undefined || val === '') return ''
  return String(val)
}

const localValue = ref<string>(modelValueToString(props.modelValue))
const localUnit = ref<string>(props.unit)
const isFocused = ref(false)
const showUnitDropdown = ref(false)

const hasUnitSelect = computed(() => props.units && props.units.length > 0)

const dropdownPos = ref({ top: 0, right: 0 })

const dropdownStyle = computed<CSSProperties>(() => ({
  position: 'fixed',
  top: `${dropdownPos.value.top}px`,
  right: `${dropdownPos.value.right}px`,
  left: 'auto',
  zIndex: 9999,
}))

const updateDropdownPos = () => {
  if (!unitSelectRef.value) return
  const rect = unitSelectRef.value.getBoundingClientRect()
  dropdownPos.value = {
    top: rect.bottom + 4,
    right: window.innerWidth - rect.right,
  }
}

// Watch for external modelValue changes (e.g. syncWithStore) but don't
// overwrite when the user is actively editing to avoid cursor jumps.
watch(
  () => props.modelValue,
  (newValue) => {
    const newStr = modelValueToString(newValue)
    // Only update if the external value actually differs from what we'd parse
    // from the current local string, so mid-typing states are preserved.
    const parsed = localValue.value !== '' ? Number(localValue.value) : null
    const incoming =
      newValue === null || newValue === undefined || newValue === '' ? null : Number(newValue)
    if (parsed !== incoming) {
      localValue.value = newStr
    }
  },
)

// Watch for external unit changes
watch(
  () => props.unit,
  (newValue) => {
    localUnit.value = newValue
  },
)

const onInput = (event: Event) => {
  const raw = (event.target as HTMLInputElement).value

  // Allow only digits, an optional leading minus (if allowNegative) and one decimal point.
  const allowedPattern = props.allowNegative ? /[^0-9.-]/g : /[^0-9.]/g
  // Strip disallowed characters
  let sanitised = raw.replace(allowedPattern, '')
  // Ensure only one decimal point
  const firstDot = sanitised.indexOf('.')
  if (firstDot !== -1) {
    sanitised = sanitised.slice(0, firstDot + 1) + sanitised.slice(firstDot + 1).replace('.', '')
  }
  // Ensure minus only at the start
  if (props.allowNegative) {
    const minusCount = (sanitised.match(/-/g) || []).length
    if (minusCount > 1 || sanitised.indexOf('-') > 0) {
      sanitised = sanitised.replace(/-/g, '')
      if (raw.startsWith('-')) sanitised = '-' + sanitised
    }
  }

  // Update the DOM input if we stripped characters
  if ((event.target as HTMLInputElement).value !== sanitised) {
    ;(event.target as HTMLInputElement).value = sanitised
  }

  localValue.value = sanitised

  // Emit: empty string → null, valid number → number, otherwise don't emit yet
  if (sanitised === '' || sanitised === '-' || sanitised === '.') {
    emit('update:modelValue', sanitised === '' ? null : sanitised)
  } else {
    const num = Number(sanitised)
    if (!isNaN(num)) {
      // Clamp to min/max if defined
      const clamped =
        props.min !== undefined && num < props.min
          ? props.min
          : props.max !== undefined && num > props.max
            ? props.max
            : num
      // Only clamp the emit value, not the display value, to allow typing freely
      emit('update:modelValue', clamped)
    }
  }
}

// Unit selection handlers
const toggleUnitDropdown = () => {
  if (!props.disabled) {
    if (!showUnitDropdown.value) {
      updateDropdownPos()
    }
    showUnitDropdown.value = !showUnitDropdown.value
  }
}

const selectUnit = (unit: string) => {
  localUnit.value = unit
  emit('update:unit', unit)
  showUnitDropdown.value = false
}

// Close dropdown when clicking outside
const handleClickOutside = (event: MouseEvent) => {
  const target = event.target as HTMLElement
  if (
    showUnitDropdown.value &&
    !target.closest('.ivyforms-mixed-input__unit-select') &&
    !target.closest('.ivyforms-mixed-input__unit-dropdown')
  ) {
    showUnitDropdown.value = false
  }
}

onMounted(() => {
  document.addEventListener('click', handleClickOutside)
})

onUnmounted(() => {
  document.removeEventListener('click', handleClickOutside)
})

const focus = () => {
  inputRef.value?.focus()
}

defineExpose({
  focus,
})
</script>

<style scoped lang="scss">
.ivyforms-mixed-input {
  display: inline-flex;
  height: 40px;
  border: 1px solid var(--map-base-dusk-stroke--2);
  background: var(--map-base-dusk-o05);
  transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
  position: relative;
  box-sizing: border-box;
  box-shadow: none !important;
  outline: none !important;

  &:hover:not(.is-disabled) {
    border-color: var(--map-base-dusk-stroke-0);
    background: var(--map-base-dusk-o05);
  }

  &:focus-within:not(.is-disabled) {
    border-color: var(--map-base-brand-stroke-0);
    background: var(--map-base-dusk-o05);
    box-shadow: none !important;
  }

  &.is-disabled {
    background: var(--map-base-dusk-o10);
    cursor: not-allowed;

    .ivyforms-mixed-input__field,
    .ivyforms-mixed-input__unit-select {
      cursor: not-allowed;
      pointer-events: none;
    }
  }

  &.is-secondary:focus-within:not(.is-disabled) {
    border-color: var(--map-base-purple-stroke-0);
    box-shadow: none !important;
  }

  // Input section
  &__input {
    min-width: 0;
  }

  &__field {
    border: none !important;
    background: transparent !important;
    color: var(--map-base-text-0);
    outline: none !important;
    box-shadow: none !important;

    &::placeholder {
      color: var(--map-base-text--2);
    }

    &:disabled {
      cursor: not-allowed;
      color: var(--map-base-text--2);
    }
  }

  // Append section (unit select)
  &__unit-select {
    cursor: pointer;
    position: relative;
    user-select: none;
    transition: background-color 0.2s ease;

    &:hover {
      background: var(--map-base-dusk-o10);
    }
  }

  &__unit-text {
    color: var(--map-base-text--1);
    min-width: 20px;
  }

  // Unit dropdown
  // Filled variant
  &.is-filled {
    background: var(--map-base-dusk-o10);
  }
}

// Error and warning states (for use in form item context)
.el-form-item {
  &.is-error {
    .ivyforms-mixed-input {
      border-color: var(--map-status-error-symbol-0) !important;
      box-shadow: 0 0 0 1px var(--map-status-error-symbol-0) inset !important;
    }
  }

  &.is-warning {
    .ivyforms-mixed-input {
      border-color: var(--map-status-warning-stroke-0) !important;
      box-shadow: 0 0 0 1px var(--map-status-warning-stroke-0) inset !important;
    }
  }
}
</style>

<!-- Global styles for the teleported unit dropdown (cannot be scoped) -->
<style lang="scss">
.ivyforms-mixed-input__unit-dropdown {
  min-width: 64px;
  overflow: hidden;
  border-radius: 8px;
  padding: 4px 0;
  background: var(--map-ground-level-3);
  border: 1px solid var(--map-base-dusk-stroke--2);
  box-shadow:
    0px 4px 15px 0px rgba(0, 0, 0, 0.05),
    0px 2px 6px 0px rgba(0, 0, 0, 0.03);
}

.ivyforms-mixed-input__unit-option {
  color: var(--map-base-text-0);
  transition: background-color 0.15s ease;

  &:hover {
    background-color: var(--map-base-dusk-o05);
  }

  &.is-selected {
    background-color: var(--map-base-brand-o10);
    color: var(--map-base-brand-symbol-0);
  }
}

.ivyforms-mixed-input__unit-dropdown[data-secondary] {
  .ivyforms-mixed-input__unit-option.is-selected {
    background-color: var(--map-base-purple-o10);
    color: var(--map-base-purple-symbol-0);
  }
}
</style>
