<template>
  <div ref="formWrapRef" class="ecp-pro-form">
    <el-form class="ecp-pro-form" ref="formRef" :model="currentFormModel" :rules="formRules" :label-width="effectiveProps.labelWidth"
      :label-position="effectiveProps.labelPosition" :size="effectiveProps.size" :disabled="effectiveProps.disabled"
      v-bind="$attrs" v-on="formListeners">
      <slot name="formHeader" />
      <el-row :gutter="effectiveProps.gutter" :style="effectiveProps.baseRowStyle">
        <template v-for="schema in displaySchemas">
          <el-col v-if="shouldShow(schema)" :key="schema.field" v-bind="getColProps(schema)"
            :offset="schema.colProps?.offset ?? effectiveProps.baseColProps?.offset ?? 0" :data-field="schema.field">
            <ProFormItem :schema="schema" :form-model="currentFormModel" :form-disabled="effectiveProps.disabled"
              :auto-placeholder="effectiveProps.autoSetPlaceholder" :form-action-type="formActionRef" :colon="effectiveProps.colon"
              :custom-components="formCustomComponents" :on-field-change="handleFieldChange"
              :register-field-instance="registerFieldInstance">
              <template v-if="slots[getSlotName(schema)]">
                <slot :name="getSlotName(schema)" :model="currentFormModel" :schema="schema" :field="schema.field"
                  :values="currentFormModel" />
              </template>
            </ProFormItem>
          </el-col>
        </template>
        <el-col class="ecp-pro-form_col" v-if="effectiveProps.showActionButtonGroup"
          v-bind="hasMoreFields ? { span: 24 } : effectiveProps.actionColOptions || { span: 6 }">
          <FormActions :show-action-button-group="effectiveProps.showActionButtonGroup"
            :show-submit-button="effectiveProps.showSubmitButton" :show-reset-button="effectiveProps.showResetButton"
            :submit-button-text="effectiveProps.submitButtonText" :reset-button-text="effectiveProps.resetButtonText"
            :submit-button-icon="effectiveProps.submitButtonIcon" :reset-button-icon="effectiveProps.resetButtonIcon"
            :submit-loading="submitLoading" :show-advanced-button="effectiveProps.showAdvancedButton"
            :has-more-fields="hasMoreFields" :collapsed="collapsed" :action-col-options="effectiveActionColOptions"
            @submit="handleSubmit" @reset="handleReset" @toggle="collapsed = !collapsed">
            <template slot="submitBefore">
              <slot name="submitBefore" />
            </template>
            <template slot="resetBefore">
              <slot name="resetBefore" />
            </template>
            <template slot="advanceBefore">
              <slot name="advanceBefore" />
            </template>
            <template slot="advanceAfter">
              <slot name="advanceAfter" />
            </template>
            <template slot="actions">
              <slot name="actions" />
            </template>
          </FormActions>
        </el-col>
      </el-row>
      <el-button v-if="effectiveProps.showAdvancedButton && hasMoreFields" type="text" class="ecp-form-actions__advance"
        @click="collapsed = !collapsed">
        <i class="el-icon-d-arrow-left" :class="collapsed ? 'down' : 'up'" />
        {{ collapsed ? '展开' : '收起' }}
      </el-button>
      <slot name="formFooter" />
    </el-form>
  </div>
</template>

<script setup lang="ts">
import { ref, computed, watch, useSlots, onMounted, onUnmounted, set } from 'vue'
import type { ComponentPublicInstance } from 'vue'
import { useComponentSetting } from '../useComponentSetting'
import ProFormItem from './ProFormItem.vue'
import FormActions from './FormActions.vue'
import { deepMerge } from '../utils/deepMerge'
import type { ProFormSchema, ProFormProps, FormActionType, ColEx, ScrollToFieldOptions, FormListeners, ApiSelectInstance } from '../types'

