<template>
  <div>
    <component
      :is="rendererComponent"
      :options="displayOptions"
      :isSelected="isSelected"
      :handleSelect="handleSelect"
      :getStateClasses="getStateClasses"
      :getOptionAriaAttributes="getOptionAriaAttributes"
      :label="label"
      :disabled="disabled"
      :error="error"
      :errorMessage="errorMessage"
      :showCheckmark="showCheckmark"
      :multiple="multiple"
      :fullWidth="fullWidth"
    >
      <!-- @ts-expect-error - TypeScript has limitations with template v-for type inference -->
      <template v-for="slotName in (Object.keys($slots) as string[])" :key="slotName" #[slotName]="slotData">
        <slot :name="slotName" v-bind="slotData" />
      </template>
    </component>

    <TitanSelectCustomInput
      v-if="showCustomInput"
      v-model="customValue"
      :variant="effectiveCustomInputVariant"
      :mode="customBehavior"
      placeholder="Enter custom value..."
      @submit="handleCustomSubmit"
      @cancel="handleCustomCancel"
    />
  </div>
</template>

<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import type { SelectContainerProps, SelectContainerEmits, SelectOption } from '@/types/components'
import TitanInlineSelectRenderer from './TitanInlineSelectRenderer.vue'
import TitanIconSelectRenderer from './TitanIconSelectRenderer.vue'
import TitanStackedSelectRenderer from './TitanStackedSelectRenderer.vue'
import TitanSelectCustomInput from './TitanSelectCustomInput.vue'

type Props = SelectContainerProps
type Emits = SelectContainerEmits

const props = withDefaults(defineProps<Props>(), {
  multiple: false,
  allowCustom: false,
  customInputVariant: 'input',
  disabled: false,
  required: false,
  error: false,
  showCheckmark: false,
  fullWidth: false
})

const emit = defineEmits<Emits>()

// Custom value state
const showCustomInput = ref(false)
const customValue = ref('')
const customOptions = ref<SelectOption[]>([])

/**
 * Determines custom input behavior based on variant:
 * - 'append': (inline) custom values are added as new selectable options
 * - 'replace': (stacked) custom text replaces the trigger value in modelValue
 * - 'disabled': (icon or allowCustom=false) no custom functionality
 */
const customBehavior = computed(() => {
  if (!props.allowCustom) return 'disabled'
  if (props.variant === 'icon') return 'disabled'
  if (props.variant === 'stacked') return 'replace'
  return 'append' // inline
})

/**
 * Force textarea for stacked variant, otherwise use prop value
 */
const effectiveCustomInputVariant = computed(() => {
  if (props.variant === 'stacked') return 'textarea'
  return props.customInputVariant
})

// Normalize options
const normalizedOptions = computed(() => {
  if (!props.options || props.options.length === 0) return []

  return props.options.map(option => {
    if (typeof option === 'string') {
      return { value: option, label: option }
    }
    return option
  })
})

// Mark last option as trigger when custom behavior is enabled
const displayOptions = computed(() => {
  if (customBehavior.value === 'disabled') return normalizedOptions.value

  // Mark the last option as the custom trigger (preserves icon, description, etc.)
  const options = normalizedOptions.value.map((opt, index) => {
    if (index === normalizedOptions.value.length - 1) {
      return { ...opt, isCustomTrigger: true }
    }
    return opt
  })

  if (showCustomInput.value) {
    if (customBehavior.value === 'append') {
      // Append mode (inline): show custom options + remaining predefined options (without trigger)
      return [...options.filter(opt => !opt.isCustomTrigger), ...customOptions.value]
    } else {
      // Replace mode (stacked): keep trigger visible, textarea appears below
      return options
    }
  }

  return options
})

// Renderer component
const rendererComponent = computed(() => {
  switch (props.variant) {
    case 'icon':
      return TitanIconSelectRenderer
    case 'stacked':
      return TitanStackedSelectRenderer
    case 'inline':
    default:
      return TitanInlineSelectRenderer
  }
})

