<template>
  <div class="v4-field-renderer">
    <label
      v-for="field in fields"
      :key="fieldKey(field)"
      class="v4-field"
      :class="{ 'v4-field--readonly': !isEditable(field) }"
    >
      <span class="v4-field__label">
        {{ field.display_name }}
        <sup v-if="field.required">*</sup>
        <em v-if="!isEditable(field)">{{ t('taskV4.field.readonly') }}</em>
      </span>

      <select
        v-if="isEditable(field) && isSelectField(field)"
        class="v4-field__control"
        :name="fieldKey(field)"
        :aria-invalid="fieldError(field) ? 'true' : 'false'"
        :disabled="selectDisabled(field)"
        :value="fieldValue(field)"
        @change="updateField(field, $event)"
      >
        <option value="">{{ t('taskV4.field.selectPlaceholder') }}</option>
        <option
          v-for="option in fieldOptions(field)"
          :key="option.value"
          :value="option.value"
        >
          {{ option.label }}
        </option>
      </select>

      <input
        v-else-if="isEditable(field) && isDateField(field)"
        class="v4-field__control"
        type="date"
        :name="fieldKey(field)"
        :aria-invalid="fieldError(field) ? 'true' : 'false'"
        :value="fieldValue(field)"
        @input="updateField(field, $event)"
      >

      <input
        v-else-if="isEditable(field) && isCheckboxField(field)"
        class="v4-field__control v4-field__control--checkbox"
        type="checkbox"
        :name="fieldKey(field)"
        :aria-invalid="fieldError(field) ? 'true' : 'false'"
        :checked="checkboxValue(field)"
        @change="updateCheckboxField(field, $event)"
      >

      <input
        v-else-if="isEditable(field) && isNumberField(field)"
        class="v4-field__control"
        type="number"
        :name="fieldKey(field)"
        :aria-invalid="fieldError(field) ? 'true' : 'false'"
        :value="fieldValue(field)"
        @input="updateField(field, $event)"
      >

      <textarea
        v-else-if="isEditable(field) && isTextAreaField(field)"
        class="v4-field__control v4-field__control--textarea"
        :name="fieldKey(field)"
        :aria-invalid="fieldError(field) ? 'true' : 'false'"
        :value="fieldValue(field)"
        rows="3"
        @input="updateField(field, $event)"
      />

      <input
        v-else-if="isEditable(field)"
        class="v4-field__control"
        type="text"
        :name="fieldKey(field)"
        :aria-invalid="fieldError(field) ? 'true' : 'false'"
        :value="fieldValue(field)"
        @input="updateField(field, $event)"
      >

      <input
        v-else-if="isCheckboxField(field)"
        class="v4-field__control v4-field__control--checkbox"
        type="checkbox"
        :name="fieldKey(field)"
        :checked="checkboxValue(field)"
        disabled
      >

      <span
        v-else
        class="v4-field__static"
      >
        {{ staticFieldValue(field) }}
      </span>

      <small
        v-if="field.control_hint"
        class="v4-field__hint"
      >{{ field.control_hint }}</small>
      <small
        v-for="hint in lookupHints(field)"
        :key="hint.key"
        class="v4-field__hint"
        :class="`v4-field__hint--${hint.tone}`"
      >{{ hint.message }}</small>
      <small
        v-for="error in visibleFieldErrors(field)"
        :key="error"
        class="v4-field__error"
      >{{ error }}</small>
    </label>
  </div>
</template>

<script setup lang="ts">
import { computed, reactive, watch } from 'vue'
import { useI18n } from 'vue-i18n'

import {
  fetchReservationV4AccountLookups,
  fetchReservationV4RateCodeLookups,
  fetchReservationV4RoomTypeLookups,
} from '@/services/reservationService'
import type {
  ReservationRecord,
  ReservationV4CatalogLookupItem,
  ReservationV4CatalogLookupResult,
  ReservationV4TaskCardFieldResult,
} from '@/types/reservation'
import {
  isReservationV4EditableField,
  readV4FieldValue,
  reservationV4LookupKindForOptionsSource,
  reservationV4FieldKey,
  stringifyReservationV4SafeDisplayValue,
  type ReservationV4LookupKind,
} from '@/utils/reservationV4FieldRules'

const props = defineProps<{
  fields: ReservationV4TaskCardFieldResult[]
  modelValue: ReservationRecord
  readOnly: boolean
  validationErrors?: Record<string, string>
  editableKeys?: string[]
  hotelId?: string
}>()