const props = withDefaults(
  defineProps<{
    schemas?: ProFormSchema[]
    value?: Record<string, unknown>
    initialValues?: Record<string, unknown>
    labelWidth?: string
    labelPosition?: 'left' | 'right' | 'top'
    colon?: boolean
    gutter?: number
    size?: 'medium' | 'small' | 'large'
    disabled?: boolean
    baseColProps?: ColEx
    baseRowStyle?: Record<string, string | number>
    autoSetPlaceholder?: boolean
    showSubmitButton?: boolean
    showResetButton?: boolean
    submitButtonText?: string
    resetButtonText?: string
    submitButtonIcon?: string
    resetButtonIcon?: string
    showActionButtonGroup?: boolean
    actionColOptions?: ColEx
    showAdvancedButton?: boolean
    autoAdvancedLine?: number
    alwaysShowLines?: number
    submitFunc?: () => Promise<void>
    resetFunc?: () => Promise<void>
    submitOnReset?: boolean
    formListeners?: FormListeners
    /** 自定义组件映射（组件名 -> 组件），供 schema.component 使用 */
    components?: Record<string, unknown>
  }>(),
  {
    labelWidth: '120px',
    labelPosition: 'right',
    colon: true,
    gutter: 24,
    size: 'medium',
    autoSetPlaceholder: true,
    showSubmitButton: true,
    showResetButton: true,
    submitButtonText: '查询',
    resetButtonText: '重置',
    submitButtonIcon: 'el-icon-search',
    resetButtonIcon: 'el-icon-refresh-left',
    showActionButtonGroup: true,
    showAdvancedButton: false,
    autoAdvancedLine: 3,
    alwaysShowLines: 1,
    baseColProps: () => ({ xs: 24, sm: 12, md: 12, lg: 8, xl: 6 }),
    actionColOptions: () => ({ xs: 24, sm: 12, md: 12, lg: 8, xl: 6 }),
    submitOnReset: false,
  }
)

const emit = defineEmits<{
  (e: 'submit', values: Record<string, unknown>): void
  (e: 'reset'): void
  (e: 'register', formAction: FormActionType): void
  (e: 'input', values: Record<string, unknown>): void
  (e: 'update:value', values: Record<string, unknown>): void
}>()
const slots = useSlots()
const formRef = ref()
const formWrapRef = ref()
const submitLoading = ref(false)
const collapsed = ref(true)
const formModel = ref<Record<string, unknown>>({})
const formRules = ref<Record<string, unknown[]>>({})
const innerSchemas = ref<ProFormSchema[]>([])
const innerProps = ref<Partial<ProFormProps>>({})
/** 字段名 -> 组件实例映射（用于获取 api-select 等组件的内部状态） */
const fieldInstanceMap = ref<Map<string, ComponentPublicInstance>>(new Map())

/** Element UI 栅格断点 (px) */
const BREAKPOINTS = { xl: 1920, lg: 1200, md: 992, sm: 768 }

/** 根据当前视口宽度获取 ColEx 的有效 span（与 el-col 断点逻辑一致） */
const getEffectiveSpan = (colProps?: ColEx | null, baseColProps?: ColEx | null, width?: number): number => {
  const w = width ?? (typeof window !== 'undefined' ? window.innerWidth : 1920)
  const col = colProps ?? {}
  const base = baseColProps ?? {}
  const fallback = base.span ?? 8
  if (w >= BREAKPOINTS.xl) return col.xl ?? base.xl ?? col.lg ?? base.lg ?? col.md ?? base.md ?? col.sm ?? base.sm ?? col.xs ?? base.xs ?? col.span ?? fallback
  if (w >= BREAKPOINTS.lg) return col.lg ?? base.lg ?? col.md ?? base.md ?? col.sm ?? base.sm ?? col.xs ?? base.xs ?? col.span ?? fallback
  if (w >= BREAKPOINTS.md) return col.md ?? base.md ?? col.sm ?? base.sm ?? col.xs ?? base.xs ?? col.span ?? fallback
  if (w >= BREAKPOINTS.sm) return col.sm ?? base.sm ?? col.xs ?? base.xs ?? col.span ?? fallback
  // if (w < BREAKPOINTS.sm) return col.sm ?? base.sm ?? col.xs ?? base.xs ?? col.span ?? fallback
  return col.xs ?? base.xs ?? col.span ?? fallback
}

