import type { VNode } from 'vue'

import type { Field, Attributes, Path, SchemaChild, BaseBagelField, VNodeFn } from '../types/BagelForm'
import {
	TextInput,
	NumberInput,
	FieldArray,
	SelectInput,
	ToggleInput,
	CheckInput,
	RichText,
	UploadInput,
	DateInput,
	TabsNav,
	BglForm,
	bindAttrs,
	classify,
	keyToLabel,
	TelInput,
	ColorInput,
	RangeInput,
	EmailInput
} from '@bagelink/vue'
import { h, isVNode } from 'vue'

const SLOT_VALUE_COMPONENTS = new Set(['div', 'span', 'p'])

const SRC_VALUE_COMPONENTS = new Set(['img', 'iframe'])

export interface UseSchemaFieldOptions<T, SFP extends Path<T>> {
	mode?: 'form' | 'preview' | 'table'
	getFormData?: () => T
	onUpdateModelValue?: (field: BaseBagelField<T, SFP>, value: any) => void
	includeUnset?: boolean
}

export function useSchemaField<T extends { [key: string]: any }, SP extends Path<T>>(optns: UseSchemaFieldOptions<T, SP>) {
	const { mode = 'form', getFormData, onUpdateModelValue, includeUnset = false } = optns

	// Helper function to render objects recursively
	function renderObject(obj: T, depth = 0): string {
		if (obj === null || obj === undefined) return ''
		if (typeof obj !== 'object') return String(obj)
		if (Array.isArray(obj)) return obj.map(item => renderObject(item, depth + 1)).join(', ')

		// For objects, format as key: value pairs
		const indent = depth > 0 ? '  '.repeat(depth) : ''
		const nextIndent = '  '.repeat(depth + 1)

		const entries = Object.entries(obj)
		if (entries.length === 0) return '{}'

		// For nested objects, format with newlines and indentation
		if (depth > 0) {
			return `{\n${entries
				.map(([key, value]) => `${nextIndent}${key}: ${renderObject(value, depth + 1)}`)
				.join(',\n')}\n${indent}}`
		}

		// For top-level objects, format as a flat list
		return entries
			.map(([key, value]) => {
				const valueStr = typeof value === 'object' && value !== null
					? renderObject(value, depth + 1)
					: String(value)
				return `${key}: ${valueStr}`
			})
			.join('\n')
	}

	function getComponent(field: Field<T>) {
		const componentMap = {
			text: TextInput,
			textarea: TextInput,
			number: NumberInput,
			array: FieldArray,
			color: ColorInput,
			tel: TelInput,
			select: SelectInput,
			toggle: ToggleInput,
			check: CheckInput,
			richtext: RichText,
			upload: UploadInput,
			file: UploadInput,
			date: DateInput,
			tabs: TabsNav,
			form: BglForm,
			range: RangeInput,
			email: EmailInput
		}

		if (field.$el === 'textarea' && !field.attrs?.multiline) {
			field.attrs = { ...field.attrs, multiline: true }
		}

		return typeof field.$el === 'object' ? field.$el : componentMap[field.$el as keyof typeof componentMap] ?? field.$el ?? 'div'
	}

	function renderChild(child: SchemaChild<T, SP>, slots?: BaseBagelField<T, SP>['slots']) {
		if (typeof child === 'string') return child
		if (isVNode(child)) return child
		return renderField(
			child as BaseBagelField<T, SP>,
			slots
		)
	}

	function renderField(
		field: BaseBagelField<T, SP>,
		slots?: BaseBagelField<T, SP>['slots']
	): VNode | undefined {
		const Component = getComponent(field as Field<T>)
		if (!Component) return

		const rowData = (getFormData?.() || {}) as T | undefined

		// Check vIf condition first
		const condition = field.vIf ?? field['v-if']
		if (condition !== undefined) {
			if (typeof condition === 'function') {
				if (
					!condition(
						field.id ? rowData?.[field.id] : undefined,
						rowData
					)
				) {
					return
				}
			} else if (typeof condition === 'string') {
				if (!rowData?.[condition]) {
					return
				}
			} else if (!condition) {
				return
			}
		}

		const {
			$el,
			children,
			options,
			attrs,
			class: fieldClass,
			id,
			transform,
			validate,
			onUpdate,
			slots: fieldSlots,
			required,
			label,
			placeholder,
			disabled,
			...fieldProps
		} = field

		// Use the get method if available, otherwise fall back to direct access
		const currentValue = field.id
			? ('get' in (rowData || {})
				? rowData?.get(field.id)
				: rowData?.[field.id])
			: undefined

		const transformedValue = transform ? transform(currentValue, rowData) : currentValue

		// First bind any function attributes with the current value and row data
		const boundFieldProps = bindAttrs(fieldProps as Attributes<T, Path<T>> | undefined, currentValue, rowData)

		// Check if this component should receive value as slot
		const isSlotValueComponent = typeof Component === 'string' && SLOT_VALUE_COMPONENTS.has(Component)
		// Check if this component should receive value as src
		const isSrcValueComponent = typeof Component === 'string' && SRC_VALUE_COMPONENTS.has(Component)

		const props: { [key: string]: any } = {
			...boundFieldProps,
			required,
			label,
			placeholder,
			disabled,
			validate,
			id,
			getFormData,
			onUpdate,
		}

		// For form mode, always use the original value for modelValue
		if (mode === 'form') {
			props.modelValue = currentValue
			props['onUpdate:modelValue'] = (value: any) => {
				onUpdateModelValue?.(field, value)
			}
		} else {
			// For display modes (table/preview), use transformed value based on component type
			if (isSlotValueComponent) {
				// Slot components don't need a value prop
			} else if (isSrcValueComponent) {
				props.src = transformedValue
			} else {
				props.modelValue = transformedValue
			}
		}

		// Remove undefined props to avoid vue warnings
		Object.keys(props).forEach(key => props[key] === undefined && delete props[key])

		// Add options if they exist in the field
		if (field.options) {
			props.options = typeof field.options === 'function'
				? field.options(currentValue, rowData)
				: field.options
		}

		// Handle dynamic props and attrs
		if (field.attrs) {
			const boundAttrs = bindAttrs(field.attrs, currentValue, rowData)
			Object.entries(boundAttrs).forEach(([key, value]) => {
				if (typeof value === 'function') {
					if (key.startsWith('on')) {
						props[key] = value
					} else {
						props[key] = value(currentValue, rowData)
					}
				} else {
					props[key] = value
				}
			})
		}

		// Handle class binding last to ensure all transformations are applied
		props.class = classify(currentValue, rowData, fieldClass, props.class)

		// Handle component slots with vIf aware child rendering
		// const componentSlots: { [name: string]: Slot } = {}
		const componentSlots: Parameters<typeof h>[1] = {}

		// Add default slot if there are children
		if (children?.length) {
			componentSlots.default = () => children
				.map(child => renderChild(child, slots))
				.filter(Boolean) // Filter out null results from vIf
		}

		// For slot value components, add the transformed value as default slot content
		if (isSlotValueComponent && transformedValue !== undefined) {
			componentSlots.default = () => transformedValue?.toString() || ''
		}

		// Handle custom slots from the field
		if (fieldSlots) {
			Object.entries(fieldSlots).forEach(([name, slot]) => {
				componentSlots[name] = () => {
					if (Array.isArray(slot)) {
						// Handle BglFormSchemaT array
						return slot.map((schemaField) => {
							if (typeof schemaField === 'function') {
								// Handle function slot
								const slotFn = schemaField
								return slotFn({ row: rowData, field })
							}
							if (isVNode(schemaField)) {
								// Handle VNode
								return schemaField
							}

							return renderField(schemaField as BaseBagelField<T, SP>, slots)
						})
					}
				}
			})
		}

		// Handle custom slot content from parent

		const slotContent = field.id ? (slots?.[field.id] as VNodeFn<T, SP> | undefined)?.({ row: rowData, field }) : undefined
		// field.id && slots?.[field.id] && typeof slots[field.id] === 'function' && !Array.isArray(slots?.[field.id])
		// 			? (slots[field.id])({ row: rowData, field })
		// 			: undefined

		if (mode === 'preview') {
			// Skip rendering if value is unset and includeUnset is false
			if (!includeUnset && (transformedValue === undefined || transformedValue === null || transformedValue.length === 0)) {
				return
			}

			return h('div', { class: 'preview-field' }, [
				h('div', { class: 'field-label' }, `${field.label || keyToLabel(field.id || '')}:`),
				h('div', { class: 'field-value' }, [
					slotContent || (typeof field.$el === 'object'
						? h(Component as any, props, componentSlots)
						: typeof transformedValue === 'object' && transformedValue !== null
							? h('pre', { style: 'margin: 0; white-space: pre-wrap; font-family: inherit; font-size: inherit;' }, renderObject(transformedValue))
							: transformedValue?.toString() || '')
				])
			])
		}

		return slotContent || h(Component as any, props, componentSlots)
	}

	return {
		renderField,
		getComponent
	}
}