// Selection logic
const isSelected = (option: SelectOption): boolean => {
  // In replace mode (stacked), show trigger as selected when custom input is active or has custom value
  if (option.isCustomTrigger) {
    if (customBehavior.value === 'replace') {
      // Check if there's a custom value in modelValue (not in predefined options)
      const predefinedValues = normalizedOptions.value
        .filter(opt => !opt.isCustomTrigger)
        .map(opt => opt.value)

      if (props.multiple) {
        const values = Array.isArray(props.modelValue) ? props.modelValue : []
        const hasCustomValue = values.some(v => v !== '' && !predefinedValues.includes(v))
        return showCustomInput.value || hasCustomValue
      } else {
        const hasCustomValue = props.modelValue && !predefinedValues.includes(props.modelValue)
        return showCustomInput.value || hasCustomValue
      }
    }
    return false
  }

  // In single-select replace mode, regular options are not selected when custom input is active
  if (!props.multiple && customBehavior.value === 'replace' && showCustomInput.value) {
    return false
  }

  if (props.multiple) {
    const values = Array.isArray(props.modelValue) ? props.modelValue : []
    return values.includes(option.value)
  }
  return props.modelValue === option.value
}

const handleSelect = (option: SelectOption): void => {
  if (option.disabled || props.disabled) return

  // Handle custom trigger
  if (option.isCustomTrigger) {
    if (customBehavior.value === 'replace') {
      // In replace mode, clicking trigger toggles custom input visibility
      const currentlySelected = isSelected(option)

      if (currentlySelected) {
        // Deselecting: check if there's custom content
        const hasContent = customValue.value.trim() !== ''

        if (hasContent) {
          // Confirm before clearing
          const confirmed = window.confirm('You will lose the text you entered. Continue?')
          if (!confirmed) {
            return // Keep selected, do nothing
          }
        }

        // Clear custom input and hide it
        customValue.value = ''
        showCustomInput.value = false

        // Remove custom value from modelValue
        const predefinedValues = normalizedOptions.value
          .filter(opt => !opt.isCustomTrigger)
          .map(opt => opt.value)

        if (props.multiple) {
          const currentValues = Array.isArray(props.modelValue) ? [...props.modelValue] : []
          const filteredValues = currentValues.filter(v => predefinedValues.includes(v))
          emit('update:modelValue', filteredValues)
          emit('change', filteredValues)
        } else {
          emit('update:modelValue', '')
          emit('change', '')
        }
      } else {
        // Selecting: show custom input
        showCustomInput.value = true
        // Clear previous selection in single-select mode
        if (!props.multiple) {
          emit('update:modelValue', '')
        }
      }
    } else {
      // Append mode: just show custom input
      showCustomInput.value = true
    }
    return
  }

  if (props.multiple) {
    const currentValues = Array.isArray(props.modelValue) ? [...props.modelValue] : []
    const index = currentValues.indexOf(option.value)

    if (index > -1) {
      currentValues.splice(index, 1)
    } else {
      currentValues.push(option.value)
    }

    emit('update:modelValue', currentValues)
    emit('change', currentValues)
  } else {
    // Close custom input when selecting a regular option in replace mode
    if (customBehavior.value === 'replace' && showCustomInput.value) {
      showCustomInput.value = false
      customValue.value = ''
    }

    const newValue = props.modelValue === option.value ? '' : option.value
    emit('update:modelValue', newValue)
    emit('change', newValue)
  }
}

const handleCustomSubmit = () => {
  // Replace mode doesn't use submit - it syncs immediately via watcher
  if (customBehavior.value === 'replace') {
    return
  }

  // Append mode (inline): add to custom options list as selectable pill
  const trimmedValue = customValue.value.trim()
  if (!trimmedValue) return

  // Check for duplicates (case-insensitive)
  const lowerValue = trimmedValue.toLowerCase()
  const isDuplicate = normalizedOptions.value.some(
    opt => opt.value.toString().toLowerCase() === lowerValue
  ) || customOptions.value.some(
    opt => opt.value.toString().toLowerCase() === lowerValue
  )
  if (isDuplicate) {
    customValue.value = ''
    return
  }

  const newOption: SelectOption = {
    label: trimmedValue,
    value: trimmedValue
  }
  customOptions.value.push(newOption)

  if (props.multiple) {
    const currentValues = Array.isArray(props.modelValue) ? [...props.modelValue] : []
    currentValues.push(trimmedValue)
    emit('update:modelValue', currentValues)
    emit('change', currentValues)
  } else {
    emit('update:modelValue', trimmedValue)
    emit('change', trimmedValue)
  }

  customValue.value = ''
  // Don't close custom input - keep it open for more entries
}