const { getSetting: getComponentSetting } = useComponentSetting()
const effectiveProps = computed(() => ({ ...getComponentSetting('ProForm'), ...props, ...innerProps.value }))
const controlledModelValue = computed(() => effectiveProps.value.value)
const isControlled = computed(() => controlledModelValue.value !== undefined)
const currentFormModel = computed<Record<string, unknown>>(() => {
  const model = controlledModelValue.value
  const fModel = formModel.value
  return isControlled.value ? (model ?? {}) : fModel
})
/** 传给 ProFormItem 的自定义组件映射：显式合并 setSetting 与 props，避免响应式代理导致组件引用丢失 */
const formCustomComponents = computed(() => ({
  ...(getComponentSetting('ProForm').components as Record<string, unknown> | undefined) ?? {},
  ...(effectiveProps.value.components ?? {}),
  ...(innerProps.value.components ?? {}),
}))
const effectiveActionColOptions = computed(() => effectiveProps.value.actionColOptions ?? { span: 24 })

/** 当前视口宽度，用于响应式计算 span */
const windowWidth = ref(typeof window !== 'undefined' ? window.innerWidth : 1920)

/** 计算前 maxLines 行能容纳的 schema 数量（考虑栅格断点，每个 field 可有自己的 span） */
const getVisibleSchemaCount = (
  schemas: ProFormSchema[],
  baseColProps: ColEx | undefined,
  maxLines: number,
  width: number
) => {
  let remaining = 24
  let rows = 1
  let count = 0
  for (const schema of schemas) {
    const span = getEffectiveSpan(schema.colProps, baseColProps, width)
    if (span > remaining) {
      rows++
      if (rows > maxLines) break
      remaining = 24 - span
    } else {
      remaining -= span
    }
    count++
  }
  return count
}

const hasMoreFields = computed(() => {
  const schemas = innerSchemas.value.filter((s) => shouldShow(s))

  if (!effectiveProps.value.showAdvancedButton) return false
  const lines = effectiveProps.value.alwaysShowLines ?? 1
  const baseColProps = effectiveProps.value.baseColProps
  const maxVisible = getVisibleSchemaCount(schemas, baseColProps, lines, windowWidth.value)
  return schemas.length > maxVisible
})

const formListeners = computed((): FormListeners => {
  return effectiveProps.value.formListeners ?? {}
})

const displaySchemas = computed(() => {
  const schemas = innerSchemas.value.filter((s) => {
    return shouldShow(s)
  })
  if (!effectiveProps.value.showAdvancedButton || !collapsed.value) return schemas
  const lines = effectiveProps.value.alwaysShowLines ?? 1
  const baseColProps = effectiveProps.value.baseColProps
  const maxVisible = getVisibleSchemaCount(schemas, baseColProps, lines, windowWidth.value)
  return schemas.slice(0, maxVisible)
})

const shouldShow = (schema: ProFormSchema) => {
  let ifShow = true
  let show = true
  if (typeof schema.ifShow === 'function') {
    ifShow = schema.ifShow({ schema, values: currentFormModel.value, model: currentFormModel.value, field: schema.field })
  }
  if (typeof schema.ifShow === 'boolean') {
    ifShow = schema.ifShow
  }
  if (typeof schema.show === 'function') {
    show = schema.show({ schema, values: currentFormModel.value, model: currentFormModel.value, field: schema.field })
  }
  if (typeof schema.show === 'boolean') {
    show = schema.show
  }
  return ifShow && show
}
const getColProps = (schema: ProFormSchema) => {
  return schema.colProps ?? effectiveProps.value.baseColProps ?? {}
}
const getSlotName = (schema: ProFormSchema) => schema.slot || schema.field
/**
 * 
 * @param baseModel 
 * @param preserveExisting 
 */
const resolveSchemaModel = (baseModel?: Record<string, unknown>, preserveExisting = true) => {
  const nextModel = preserveExisting ? { ...(baseModel ?? {}) } : {}
  const initialValues = effectiveProps.value.initialValues ?? props.initialValues
  innerSchemas.value.forEach((schema) => {
    if (preserveExisting && Object.prototype.hasOwnProperty.call(nextModel, schema.field)) return
    if (schema.defaultValue !== undefined) {
      nextModel[schema.field] = schema.defaultValue
      return
    }
    if (initialValues && Object.prototype.hasOwnProperty.call(initialValues, schema.field)) {
      nextModel[schema.field] = initialValues[schema.field]
    }
  })
  return nextModel
}

const applyFormModel = (nextModel: Record<string, unknown>, shouldEmit = true) => {
  formModel.value = nextModel
  if (shouldEmit) {
    emit('input', nextModel)
    emit('update:value', nextModel)
  }
}