const emit = defineEmits<{
  'update:modelValue': [value: ReservationRecord]
}>()

const { t } = useI18n()
type FieldOption = {
  value: string
  label: string
  item?: ReservationV4CatalogLookupItem
}
type LookupHintTone = 'normal' | 'warning' | 'error'
type LookupHint = {
  key: string
  message: string
  tone: LookupHintTone
}
type LookupState = {
  loading: boolean
  error: string
  result: ReservationV4CatalogLookupResult | null
}
type ExactLookupState = {
  loading: boolean
  searched: boolean
  item: ReservationV4CatalogLookupItem | null
}

const invalidLookupValues = reactive<Record<string, string>>({})
const exactLookupStates = reactive<Record<string, ExactLookupState>>({})
const lookupStates = reactive<Record<ReservationV4LookupKind, LookupState>>({
  ACCOUNT: createLookupState(),
  ROOM_TYPE: createLookupState(),
  RATE_CODE: createLookupState(),
})
const lookupRequestVersions: Record<ReservationV4LookupKind, number> = {
  ACCOUNT: 0,
  ROOM_TYPE: 0,
  RATE_CODE: 0,
}
const lookupKinds = computed(() => {
  const kinds = props.fields
    .filter((field) => isSelectControl(field) && isEditable(field))
    .map((field) => reservationV4LookupKindForOptionsSource(field.options_source))
    .filter((kind): kind is ReservationV4LookupKind => Boolean(kind))
  return [...new Set(kinds)]
})
const lookupKindKey = computed(() => lookupKinds.value.join('|'))

watch(
  [lookupKindKey, () => props.hotelId],
  () => {
    resetLookupStates()
    lookupKinds.value.forEach((kind) => {
      void loadLookup(kind)
    })
  },
  { immediate: true },
)

watch(
  [
    () => props.modelValue,
    () => lookupStates.ACCOUNT.result,
    () => lookupStates.ROOM_TYPE.result,
    () => lookupStates.RATE_CODE.result,
  ],
  () => {
    clearInvalidLoadedCatalogValues()
  },
  { deep: true },
)

function fieldKey(field: ReservationV4TaskCardFieldResult): string {
  return reservationV4FieldKey(field)
}

function isEditable(field: ReservationV4TaskCardFieldResult): boolean {
  const key = reservationV4FieldKey(field)
  return !props.readOnly &&
    (!props.editableKeys || props.editableKeys.includes(key)) &&
    isReservationV4EditableField(field)
}

function isSelectField(field: ReservationV4TaskCardFieldResult): boolean {
  return isSelectControl(field) &&
    (Boolean(reservationV4LookupKindForOptionsSource(field.options_source)) || fixedFieldOptions(field).length > 0)
}

function isDateField(field: ReservationV4TaskCardFieldResult): boolean {
  return normalizeControlType(field.control_type) === 'DATE'
}

function isCheckboxField(field: ReservationV4TaskCardFieldResult): boolean {
  return ['CHECKBOX', 'BOOLEAN'].includes(normalizeControlType(field.control_type))
}

function isNumberField(field: ReservationV4TaskCardFieldResult): boolean {
  return normalizeControlType(field.control_type) === 'NUMBER'
}

function isTextAreaField(field: ReservationV4TaskCardFieldResult): boolean {
  return normalizeControlType(field.control_type) === 'TEXTAREA'
}

function fieldOptions(field: ReservationV4TaskCardFieldResult): FieldOption[] {
  const kind = reservationV4LookupKindForOptionsSource(field.options_source)
  const options = kind
    ? (lookupStates[kind].result?.items ?? []).map((item) => ({
      value: item.code,
      label: lookupItemLabel(item),
      item,
    }))
    : fixedFieldOptions(field)
  const exactItem = exactLookupItemForField(field)
  if (exactItem && !options.some((option) => option.value === exactItem.code)) {
    options.push({
      value: exactItem.code,
      label: lookupItemLabel(exactItem),
      item: exactItem,
    })
  }
  return options
}

function selectDisabled(field: ReservationV4TaskCardFieldResult): boolean {
  const kind = reservationV4LookupKindForOptionsSource(field.options_source)
  if (!kind) {
    return false
  }
  const state = lookupStates[kind]
  return state.loading || Boolean(state.error)
}

