import type { SelectInput, TextInput } from '@bagelink/vue'
import type { Paths, Get, IterableElement, OmitIndexSignature } from 'type-fest'
import type { ToString } from 'type-fest/source/internal'
import type { LiteralStringUnion } from 'type-fest/source/literal-union'
import type { PathsOptions, DefaultPathsOptions } from 'type-fest/source/paths'
import type { VNode } from 'vue'
import type { ComponentExposed } from 'vue-component-type-helpers'

export type AttributeValue =
	| string
	| number
	| boolean
	| undefined
	| { [key: string]: any }

export type AttributeFn<T, P extends Path<T>> = (
	field: FieldVal<T, P>,
	row?: T
) => AttributeValue

export interface Attributes<T, P extends Path<T>> {
	[key: string]: AttributeValue | AttributeFn<T, P>
}

export type BagelFieldOptions<T, P extends Path<T>> =
	| string
	| (
		| {
			label?: string
			value: string | number
		}
		| string
		| number
		| boolean
		| { [key: string]: any }
	)[]
	| ((val?: FieldVal<T, P>, rowData?: T) => void)

export type VIfType<T, P extends Path<T>> =
	| string
	| boolean
	| ((val?: FieldVal<T, P>, rowData?: T) => boolean)

export type ValidationFn<T, P extends Path<T>> = (
	val?: FieldVal<T, P>,
	rowData?: T
) => string | undefined

export interface ShallowPathsOptions extends PathsOptions {
	maxRecursionDepth: 0
}

export type _Path<
	T,
	PO extends PathsOptions = DefaultPathsOptions,
> = ToString<Paths<OmitIndexSignature<T>, PO>>

export type Path<T, PO extends PathsOptions = DefaultPathsOptions> =
	FieldVal<T, _Path<T, PO>> extends Array<any>
		? LiteralStringUnion<_Path<T, PO>>
		: _Path<T, PO>

/** The value type at path P in object T. */
// Fall back to unknown if type resolution fails
export type FieldVal<T, P extends Path<T>> =
	unknown extends Get<T, P> ? unknown : Get<T, P>

/** If path P in T is an array, this gives the array's element type. */
export type ArrayFieldVal<T, P extends Path<T>> = IterableElement<Get<T, P>>

export type VNodeFn<T, P extends Path<T>> = (props: {
	row?: T
	field: BaseBagelField<T, P>
}) => VNode

export type SchemaChild<
	T,
	P extends Path<T, PO>,
	PO extends PathsOptions = DefaultPathsOptions,
> = Field<T, PO> | VNode | VNodeFn<T, P> | string

export interface BaseBagelField<
	T,
	P extends Path<T, PO>,
	PO extends PathsOptions = DefaultPathsOptions,
> {
	'$el'?: any
	'id'?: P
	'label'?: string
	'placeholder'?: string
	'class'?: AttributeValue | AttributeFn<T, P>
	'attrs'?: Attributes<T, P>
	'required'?: boolean
	'disabled'?: boolean
	'helptext'?: string
	'options'?: BagelFieldOptions<T, P>
	'children'?: SchemaChild<T, P, PO>[]
	'slots'?: { [key: string]: SchemaChild<T, P, PO>[] }
	'defaultValue'?: any
	'vIf'?: VIfType<T, P>
	'v-if'?: VIfType<T, P>
	'transform'?: (val?: FieldVal<T, P>, rowData?: T) => any
	'onUpdate'?: (val?: FieldVal<T, P>, rowData?: T) => unknown
	'validate'?: ValidationFn<T, P>
}

export type _MappedBaseBagelField<
	T,
	PO extends PathsOptions = DefaultPathsOptions,
> = {
	[P in Path<T, PO>]: BaseBagelField<T, P, PO>
}

export type MappedBaseBagelFieldP<
	T,
	P extends Path<T, PO>,
	PO extends PathsOptions = DefaultPathsOptions,
> = _MappedBaseBagelField<T, PO>[P]

export type FieldByP<
	T,
	P extends Path<T, PO>,
	PO extends PathsOptions = DefaultPathsOptions,
> = MappedBaseBagelFieldP<T, P, PO>

export type Field<
	T,
	PO extends PathsOptions = DefaultPathsOptions,
> = MappedBaseBagelFieldP<T, Path<T, PO>, PO>

export type BglFieldT<
	T,
	PO extends PathsOptions = DefaultPathsOptions,
> = Field<T, PO>

export type BglFormSchemaT<
	T,
	PO extends PathsOptions = DefaultPathsOptions,
> = Field<T, PO>[]

export type ShallowBglFormSchemaT<
	T,
	PO extends PathsOptions = ShallowPathsOptions,
> = Field<T, PO>[]

export interface InputBagelField<
	T,
	P extends Path<T, PO>,
	PO extends PathsOptions = DefaultPathsOptions
>
	extends BaseBagelField<T, P, PO> {
	$el: 'text' | ComponentExposed<typeof TextInput>
	type?: string
}

export interface SelectBagelField<
	T,
	P extends Path<T, PO>,
	PO extends PathsOptions = DefaultPathsOptions,
>
	extends BaseBagelField<T, P, PO> {
	$el: 'select' | ComponentExposed<typeof SelectInput>
	type?: string
}

export interface ValidateInputBaseT {
	validate?: ValidationFn<{ [key: string]: unknown }, string>
	getFormData?: () => any
}

export interface UploadInputProps {
	id?: string
	label?: string
	multiple?: boolean
	modelValue?: string | string[]
	width?: string
	height?: string | 'auto'
	dirPath?: string
	fill?: boolean
	oval?: boolean
	theme?: 'dropzone' | 'basic'
	accept?: string
	required?: boolean
	disabled?: boolean
	baseURL?: string
	dropPlaceholder?: string
	noFilePlaceholder?: string
	btnPlaceholder?: string
}