const updateFormModel = (values: Record<string, unknown>) => {
  const nextModel = resolveSchemaModel({ ...currentFormModel.value, ...values })
  applyFormModel(nextModel)
  return nextModel
}

const initForm = () => {
  const rules: Record<string, unknown[]> = {}
  innerSchemas.value.forEach((schema) => {
    if (schema.rules?.length) rules[schema.field] = schema.rules
  })
  applyFormModel(resolveSchemaModel(currentFormModel.value), false)
  formRules.value = rules
}

const filterByIfShow = (values: Record<string, unknown>) => {
  const result = { ...values }
  innerSchemas.value.forEach((schema) => {
    const ifShow = schema.ifShow
    if (ifShow === undefined) return
    const visible = typeof ifShow === 'boolean' ? ifShow : ifShow({ schema, values, model: values, field: schema.field })
    if (!visible) delete result[schema.field]
  })
  return result
}

const processFieldMapToTime = (values: Record<string, unknown>) => {
  const filtered = filterByIfShow(values)
  const fieldMap = innerProps.value.fieldMapToTime
  if (!fieldMap?.length) return filtered
  const result = { ...filtered }
  fieldMap.forEach(([field, [startKey, endKey]]) => {
    const val = result[field]
    if (Array.isArray(val) && val.length === 2) {
      delete result[field]
        ; (result as Record<string, unknown>)[startKey] = val[0]
        ; (result as Record<string, unknown>)[endKey] = val[1]
    }
  })
  return result
}

const handleSubmit = async () => {
  try {
    await formRef.value?.validate()
    if (effectiveProps.value.submitFunc) {
      await effectiveProps.value.submitFunc()
    } else {
      submitLoading.value = true
      emit('submit', processFieldMapToTime({ ...currentFormModel.value }))
    }
  } catch (e) {
    console.error('Form validation failed:', e)
  } finally {
    submitLoading.value = false
  }
}

const handleReset = async () => {
  if (effectiveProps.value.resetFunc) {
    await effectiveProps.value.resetFunc()
  } else {
    formRef.value?.resetFields()
    initForm()
    emit('reset')
    if (effectiveProps.value.submitOnReset) await handleSubmit()
  }
}

const setFieldsValue = (values: Record<string, unknown>) => {
  updateFormModel(values)
  return Promise.resolve()
}

const getFieldsValue = () => processFieldMapToTime({ ...currentFormModel.value })

const resetFields = async () => {
  formRef.value?.resetFields()
  applyFormModel(resolveSchemaModel(undefined, false))
}

const handleFieldChange = (field: string, value: unknown) => {
  updateFormModel({ [field]: value })
}

const validate = (nameList?: string[]) => {
  if (!formRef.value) return Promise.resolve(getFieldsValue())
  return formRef.value
    .validate(nameList)
    .then(() => getFieldsValue())
    .catch((errors: Record<string, unknown>) => (Promise.reject({
      valid: false,
      errors,
    }) ))
}

const validateFields = (nameList?: string[]) => {
  if (!formRef.value) return Promise.resolve()
  if (!nameList?.length) return formRef.value.validate()
  return Promise.all(nameList.map((prop) => new Promise((resolve, reject) => {
    formRef.value.validateField(prop, (valid: boolean) => (valid ? resolve(undefined) : reject(new Error('Validation failed'))))
  })))
}

const scrollToField = async (name: string, options?: ScrollToFieldOptions) => {
  const el = formWrapRef.value?.querySelector(`[data-field="${name}"]`)
  if (el) {
    el.scrollIntoView({ behavior: options?.behavior ?? 'smooth', block: options?.block ?? 'nearest' })
  }
  return Promise.resolve()
}

const clearValidate = (name?: string | string[]) => {
  formRef.value?.clearValidate(name)
}

const updateSchema = async (data: Partial<ProFormSchema> | Partial<ProFormSchema>[]) => {
  const list = Array.isArray(data) ? data : [data]
  list.forEach((item) => {
    const idx = innerSchemas.value.findIndex((s) => s.field === item.field)
    if (idx >= 0) {
      // @ts-ignore
      innerSchemas.value.splice(idx, 1, {...deepMerge(innerSchemas.value[idx], item)})
      // set(innerSchemas.value, idx, deepMerge(innerSchemas.value[idx], item))
    }
  })
  initForm()
}