function fieldValue(field: ReservationV4TaskCardFieldResult): string | number {
  const value = readV4FieldValue(field, props.modelValue)
  if (!isEditable(field)) {
    return stringifyV4Value(value)
  }
  if (typeof value === 'number') {
    return value
  }
  if (typeof value === 'string') {
    return value
  }
  return stringifyV4Value(value)
}

function staticFieldValue(field: ReservationV4TaskCardFieldResult): string {
  const value = fieldValue(field)
  const matchedOption = fieldOptions(field).find((option) => option.value === value)
  return matchedOption?.label ?? stringifyV4Value(value)
}

function checkboxValue(field: ReservationV4TaskCardFieldResult): boolean {
  const value = readV4FieldValue(field, props.modelValue)
  return value === true || value === 'true' || value === 1 || value === '1'
}

function updateField(field: ReservationV4TaskCardFieldResult, event: Event): void {
  const target = event.target as HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
  delete invalidLookupValues[reservationV4FieldKey(field)]
  emit('update:modelValue', {
    ...props.modelValue,
    [reservationV4FieldKey(field)]: target.value,
  })
}

function updateCheckboxField(field: ReservationV4TaskCardFieldResult, event: Event): void {
  const target = event.target as HTMLInputElement
  delete invalidLookupValues[reservationV4FieldKey(field)]
  emit('update:modelValue', {
    ...props.modelValue,
    [reservationV4FieldKey(field)]: target.checked,
  })
}

function lookupHints(field: ReservationV4TaskCardFieldResult): LookupHint[] {
  const kind = reservationV4LookupKindForOptionsSource(field.options_source)
  if (!kind) {
    return []
  }
  const state = lookupStates[kind]
  if (state.loading) {
    return [{
      key: `${kind}-loading`,
      message: t('taskV4.lookup.loading'),
      tone: 'normal',
    }]
  }
  if (state.error) {
    return [{
      key: `${kind}-error`,
      message: state.error,
      tone: 'error',
    }]
  }
  const result = state.result
  if (!result) {
    return []
  }

  const hints: LookupHint[] = []
  if (isExactLookupLoading(field)) {
    hints.push({
      key: `${kind}-exact-loading`,
      message: t('taskV4.lookup.loading'),
      tone: 'normal',
    })
  }
  const selectedItem = selectedLookupItem(field)
  if (kind === 'ACCOUNT' && selectedItem && (selectedItem.market_code || selectedItem.source_code)) {
    hints.push({
      key: `${kind}-derived`,
      message: t('taskV4.lookup.accountDerived', {
        market: selectedItem.market_code ?? '-',
        source: selectedItem.source_code ?? '-',
      }),
      tone: 'normal',
    })
  }
  if (result.stale) {
    hints.push({
      key: `${kind}-stale`,
      message: t('taskV4.lookup.stale'),
      tone: 'warning',
    })
  }
  result.warnings.forEach((warning, index) => {
    hints.push({
      key: `${kind}-warning-${index}`,
      message: t('taskV4.lookup.warning', { warning }),
      tone: 'warning',
    })
  })
  if (!result.items.length) {
    hints.push({
      key: `${kind}-empty`,
      message: t('taskV4.lookup.empty'),
      tone: 'warning',
    })
  }
  if (result.catalog_source || result.catalog_version) {
    hints.push({
      key: `${kind}-meta`,
      message: t('taskV4.lookup.catalogMeta', {
        source: result.catalog_source ?? '-',
        version: result.catalog_version ?? '-',
      }),
      tone: 'normal',
    })
  }
  const invalidValue = invalidLookupValues[reservationV4FieldKey(field)]
  if (invalidValue) {
    hints.push({
      key: `${kind}-invalid-current`,
      message: t('taskV4.lookup.currentNotInCatalog', { value: invalidValue }),
      tone: 'error',
    })
  }
  return hints
}

function visibleFieldErrors(field: ReservationV4TaskCardFieldResult): string[] {
  return [
    ...(field.validation_errors ?? []),
    fieldError(field),
  ].filter(Boolean)
}

function fieldError(field: ReservationV4TaskCardFieldResult): string {
  return props.validationErrors?.[reservationV4FieldKey(field)] ?? ''
}

function stringifyV4Value(value: unknown): string {
  return stringifyReservationV4SafeDisplayValue(
    value,
    t('taskV4.field.empty'),
    t('taskV4.field.hidden'),
  )
}

