<template>
  <v-dialog v-model="dialogModel" max-width="480" v-bind="$attrs">
    <v-card>
      <v-card-title class="text-subtitle-1 font-weight-medium">
        <slot name="title">
          {{ props.titleText }}
        </slot>
      </v-card-title>

      <v-card-text class="pt-2">
        <slot :form="form" name="content" :permission="formPermission" :status="formStatus">
          <div class="d-flex flex-column ga-4">
            <v-select
              v-if="props.showStatus"
              v-model="formStatus"
              density="comfortable"
              hide-details
              item-title="title"
              item-value="value"
              :items="normalizedStatusOptions"
              :label="props.statusLabelText"
              variant="outlined"
            />

            <v-select
              v-if="props.showPermission"
              v-model="formPermission"
              density="comfortable"
              hide-details
              item-title="title"
              item-value="value"
              :items="normalizedPermissionOptions"
              :label="props.permissionLabelText"
              variant="outlined"
            />

            <slot :form="form" name="fields"></slot>
          </div>
        </slot>
      </v-card-text>

      <v-card-actions class="px-4 pb-4">
        <slot :cancel="handleCancel" name="actions" :submit="handleSubmit">
          <v-spacer />
          <v-btn :disabled="props.loading" variant="text" @click="handleCancel">
            {{ props.cancelText }}
          </v-btn>
          <v-btn color="primary" :loading="props.loading" @click="handleSubmit">
            {{ props.confirmText }}
          </v-btn>
        </slot>
      </v-card-actions>
    </v-card>
  </v-dialog>
</template>

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

type OptionValue = string | number
type Option = { title: string; value: OptionValue }

type GenericRecord = Record<string, unknown>

interface Props {
  modelValue: boolean
  item: GenericRecord | null
  statusKey?: string
  permissionKey?: string
  showStatus?: boolean
  showPermission?: boolean
  statusOptions?: Array<Option | string | number>
  permissionOptions?: Array<Option | string | number>
  titleText?: string
  statusLabelText?: string
  permissionLabelText?: string
  cancelText?: string
  confirmText?: string
  loading?: boolean
  closeOnSubmit?: boolean
}

const props = withDefaults(defineProps<Props>(), {
  statusKey: 'status',
  permissionKey: 'permission',
  showStatus: true,
  showPermission: true,
  statusOptions: () => [],
  permissionOptions: () => [],
  titleText: '編輯',
  statusLabelText: '狀態',
  permissionLabelText: '權限',
  cancelText: '取消',
  confirmText: '確認',
  loading: false,
  closeOnSubmit: true,
})

const emit = defineEmits<{
  (e: 'update:modelValue', value: boolean): void
  (e: 'submit', value: GenericRecord): void
  (e: 'cancel'): void
}>()

const dialogModel = computed({
  get: () => props.modelValue,
  set: (v: boolean) => emit('update:modelValue', v),
})

function normalizeOptions (options: Array<Option | string | number>) {
  return options.map((o) => {
    if (typeof o === 'string' || typeof o === 'number') {
      return { title: String(o), value: o }
    }
    return o
  })
}

const normalizedStatusOptions = computed(() => normalizeOptions(props.statusOptions))
const normalizedPermissionOptions = computed(() => normalizeOptions(props.permissionOptions))

const form = reactive<GenericRecord>({})

function resetForm (next: GenericRecord) {
  for (const key of Object.keys(form)) {
    delete form[key]
  }
  Object.assign(form, next)
}

const getDefaultStatus = (): OptionValue | '' => normalizedStatusOptions.value[0]?.value ?? ''
function getDefaultPermission (): OptionValue | '' {
  return normalizedPermissionOptions.value[0]?.value ?? ''
}

const formStatus = computed<OptionValue | ''>({
  get: () => {
    const current = form[props.statusKey] as OptionValue | undefined
    return current ?? getDefaultStatus()
  },
  set: (v) => {
    form[props.statusKey] = v
  },
})

const formPermission = computed<OptionValue | ''>({
  get: () => {
    const current = form[props.permissionKey] as OptionValue | undefined
    return current ?? getDefaultPermission()
  },
  set: (v) => {
    form[props.permissionKey] = v
  },
})

function syncFromItem () {
  const item = props.item ?? {}
  resetForm({ ...item })

  if (props.showStatus) {
    const status = item[props.statusKey] as OptionValue | undefined
    form[props.statusKey] = status ?? getDefaultStatus()
  }

  if (props.showPermission) {
    const permission = item[props.permissionKey] as OptionValue | undefined
    form[props.permissionKey] = permission ?? getDefaultPermission()
  }
}

watch(
  () => props.modelValue,
  (open) => {
    if (open) syncFromItem()
  }
)

watch(
  () => props.item,
  () => {
    if (props.modelValue) syncFromItem()
  }
)

function handleCancel () {
  emit('cancel')
  dialogModel.value = false
}

function handleSubmit () {
  emit('submit', { ...form })

  if (props.closeOnSubmit) {
    dialogModel.value = false
  }
}
</script>
