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,
	getNestedValue
} 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 }>(optns: UseSchemaFieldOptions<T, Path<T>>) {
	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, Path<T>>, slots?: BaseBagelField<T, Path<T>>['slots']) {
		if (typeof child === 'string') { return child }
		if (isVNode(child)) { return child }
		return renderField(
			child as BaseBagelField<T, Path<T>>,
			slots
		)
	}

	function renderField(
		field: BaseBagelField<T, Path<T>>,
		slots?: BaseBagelField<T, Path<T>>['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') {
				// Compute currentValue for the vIf check
				const vIfCurrentValue = field.id
					? ('get' in (rowData || {})
						? (rowData as any)?.get(field.id)
						: getNestedValue(rowData, field.id as string))
					: undefined

				const vIfResult = field.id
					? condition(vIfCurrentValue, rowData)
					: (condition as any)(rowData)
				if (!vIfResult) {
					return
				}
			} else if (typeof condition === 'string') {
				if (!getNestedValue(rowData, condition)) {
					return
				}
			} else if (!condition) {
				return
			}
		}

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

		// Use the get method if available, otherwise fall back to nested value access
		const currentValue = field.id
			? ('get' in (rowData || {})
				? (rowData as any)?.get(field.id)
				: getNestedValue(rowData, field.id as string))
			: undefined

		// Apply transform with conditional args: provide (val,row) only when id exists; otherwise provide only (row)
		const transformedValue = transform
			? (id
				? transform(currentValue, rowData)
				: (transform as any)(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,
			defaultValue,
		}

		// Special handling for FieldArray component to pass attrs.schema as schema prop
		if (Component === FieldArray && field.attrs?.schema) {
			props.schema = field.attrs.schema
		}
		// Special handling for FieldArray component to pass attrs.type as type prop
		if (Component === FieldArray && field.attrs?.type) {
			props.type = field.attrs.type
		}
		// Special handling for FieldArray component to pass collapsed prop
		if (Component === FieldArray && 'collapsed' in field) {
			props.collapsed = (field as any).collapsed
		}

		// Wire top-level onClick with conditional args
		if (typeof (field as any).onClick === 'function') {
			const original = (field as any).onClick as (val?: any, row?: T) => void
			props.onClick = () => {
				if (id) {
					original(currentValue, rowData)
				} else {
					;(original as any)(rowData)
				}
			}
		}

		// 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) => {
			if (props[key] === undefined) {
				delete props[key]
			}
		})

		// Add options if they exist in the field
		if (field.options) {
			if (Array.isArray(field.options)) {
				props.options = field.options
			} else if (typeof field.options === 'function') {
				const fn = field.options as any
				// Component-aware mapping
				if (Component === SelectInput) {
					props.options = (query: string) => {
						const row = getFormData?.()
						const val = currentValue
						// If author expects (query) only, call directly
						if (typeof fn === 'function' && fn.length === 1) {
							return fn(query)
						}
						// If author expects (query, val, row), pass it directly
						if (typeof fn === 'function' && fn.length >= 3) {
							return fn(query, val, row)
						}
						// Otherwise evaluate (val,row) → array | (query)=>Promise | Promise
						const out = fn(val, row)
						if (Array.isArray(out)) { return out }
						if (typeof out === 'function') { return out(query) }
						if (out && typeof out.then === 'function') { return out }
						return []
					}
				} else {
					// Non-search components (e.g., RadioGroup): return a zero-arg async loader using latest row
					props.options = async () => {
						const row = getFormData?.()
						const val = currentValue
						const out = fn(val, row)
						if (Array.isArray(out)) { return out }
						if (typeof out === 'function') { return await out('') }
						if (out && typeof out.then === 'function') { return await out }
						if (fn.length >= 3) { return await fn('', val, row) }
						return []
					}
				}
			} else {
				props.options = field.options
			}
		}

		// Handle dynamic props and attrs
		if (field.attrs) {
			const boundAttrs = bindAttrs(field.attrs, currentValue, rowData)
			Object.entries(boundAttrs).forEach(([key, value]) => {
				// Skip $el as it's not a DOM attribute
				if (key === '$el') { return }

				if (typeof value === 'function') {
					if (key === 'onClick') {
						const original = value as (val?: any, row?: T) => void
						props.onClick = () => {
							if (id) {
								original(currentValue, rowData)
							} else {
								;(original as any)(rowData)
							}
						}
					} else 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 && children.length > 0) {
			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 = () => String(transformedValue ?? '')
		}

		// 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, Path<T>>, slots)
						})
					}
					return []
				}
			})
		}

		// Handle custom slot content from parent

		const slotContent = field.id
			? (slots?.[field.id] as VNodeFn<T, Path<T>> | 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
				|| (typeof transformedValue === 'string' && 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))
							: String(transformedValue ?? ''))
				])
			])
		}

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

	return {
		renderField,
		getComponent
	}
}