const appendSchemaByField = async (schema: ProFormSchema, prefixField?: string, first?: boolean) => {
  if (first) innerSchemas.value.unshift(schema)
  else if (prefixField) {
    const idx = innerSchemas.value.findIndex((s) => s.field === prefixField)
    innerSchemas.value.splice(idx + 1, 0, schema)
  } else innerSchemas.value.push(schema)
  initForm()
}

const removeSchemaByField = async (field: string | string[]) => {
  const fields = Array.isArray(field) ? field : [field]
  innerSchemas.value = innerSchemas.value.filter((s) => !fields.includes(s.field))
}

const setProps = async (formProps: Partial<ProFormProps>) => {
  innerProps.value = { ...innerProps.value, ...formProps }
  if (formProps.schemas) {
    innerSchemas.value = [...formProps.schemas]
    initForm()
  }
}

/** 注册组件实例（供 ProFormItem 调用） */
const registerFieldInstance = (field: string, instance: Record<string, unknown> | null) => {
  if (instance) {
    fieldInstanceMap.value.set(field, instance as unknown as ComponentPublicInstance)
  } else {
    fieldInstanceMap.value.delete(field)
  }
}

/** 获取指定字段的组件实例 */
const getComponentInstance = (field: string): ComponentPublicInstance | null => {
  return fieldInstanceMap.value.get(field) ?? null
}

/** 获取 api-select 字段的 options，raw=true 时返回接口原始数据 */
function getFieldOptions(field: string): Array<{ label: string; value: unknown }>
function getFieldOptions(field: string, raw: false): Array<{ label: string; value: unknown }>
function getFieldOptions(field: string, raw: true): unknown[]
function getFieldOptions(field: string, raw = false): Array<{ label: string; value: unknown }> | unknown[] {
  const instance = fieldInstanceMap.value.get(field)
  if (!instance) return []
  const apiSelectInstance = instance as unknown as ApiSelectInstance
  if (raw) {
    return apiSelectInstance?.rawOptions ?? []
  }
  return apiSelectInstance?.options ?? []
}

/** 获取 api-select 字段的加载状态 */
const isFieldLoading = (field: string): boolean => {
  const instance = fieldInstanceMap.value.get(field)
  if (!instance) return false
  const apiSelectInstance = instance as unknown as ApiSelectInstance
  return apiSelectInstance?.loading ?? false
}

const formActionRef: FormActionType = {
  getFieldsValue,
  setFieldsValue,
  resetFields,
  validate,
  validateFields,
  submit: handleSubmit,
  scrollToField,
  clearValidate,
  updateSchema,
  appendSchemaByField,
  removeSchemaByField,
  setProps,
  getComponentInstance,
  getFieldOptions,
  isFieldLoading,
}

defineExpose({
  ...formActionRef,
  registerFieldInstance,
})

const syncSchemas = () => {
  innerSchemas.value = [...(props.schemas ?? [])]
  initForm()
}

const handleResize = () => {
  if (typeof window !== 'undefined') windowWidth.value = window.innerWidth
}

onMounted(() => {
  syncSchemas()
  emit('register', formActionRef)
  if (typeof window !== 'undefined') window.addEventListener('resize', handleResize)
})

onUnmounted(() => {
  if (typeof window !== 'undefined') window.removeEventListener('resize', handleResize)
})

watch(() => controlledModelValue.value, (value) => {
  if (!isControlled.value || value === undefined) return
  applyFormModel(resolveSchemaModel(value), false)
}, { deep: true, immediate: true })

watch(() => [props.schemas, props.initialValues], syncSchemas, { deep: true })
</script>

<style>
.ecp-pro-form {
  padding: 16px;
  position: relative;
}

.ecp-pro-form__advance {
  margin-bottom: 16px;
}

.ecp-pro-form .ecp-pro-form_col {
  position: relative;
  float: right;
}

.el-icon-d-arrow-left.up {
  transform: rotate(90deg);
}

.el-icon-d-arrow-left.down {
  transform: rotate(-90deg);
}

.ecp-form-actions__advance {
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translate(-50%, -50%);
}
</style>