function normalizeControlType(value: string | null): string {
  return value?.trim().replace(/[\s-]+/g, '_').toUpperCase() ?? ''
}

function isSelectControl(field: ReservationV4TaskCardFieldResult): boolean {
  return ['SELECT', 'LOOKUP'].includes(normalizeControlType(field.control_type))
}

function fixedFieldOptions(field: ReservationV4TaskCardFieldResult): FieldOption[] {
  const optionsSource = normalizeControlType(field.options_source)
  if (optionsSource !== 'RESERVATION_V4_GROUP_BOOKING_STATUS_FIXED') {
    return []
  }
  return [
    {
      value: 'TEN',
      label: 'TEN-Tentative',
    },
    {
      value: 'DEF',
      label: 'DEF-Definite',
    },
    {
      value: 'INQ',
      label: 'INQ-Inquiry',
    },
  ]
}

function lookupItemLabel(item: ReservationV4CatalogLookupItem): string {
  if (item.display_name && item.display_name !== item.code) {
    return `${item.code} - ${item.display_name}`
  }
  return item.code
}

function selectedLookupItem(field: ReservationV4TaskCardFieldResult): ReservationV4CatalogLookupItem | null {
  const kind = reservationV4LookupKindForOptionsSource(field.options_source)
  const value = String(readV4FieldValue(field, props.modelValue) || '')
  if (!kind || !value) {
    return null
  }
  return lookupStates[kind].result?.items.find((item) => item.code === value)
    ?? exactLookupStates[exactLookupKey(kind, value)]?.item
    ?? null
}

function createLookupState(): LookupState {
  return {
    loading: false,
    error: '',
    result: null,
  }
}

function resetLookupStates(): void {
  const allKinds: ReservationV4LookupKind[] = ['ACCOUNT', 'ROOM_TYPE', 'RATE_CODE']
  allKinds.forEach((kind) => {
    lookupRequestVersions[kind] += 1
    lookupStates[kind].loading = false
    lookupStates[kind].error = ''
    lookupStates[kind].result = null
  })
  Object.keys(invalidLookupValues).forEach((key) => {
    delete invalidLookupValues[key]
  })
  Object.keys(exactLookupStates).forEach((key) => {
    delete exactLookupStates[key]
  })
}

async function loadLookup(kind: ReservationV4LookupKind): Promise<void> {
  const state = lookupStates[kind]
  const requestVersion = ++lookupRequestVersions[kind]
  state.loading = true
  state.error = ''
  try {
    const result = await lookupFetcher(kind)({
      ...(props.hotelId ? { hotel_id: props.hotelId } : {}),
      page_num: 1,
      page_size: 100,
    })
    if (requestVersion === lookupRequestVersions[kind]) {
      state.result = {
        ...result,
        items: result.items ?? [],
        warnings: result.warnings ?? [],
      }
    }
  } catch {
    if (requestVersion === lookupRequestVersions[kind]) {
      state.error = t('taskV4.lookup.error')
    }
  } finally {
    if (requestVersion === lookupRequestVersions[kind]) {
      state.loading = false
    }
  }
}

function lookupFetcher(kind: ReservationV4LookupKind) {
  switch (kind) {
    case 'ACCOUNT':
      return fetchReservationV4AccountLookups
    case 'ROOM_TYPE':
      return fetchReservationV4RoomTypeLookups
    case 'RATE_CODE':
      return fetchReservationV4RateCodeLookups
  }
}

function clearInvalidLoadedCatalogValues(): void {
  const nextValues = { ...props.modelValue }
  let changed = false
  props.fields.forEach((field) => {
    if (!isSelectField(field) || !isEditable(field)) {
      return
    }
    const kind = reservationV4LookupKindForOptionsSource(field.options_source)
    const result = kind ? lookupStates[kind].result : null
    if (!kind || !result) {
      return
    }

    const key = reservationV4FieldKey(field)
    const currentValue = String(readV4FieldValue(field, nextValues) || '')
    if (!currentValue) {
      return
    }
    if (result.items.some((item) => item.code === currentValue)) {
      delete invalidLookupValues[key]
      return
    }
    if (!isFullyLoadedCatalog(result)) {
      const exactState = exactLookupStates[exactLookupKey(kind, currentValue)]
      if (!exactState) {
        void loadExactLookup(kind, currentValue)
        return
      }
      if (exactState.loading || (exactState.searched && exactState.item)) {
        delete invalidLookupValues[key]
        return
      }
    }
    clearInvalidCatalogValue(field, currentValue, nextValues)
    changed = true
  })
  if (changed) {
    emit('update:modelValue', nextValues)
  }
}

