<template>
  <q-dialog v-model="visible" persistent>
    <q-card class="contained-card">
      <q-card-section>
        <div class="text-h6">{{ title }}</div>

        <q-form ref="formRef" class="q-gutter-md q-mt-md">
          <q-input v-model="draft.street" :label="t('address.street')" :rules="[required()]" />

          <div v-if="!draft.foreign">
            <div class="row items-center q-gutter-sm">
              <div class="col-4">
                <q-select
                  hide-selected
                  fill-input
                  v-model="selectedSwissZip"
                  :options="swissOptions"
                  option-label="label"
                  option-value="zip"
                  :label="t('address.zip')"
                  :input-attrs="{ inputmode: 'numeric', maxlength: 4 }"
                  @input-value="onSwissZipInputValue"
                  use-input
                  input-debounce="200"
                  emit-value
                  map-options
                  :rules="[required()]"
                  @filter="filterCapFn"
                  @blur="onZipBlur"
                />
              </div>
              <div class="col">
                <q-input v-model="draft.city" :label="t('address.city')" :rules="[required()]" />
              </div>
            </div>

          </div>
          <div class="row items-center q-gutter-sm" v-else>
            <q-input class="col-4" v-model="draft.zip" :label="t('address.zip')" :rules="[required(), zipRule]" :input-attrs="{ inputmode: 'numeric', maxlength: 4 }" @input-value="onZipInputValue" />
            <q-input class="col" v-model="draft.city" :label="t('address.city')" :rules="[required()]" />
          </div>
  <q-select
            v-if="draft.foreign"
            v-model="draft.country"
            :options="countryOptions"
            option-label="label"
            option-value="value"
            :label="t('address.country')"
            use-input
            input-debounce="200"
            emit-value
            map-options
            :rules="[required()]"
          />
          <q-toggle v-if="allowForeign" v-model="draft.foreign" :label="t('address.foreign')" />

        </q-form>
      </q-card-section>

      <q-card-actions align="right">
        <q-btn flat :label="t('button.cancel')" @click="onCancel" />
        <q-btn color="primary" :label="t('button.save')" @click="onSave" />
      </q-card-actions>
    </q-card>
  </q-dialog>
</template>