const handleCustomCancel = () => {
  customValue.value = ''
  showCustomInput.value = false
}

const getStateClasses = (option: SelectOption) => {
  const selected = isSelected(option)
  const isStackedVariant = props.variant === 'stacked'

  const iconBaseClasses = props.fullWidth
    ? 'flex-1 min-w-[100px] h-[102px] flex flex-col items-center justify-center rounded-lg border-2'
    : 'w-[100px] h-[102px] flex flex-col items-center justify-center rounded-lg border-2'

  return {
    base: props.variant === 'icon'
      ? iconBaseClasses
      : isStackedVariant
      ? 'w-full p-4 rounded-lg border-2 text-left'
      : 'px-4 py-1.5 rounded-2xl text-sm font-medium transition-colors',
    state: selected
      ? 'bg-titan-purple-500 text-white border-titan-purple-500 shadow-sm'
      : 'bg-titan-green-800/10 text-titan-green-800 border-titan-green-800/20 hover:bg-titan-green-800/15',
    disabled: option.disabled || props.disabled ? 'opacity-50 cursor-not-allowed' : ''
  }
}

const getOptionAriaAttributes = (option: SelectOption) => ({
  role: props.multiple ? 'checkbox' : 'radio',
  'aria-checked': isSelected(option),
  'aria-disabled': option.disabled || props.disabled
})

/**
 * Reconstruct custom values state from modelValue on mount/update
 * This fixes the bug where custom values are lost on component remount
 *
 * Behavior differs by custom mode:
 * - Append mode (inline): Reconstructs custom values as selectable pills in customOptions array
 * - Replace mode (stacked): Just shows textarea if custom value exists (no pills)
 */
watch(
  () => props.modelValue,
  (newValue) => {
    if (!props.allowCustom || !newValue) {
      // Clear customValue if modelValue is empty in replace mode
      if (customBehavior.value === 'replace') {
        customValue.value = ''
      }
      return
    }

    // Get all predefined option values (excluding trigger)
    const predefinedValues = normalizedOptions.value
      .filter(opt => !opt.isCustomTrigger)
      .map(opt => opt.value)

    // Find values in modelValue that aren't in predefined options (= custom values)
    const selectedValues = Array.isArray(newValue) ? newValue : [newValue]
    const customValues = selectedValues.filter(v => v !== '' && !predefinedValues.includes(v))

    if (customValues.length > 0) {
      if (customBehavior.value === 'append') {
        // Append mode (inline): Reconstruct custom options as selectable pills
        customOptions.value = customValues.map(v => ({
          label: String(v),
          value: v
        }))
        // Activate custom input mode (hide trigger, show input)
        showCustomInput.value = true
      } else if (customBehavior.value === 'replace') {
        // Replace mode (stacked): Sync customValue with the custom text in modelValue
        // Take the first custom value (or last if multiple in multi-select mode)
        const customText = props.multiple ? customValues[customValues.length - 1] : customValues[0]
        customValue.value = String(customText)
        showCustomInput.value = true
      }
    }
  },
  { immediate: true } // Run on initial mount to handle pre-existing custom values
)

/**
 * In replace mode, sync customValue changes immediately to modelValue
 */
watch(
  customValue,
  (newCustomValue) => {
    if (customBehavior.value !== 'replace' || !showCustomInput.value) return

    const trimmedValue = newCustomValue.trim()

    // Get predefined values (excluding trigger)
    const predefinedValues = normalizedOptions.value
      .filter(opt => !opt.isCustomTrigger)
      .map(opt => opt.value)

    if (props.multiple) {
      // For multi-select: keep predefined selections, update/add custom value
      const currentValues = Array.isArray(props.modelValue) ? [...props.modelValue] : []
      const predefinedSelections = currentValues.filter(v => predefinedValues.includes(v))

      if (trimmedValue) {
        // Add custom value
        emit('update:modelValue', [...predefinedSelections, trimmedValue])
      } else {
        // No custom value, just keep predefined selections
        emit('update:modelValue', predefinedSelections)
      }
    } else {
      // For single-select: replace with custom value
      emit('update:modelValue', trimmedValue)
    }
  }
)
</script>
