<template>
  <h2>{{ title ?? (isEdit ? `Edit ${entityLabel}` : `Create ${entityLabel}`) }}</h2>
  <div v-if="loading" class="loading-message">Loading {{ entityLabel.toLowerCase() }}...</div>
  <form v-else @submit.prevent="submit" class="entity-form" ref="formRef">
    <template v-for="field in fields" :key="field.name">
      <div class="group">
        <label :for="field.name">
          {{ field.label }}
          <!-- Custom field slot -->
          <slot
            :name="`custom-field-${field.name}`"
            :modelValue="formData[field.name]"
            :update="(val: unknown) => (formData[field.name] = val)"
          >
            <!-- Default rendering if no slot provided -->
            <input
              v-if="field.type === 'text'"
              :id="field.name"
              v-model="formData[field.name]"
              type="text"
              :required="field.required"
              :maxlength="field.maxlength"
            />
            <input
              v-else-if="field.type === 'number'"
              :id="field.name"
              v-model="formData[field.name]"
              type="number"
              :required="field.required"
              :min="field.min"
              :max="field.max"
              inputmode="numeric"
              pattern="\\d{1,3}"
              @input="
                (e) => {
                  if (field.maxlength && e.target.value.length > field.maxlength) {
                    e.target.value = e.target.value.slice(0, field.maxlength)
                    formData[field.name] = e.target.value
                  }
                }
              "
            />
            <ImagePicker
              v-else-if="field.type === 'image'"
              :id="field.name"
              v-model="formData[field.name]"
              :image-type="field.imageType || 1"
              @add-image="onAddImage"
            />
          </slot>
        </label>
      </div>
    </template>
    <div v-if="error" class="error">{{ error }}</div>
    <div class="actions">
      <button type="button" class="btn btn-secondary" @click="onCancel" :disabled="loading">
        Cancel
      </button>
      <button
        type="submit"
        class="btn btn-primary"
        :disabled="loading || !isValid || (props.requireDirty && !isDirty)"
      >
        {{ isEdit ? 'Save' : 'Create' }}
      </button>
    </div>
  </form>
</template>

<script setup lang="ts">
import { ref, onMounted, nextTick, watch, computed } from 'vue'
import ImagePicker from '@/components/utils/ImagePicker.vue'
import { useRouter } from 'vue-router'
import '@/assets/styles.css'

type Field = {
  name: string
  label: string
  type: 'text' | 'number' | 'image' | 'custom'
  required?: boolean
  maxlength?: number
  min?: number
  max?: number
  imageType?: number
}

const props = withDefaults(
  defineProps<{
    entityLabel: string
    fields: Field[]
    initialData?: Record<string, any>
    isEdit?: boolean
    loading?: boolean
    error?: string | null
    title?: string
    requireDirty?: boolean
  }>(),
  {
    requireDirty: true,
  },
)

const emit = defineEmits(['submit', 'cancel', 'add-image'])

const router = useRouter()
const formData = ref<Record<string, any>>({ ...props.initialData })
const baselineData = ref<Record<string, any>>({ ...props.initialData })
const formRef = ref<HTMLFormElement | null>(null)

async function focusFirstInput() {
  await nextTick()
  const firstInput = formRef.value?.querySelector<HTMLElement>('input, select, textarea')
  firstInput?.focus()
}

onMounted(async () => {
  await nextTick()
  isDirty.value = false
  if (!props.loading) {
    focusFirstInput()
  }
})

watch(
  () => props.loading,
  (newVal, oldVal) => {
    if (!newVal && oldVal === true) {
      focusFirstInput()
    }
  },
)

function onAddImage({ id, file }: { id: string; file: File }) {
  emit('add-image', { id, file })
}

function onCancel() {
  emit('cancel')
}

function submit() {
  emit('submit', { ...formData.value })
  // After submit, reset isDirty so Save button is disabled until next change
  isDirty.value = false
}

// Editable field names (exclude custom fields that are not editable)
const editableFieldNames = props.fields
  .filter((f) => f.type !== 'custom' || f.name === 'type')
  .map((f) => f.name)

const isDirty = ref(false)

function getFieldByName(name: string): Field | undefined {
  return props.fields.find((field) => field.name === name)
}

function valuesEqualForDirtyCheck(
  fieldName: string,
  currentValue: unknown,
  initialValue: unknown,
): boolean {
  const field = getFieldByName(fieldName)

  if (field?.type === 'number') {
    const currentEmpty = currentValue === '' || currentValue === null || currentValue === undefined
    const initialEmpty = initialValue === '' || initialValue === null || initialValue === undefined
    if (currentEmpty && initialEmpty) return true
    if (currentEmpty !== initialEmpty) return false
    return Number(currentValue) === Number(initialValue)
  }

  return JSON.stringify(currentValue) === JSON.stringify(initialValue)
}

function checkDirty() {
  isDirty.value = editableFieldNames.some((key) => {
    return !valuesEqualForDirtyCheck(key, formData.value[key], baselineData.value[key])
  })
}

// Validation logic
const isValid = computed(() => {
  return props.fields.every((field) => {
    if (!field.required) return true
    const value = formData.value[field.name]

    if (field.type === 'text') {
      return typeof value === 'string' && value.trim().length > 0
    }

    if (field.type === 'number') {
      if (value === '' || value === null || value === undefined) return false
      const numValue = Number(value)
      if (isNaN(numValue)) return false
      if (field.min !== undefined && numValue < field.min) return false
      if (field.max !== undefined && numValue > field.max) return false
      return true
    }

    // For other types, just check it's not null/undefined
    return value != null
  })
})

watch(
  () => ({ ...formData.value }),
  () => {
    checkDirty()
  },
  { deep: true },
)

watch(
  () => props.initialData,
  (newVal) => {
    if (newVal) {
      formData.value = { ...newVal }
      baselineData.value = { ...newVal }
      isDirty.value = false
    }
  },
  { immediate: true, deep: true },
)
</script>

<style scoped>
h2 {
  text-align: center;
  margin-bottom: 1.5rem;
  color: var(--form-heading);
}
.entity-edit-view {
  max-width: 420px;
  margin: 0 auto;
  background: var(--edit-view-bg, #fff);
  border-radius: 14px;
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08);
  padding: 2.2rem 2.2rem 1.5rem 2.2rem;
}
.entity-edit-view h2 {
  text-align: center;
  margin-bottom: 1.5rem;
  color: var(--form-heading);
}
.entity-form .group {
  margin-bottom: 1.2rem;
}
.entity-form label {
  display: block;
  font-weight: 600;
  color: var(--form-label, #444);
  margin-bottom: 0.4rem;
}
.entity-form input[type='text'],
.entity-form input[type='number'] {
  width: 100%;
  padding: 0.6rem;
  border-radius: 7px;
  border: 1px solid var(--form-input-border, #e6e6e6);
  font-size: 1rem;
  background: var(--form-input-bg, #fff);
  box-sizing: border-box;
}
.actions {
  display: flex;
  gap: 3rem;
  justify-content: center;
  margin-top: 0.5rem;
  margin-bottom: 0.4rem;
}
.actions .btn {
  padding: 1rem 2.2rem;
  font-weight: 700;
  font-size: 1.25rem;
  min-width: 120px;
}

.error {
  color: var(--error-color, #e53e3e);
  margin-bottom: 1rem;
  font-size: 1rem;
}
.loading-message {
  color: var(--loading-color, #888);
  margin-bottom: 1.2rem;
  font-size: 1rem;
}
</style>