function isFullyLoadedCatalog(result: ReservationV4CatalogLookupResult): boolean {
  return result.items.length >= result.page.total
}

async function loadExactLookup(kind: ReservationV4LookupKind, code: string): Promise<void> {
  const key = exactLookupKey(kind, code)
  exactLookupStates[key] = {
    loading: true,
    searched: false,
    item: null,
  }
  try {
    const result = await lookupFetcher(kind)({
      ...(props.hotelId ? { hotel_id: props.hotelId } : {}),
      keyword: code,
      page_num: 1,
      page_size: 20,
    })
    exactLookupStates[key] = {
      loading: false,
      searched: true,
      item: (result.items ?? []).find((item) => item.code === code) ?? null,
    }
  } catch {
    exactLookupStates[key] = {
      loading: false,
      searched: true,
      item: null,
    }
  } finally {
    clearInvalidLoadedCatalogValues()
  }
}

function clearInvalidCatalogValue(
  field: ReservationV4TaskCardFieldResult,
  currentValue: string,
  nextValues: ReservationRecord,
): void {
  const key = reservationV4FieldKey(field)
  invalidLookupValues[key] = currentValue
  nextValues[key] = ''
}

function exactLookupItemForField(field: ReservationV4TaskCardFieldResult): ReservationV4CatalogLookupItem | null {
  const kind = reservationV4LookupKindForOptionsSource(field.options_source)
  const value = String(readV4FieldValue(field, props.modelValue) || '')
  if (!kind || !value) {
    return null
  }
  return exactLookupStates[exactLookupKey(kind, value)]?.item ?? null
}

function isExactLookupLoading(field: ReservationV4TaskCardFieldResult): boolean {
  const kind = reservationV4LookupKindForOptionsSource(field.options_source)
  const value = String(readV4FieldValue(field, props.modelValue) || '')
  if (!kind || !value) {
    return false
  }
  return exactLookupStates[exactLookupKey(kind, value)]?.loading ?? false
}

function exactLookupKey(kind: ReservationV4LookupKind, code: string): string {
  return `${props.hotelId ?? ''}:${kind}:${code}`
}
</script>

<style scoped>
.v4-field-renderer {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
}

.v4-field {
  display: grid;
  gap: 7px;
}

.v4-field__label {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--th-color-slate-700);
  font-size: 12px;
  font-weight: 800;
}

.v4-field__label sup {
  color: var(--th-color-danger);
}

.v4-field__label em {
  border-radius: 999px;
  background: var(--th-color-slate-100);
  color: var(--th-color-slate-500);
  font-size: 11px;
  font-style: normal;
  padding: 2px 6px;
}

.v4-field__control,
.v4-field__static {
  min-height: 38px;
  width: 100%;
  border: 1px solid var(--th-color-slate-200);
  border-radius: var(--th-radius-sm);
  background: var(--th-color-white);
  color: var(--th-color-slate-900);
  font: inherit;
  padding: 9px 10px;
}

.v4-field__control--textarea {
  resize: vertical;
}

.v4-field__control--checkbox {
  min-height: 18px;
  width: 18px;
  accent-color: var(--th-color-blue-600);
  box-shadow: none;
  padding: 0;
}

.v4-field__control[aria-invalid='true'] {
  border-color: var(--th-color-danger);
  box-shadow: 0 0 0 3px var(--th-color-danger-bg);
}

.v4-field__static {
  display: block;
  min-height: 38px;
  background: var(--th-color-slate-50);
  overflow-wrap: anywhere;
}

.v4-field__hint {
  color: var(--th-color-slate-500);
  font-size: 12px;
}

.v4-field__hint--warning {
  color: var(--th-color-warning);
  font-weight: 700;
}

.v4-field__hint--error {
  color: var(--th-color-danger);
  font-weight: 700;
}

.v4-field__error {
  color: var(--th-color-danger);
  font-size: 12px;
  font-weight: 700;
}

@media (max-width: 760px) {
  .v4-field-renderer {
    grid-template-columns: 1fr;
  }
}
</style>