<script setup lang="ts">
import { ref, reactive, watch, nextTick, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import type { Address } from '../types/address'

function filterCapFn (val: string, update: (fn: () => void) => void) {
  // allow only digits and max 4 characters when filtering
  update(() => {
    const q = String(val || '').replace(/\D/g, '').slice(0, 4)
    const hits = findPostalCodes(q)
    swissOptionsRef.value = hits.map(z => ({ zip: z, label: z }))
  })
}

import { toRefs } from 'vue'

const props = withDefaults(defineProps<{
  modelValue: boolean
  modelAddress?: Partial<Address> | null
  title?: string
  allowForeign?: boolean
}>(), { allowForeign: true })
const { allowForeign } = toRefs(props)
const emit = defineEmits<{
  (e: 'update:modelValue', v: boolean): void
  (e: 'save', payload: Address): void
  (e: 'cancel'): void
}>()

const { t, locale } = useI18n()

const visible = ref(!!props.modelValue)
watch(() => props.modelValue, v => (visible.value = !!v))
watch(visible, v => {
  emit('update:modelValue', v)
  if (v) populateFromModel()
})

function populateFromModel() {
  const src = props.modelAddress || {}
  Object.assign(draft, { ...defaultAddress(), ...src })
  if (hasCountryCode(src.country)) {
    draft.country = src.country.code || ''
  }
  // if foreign addresses are not allowed, ensure foreign is false
  if (!allowForeign.value) draft.foreign = false
  // sanitize zip and limit to 4 digits
  if (draft.zip) {
    const s = String(draft.zip || '').replace(/\D/g, '').slice(0, 4)
    draft.zip = s
    if (!draft.foreign && s.length) {
      if (ALL_CH_POSTAL_CODES.includes(s)) {
        selectedSwissZip.value = s
      }
      if (s.length >= 4) {
        const recs = findPostalCodeDetails(s)
        if (recs && recs.length) {
          const rec = recs[0]
          draft.city = rec?.placeName || ''
          draft.zip = rec?.postalCode || ''
           draft.canton = rec?.cantonCode || null
          selectedSwissZip.value = rec?.postalCode || ''
        }
      }
    }
  }
}

const defaultAddress = (): Address => ({ street: '', zip: '', city: '', country: '', canton: '', foreign: false })
// keep draft.country as string when editing; on save we'll convert to {code,name}
const draft = reactive<Address>({ ...defaultAddress(), ...(props.modelAddress || {}) })
function hasCountryCode(x: unknown): x is { code: string } {
  return typeof x === 'object' && x !== null && Object.prototype.hasOwnProperty.call(x, 'code')
}

if (props.modelAddress && hasCountryCode(props.modelAddress.country)) {
  draft.country = props.modelAddress.country.code || ''
}

const formRef = ref<{ validate?: () => Promise<boolean> | boolean; resetValidation?: () => void } | null>(null)

function required() {
  const fallback = t('validation.required') || 'Required'
  return (v: unknown) => {
    if (v === null || v === undefined) return fallback
    if (typeof v === 'string') return (v.trim() !== '') || fallback
    return true
  }
}

const zipRule = (v: unknown) => {
  if (v === null || v === undefined || v === '') return t('validation.required') || 'Required'
  if (typeof v !== 'string' && typeof v !== 'number') return t('validation.invalidZip') || 'Invalid ZIP'
  const s = String(v).trim()
  if (/^\d{1,4}$/.test(s)) return true
  return t('validation.invalidZip') || 'Invalid ZIP'
}

watch(() => props.modelAddress, (n) => {
  const src = n as Partial<Address> | undefined
  Object.assign(draft, { ...defaultAddress(), ...(src || {}) })
  if (hasCountryCode(src?.country)) {
    draft.country = src?.country?.code || ''
  }
  if (src && Object.prototype.hasOwnProperty.call(src, 'canton')) {
    draft.canton = src.canton || ''
  }
  void nextTick(() => formRef.value?.resetValidation?.())
  // ensure selections are populated from the new model
  populateFromModel()
}, { deep: true })

// if allowForeign changes to false, force draft.foreign = false
watch(allowForeign, (v) => {
  if (!v) draft.foreign = false
})

async function onSave() {
  try {
    const ok = await (formRef.value?.validate?.() ?? true)
    if (ok === false) return
  } catch {
    return
  }
  // build saved address: include localized country name + code when foreign
  const out: Address = { ...draft }
  if (draft.foreign) {
    const code = typeof draft.country === 'string' ? draft.country : ''
    const label = countryOptions.value.find(o => o.value === code)?.label || code
    out.country = code ? { code, name: label } : ''
    out.canton = ''
  } else {
    // non-foreign: explicitly set Switzerland as country ref and persist canton
    try {
      const dn = new Intl.DisplayNames([String(locale.value)], { type: 'region' })
      out.country = { code: 'CH', name: dn.of('CH') || 'Switzerland' }
    } catch {
      out.country = { code: 'CH', name: 'Switzerland' }
    }
    out.canton = draft.canton || ''
  }
  emit('save', out)
  visible.value = false
}

function onCancel() {
  emit('cancel')
  visible.value = false
}

import { COUNTRY_CODES, findPostalCodes, findPostalCodeDetails, ALL_CH_POSTAL_CODES } from '../data/countryCodes'

const countryOptions = computed(() => {
  try {
    // Intl.DisplayNames with type 'region' localizes country names
    const dn = new Intl.DisplayNames([String(locale.value)], { type: 'region' })
    return COUNTRY_CODES.map(code => ({ value: code, label: dn.of(code) || code }))
  } catch {
    return COUNTRY_CODES.map(code => ({ value: code, label: code }))
  }
})

// SWISS_CITIES now imported from src/data/countryCodes

const swissOptions = computed(() => swissOptionsRef.value)
// Options for the ZIP select are only the list of postal codes (no city labels)
const swissOptionsRef = ref(ALL_CH_POSTAL_CODES.map(z => ({ zip: z, label: z })))

const selectedSwissZip = ref<string | null>(null)

function onZipInputValue (val: string) {
  // sanitize input to digits only and max length 4
  const s = String(val || '').replace(/\D/g, '').slice(0, 4)
  if (draft.zip !== s) draft.zip = s
}

function onSwissZipInputValue (val: string) {
  // sanitize incoming input from the q-select text field
  const s = String(val || '').replace(/\D/g, '').slice(0, 4)
  // update options shown
  swissOptionsRef.value = findPostalCodes(s).map(z => ({ zip: z, label: z }))
  // keep draft.zip in sync so watcher can fill city when 4 digits reached
  if (!draft.foreign) {
    if (draft.zip !== s) draft.zip = s
    if (s.length >= 4) {
      const recs = findPostalCodeDetails(s)
        if (recs && recs.length) {
          const rec = recs[0]
          draft.zip = rec?.postalCode || ""
          draft.city = rec?.placeName || ""
          draft.canton = rec?.cantonCode || ''
          selectedSwissZip.value = rec?.postalCode || ""
        }
    }
  }
}

// Keep draft.zip in sync with selection; populate city on blur
watch(selectedSwissZip, (z) => {
  if (!z) return
  const rec = findPostalCodeDetails(z)[0]
  if (rec) {
    draft.zip = rec.postalCode
    // populate city and canton only on blur to avoid aggressive overriding
    draft.canton = rec.cantonCode || ''
  }
})

function onZipBlur() {
  const z = selectedSwissZip.value
  if (!z) return
  const recs = findPostalCodeDetails(z)
    if (recs && recs.length) {
    const rec = recs[0]
    draft.zip = rec?.postalCode || ""
    draft.city = rec?.placeName || ""
    draft.canton = rec?.cantonCode || ''
  }
}

// initialize selectedSwissZip from draft.zip when appropriate
// initialize selectedSwissZip from draft.zip when appropriate
watch(() => draft.zip, (z) => {
  if (!draft.foreign && z) {
    // sanitize to digits and max 4
    const s = String(z || '').replace(/\D/g, '').slice(0, 4)
    if (s !== z) {
      draft.zip = s
      return
    }
    if (ALL_CH_POSTAL_CODES.includes(s)) selectedSwissZip.value = s
    // when the user types a full 4-digit ZIP, auto-fill city from dataset
    if (s.length >= 4) {
      const recs = findPostalCodeDetails(s)
      if (recs && recs.length) {
        const rec = recs[0]
        draft.city = rec?.placeName || ''
        draft.zip = rec?.postalCode || s
        selectedSwissZip.value = rec?.postalCode || s
      }
    }
  }
})

const title = props.title || t('address.modalTitle') || t('address.title') || 'Address'

// If the modal is already open or modelAddress already provided at mount,
// ensure the form is populated once.
void nextTick(() => {
  if (props.modelValue || props.modelAddress) populateFromModel()
})
</script>

<style scoped>
.contained-card { min-width: 420px; max-width: 720px }
</style>
