<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.name" :label="t('children.school')" :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="selectedSwissCap"
                  :options="swissOptions"
                  option-label="label"
                  option-value="cap"
                  :label="t('address.zip')"
                  :input-attrs="{ inputmode: 'numeric', maxlength: 4 }"
                  @input-value="onSwissCapInputValue"
                  use-input
                  input-debounce="200"
                  emit-value
                  map-options
                  :rules="[required()]"
                  @filter="filterCapFn"
                  @blur="onCapBlur"
                />
              </div>
              <div class="col">
                <q-input v-model="draft.city" :label="t('address.city')" :rules="[required()]" />
              </div>
            </div>
          </div>

          <div v-else>
            <div class="row items-center q-gutter-sm">
              <q-input
                class="col-4"
                v-model="draft.cap"
                :label="t('address.zip')"
                :rules="[required(), capRule]"
                :input-attrs="{ inputmode: 'numeric', maxlength: 10 }"
                @input-value="onCapInputValue"
              />
              <q-input class="col" v-model="draft.city" :label="t('address.city')" :rules="[required()]" />
            </div>

            <q-select
              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()]"
            />
          </div>

          <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 { computed, nextTick, reactive, ref, watch, toRefs } from 'vue'
import { useI18n } from 'vue-i18n'
import { COUNTRY_CODES, findPostalCodeDetails, findPostalCodes, ALL_CH_POSTAL_CODES } from '../data/countryCodes'

export interface SchoolOut {
  name: string
  cap: string
  city: string
  country: { code: string; name: string } | ''
}

type Draft = {
  name: string
  cap: string
  city: string
  country: string
  foreign: boolean
}

const props = withDefaults(
  defineProps<{
    modelValue: boolean
    modelSchool?: Partial<SchoolOut> | null
    title?: string
    allowForeign?: boolean
  }>(),
  { allowForeign: true }
)

const { allowForeign } = toRefs(props)

const emit = defineEmits<{
  (e: 'update:modelValue', v: boolean): void
  (e: 'save', payload: SchoolOut): 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()
})

const defaultDraft = (): Draft => ({ name: '', cap: '', city: '', country: '', foreign: false })
const draft = reactive<Draft>({ ...defaultDraft() })

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 capRule = (v: unknown) => {
  if (v === null || v === undefined || v === '') return t('validation.required') || 'Required'
  if (typeof v === 'number') return true
  if (typeof v === 'string') {
    const s = v.trim()
    if (s.length === 0) return t('validation.required') || 'Required'
    return true
  }
  return t('validation.invalidZip') || 'Invalid ZIP'
}

const countryOptions = computed(() => {
  try {
    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 }))
  }
})

const swissOptionsRef = ref(ALL_CH_POSTAL_CODES.map(z => ({ cap: z, label: z })))
const swissOptions = computed(() => swissOptionsRef.value)
const selectedSwissCap = ref<string | null>(null)

function filterCapFn(val: string, update: (fn: () => void) => void) {
  update(() => {
    const q = String(val || '').replace(/\D/g, '').slice(0, 4)
    swissOptionsRef.value = findPostalCodes(q).map(z => ({ cap: z, label: z }))
  })
}

function onCapInputValue(val: string) {
  const s = String(val || '').trim()
  if (draft.cap !== s) draft.cap = s
}

function onSwissCapInputValue(val: string) {
  const s = String(val || '').replace(/\D/g, '').slice(0, 4)
  swissOptionsRef.value = findPostalCodes(s).map(z => ({ cap: z, label: z }))

  if (!draft.foreign) {
    if (draft.cap !== s) draft.cap = s
    if (s.length >= 4) {
      const recs = findPostalCodeDetails(s)
      if (recs && recs.length) {
        const rec = recs[0]
        draft.cap = rec?.postalCode || s
        draft.city = rec?.placeName || ''
        selectedSwissCap.value = rec?.postalCode || s
      }
    }
  }
}

watch(selectedSwissCap, (cap) => {
  if (!cap || draft.foreign) return
  const rec = findPostalCodeDetails(cap)[0]
  if (rec) {
    draft.cap = rec.postalCode
  }
})

function onCapBlur() {
  const cap = selectedSwissCap.value
  if (!cap || draft.foreign) return
  const recs = findPostalCodeDetails(cap)
  if (recs && recs.length) {
    const rec = recs[0]
    draft.city = rec?.placeName || draft.city
    draft.cap = rec?.postalCode || draft.cap
  }
}

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

function populateFromModel() {
  const src = props.modelSchool || {}
  const capRaw = (src as Record<string, unknown>).cap
  const capStr = typeof capRaw === 'string' ? capRaw : (typeof capRaw === 'number' ? String(capRaw) : '')
  const countryCode =
    typeof src.country === 'object' && src.country !== null
      ? (src.country.code || '')
      : (typeof src.country === 'string' ? src.country : '')

  Object.assign(draft, {
    ...defaultDraft(),
    name: typeof src.name === 'string' ? src.name : '',
    cap: capStr,
    city: typeof src.city === 'string' ? src.city : '',
    country: countryCode,
    foreign: !!countryCode && countryCode !== 'CH'
  })

  if (!allowForeign.value) draft.foreign = false

  // init swiss select if CH and cap present
  if (!draft.foreign && draft.cap) {
    const s = String(draft.cap || '').replace(/\D/g, '').slice(0, 4)
    draft.cap = s
    if (ALL_CH_POSTAL_CODES.includes(s)) selectedSwissCap.value = s
    if (s.length >= 4) {
      const recs = findPostalCodeDetails(s)
      if (recs && recs.length) {
        const rec = recs[0]
        draft.city = rec?.placeName || draft.city
        draft.cap = rec?.postalCode || draft.cap
        selectedSwissCap.value = rec?.postalCode || s
      }
    }
  }

  void nextTick(() => formRef.value?.resetValidation?.())
}

async function onSave() {
  try {
    const ok = await (formRef.value?.validate?.() ?? true)
    if (ok === false) return
  } catch {
    return
  }

  let countryOut: { code: string; name: string } | '' = ''

  if (draft.foreign) {
    const code = draft.country || ''
    const label = countryOptions.value.find(o => o.value === code)?.label || code
    countryOut = code ? { code, name: label } : ''
  } else {
    try {
      const dn = new Intl.DisplayNames([String(locale.value)], { type: 'region' })
      countryOut = { code: 'CH', name: dn.of('CH') || 'Switzerland' }
    } catch {
      countryOut = { code: 'CH', name: 'Switzerland' }
    }
  }

  emit('save', {
    name: draft.name || '',
    cap: draft.cap || '',
    city: draft.city || '',
    country: countryOut
  })

  visible.value = false
}

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

const title = computed(() => props.title || t('children.school') || 'School')

// if already open at mount, ensure draft is populated
void nextTick(() => {
  if (props.modelValue || props.modelSchool) populateFromModel()
})
</script>

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