<script setup lang="ts">
import { Btn, Card, CheckInput, NumberInput, SelectBtn, SelectInput, TextInput } from '@bagelink/vue'
import { computed, ref, watch } from 'vue'

/**
 * JSON Schema Draft 2020-12 Types
 * @see https://json-schema.org/draft/2020-12/json-schema-core.html
 */

type JSONSchemaType = 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array' | 'null'

interface JSONSchemaProperty {
	type?: JSONSchemaType | JSONSchemaType[]
	title?: string
	description?: string
	default?: unknown
	enum?: unknown[]
	const?: unknown
	// String validations
	minLength?: number
	maxLength?: number
	pattern?: string
	format?: string
	// Number validations
	minimum?: number
	maximum?: number
	exclusiveMinimum?: number
	exclusiveMaximum?: number
	multipleOf?: number
	// Array validations
	items?: JSONSchema
	minItems?: number
	maxItems?: number
	uniqueItems?: boolean
	// Object validations
	properties?: Record<string, JSONSchema>
	required?: string[]
	additionalProperties?: boolean | JSONSchema
	// Composition
	allOf?: JSONSchema[]
	anyOf?: JSONSchema[]
	oneOf?: JSONSchema[]
	not?: JSONSchema
}

interface JSONSchema extends JSONSchemaProperty {
	$schema?: string
	$id?: string
	$ref?: string
	$defs?: Record<string, JSONSchema>
}

interface Props {
	modelValue?: JSONSchema
	level?: number
	propertyName?: string
	minimal?: boolean
}

const props = withDefaults(defineProps<Props>(), {
	modelValue: () => ({}),
	level: 0,
	minimal: true,
	propertyName: '',
})

const emit = defineEmits<{
	'update:modelValue': [value: JSONSchema]
}>()

const schema = ref<JSONSchema>({ ...props.modelValue })

// Top-level only fields
const schemaVersion = ref(schema.value.$schema ?? 'https://json-schema.org/draft/2020-12/schema')
const schemaId = ref(schema.value.$id ?? '')

// Basic fields
const type = ref<JSONSchemaType>(
	Array.isArray(schema.value.type) ? schema.value.type[0] : (schema.value.type ?? 'object')
)
const title = ref(schema.value.title ?? '')
const description = ref(schema.value.description ?? '')
const required = ref<string[]>(schema.value.required ?? [])

// String validations
const minLength = ref<number | undefined>(schema.value.minLength)
const maxLength = ref<number | undefined>(schema.value.maxLength)
const pattern = ref(schema.value.pattern ?? '')
const format = ref(schema.value.format ?? '')

// Number validations
const minimum = ref<number | undefined>(schema.value.minimum)
const maximum = ref<number | undefined>(schema.value.maximum)
const multipleOf = ref<number | undefined>(schema.value.multipleOf)

// Array validations
const minItems = ref<number | undefined>(schema.value.minItems)
const maxItems = ref<number | undefined>(schema.value.maxItems)
const uniqueItems = ref(schema.value.uniqueItems ?? false)

// Object properties
const properties = ref<Record<string, JSONSchema>>(schema.value.properties ?? {})
const additionalProperties = ref(
	schema.value.additionalProperties !== undefined ? schema.value.additionalProperties : true
)

// Array items
const items = ref<JSONSchema | undefined>(schema.value.items)

const isRootLevel = computed(() => props.level === 0)
const showStringValidations = computed(() => type.value === 'string')
const showNumberValidations = computed(() => type.value === 'number' || type.value === 'integer')
const showArrayValidations = computed(() => type.value === 'array')
const showObjectValidations = computed(() => type.value === 'object')

const propertyKeys = computed(() => Object.keys(properties.value))

function updateSchema() {
	const newSchema: JSONSchema = {
		type: type.value,
	}

	// Top-level fields
	if (isRootLevel.value) {
		if (schemaVersion.value) newSchema.$schema = schemaVersion.value
		if (schemaId.value) newSchema.$id = schemaId.value
	}

	// Basic fields
	if (title.value !== null && title.value !== undefined && title.value !== '') newSchema.title = title.value
	if (description.value !== null && description.value !== undefined && description.value !== '') newSchema.description = description.value

	// String validations
	if (showStringValidations.value) {
		if (minLength.value !== undefined) newSchema.minLength = minLength.value
		if (maxLength.value !== undefined) newSchema.maxLength = maxLength.value
		if (pattern.value !== null && pattern.value !== undefined && pattern.value !== '') newSchema.pattern = pattern.value
		if (format.value !== null && format.value !== undefined && format.value !== '') newSchema.format = format.value
	}

	// Number validations
	if (showNumberValidations.value) {
		if (minimum.value !== undefined) newSchema.minimum = minimum.value
		if (maximum.value !== undefined) newSchema.maximum = maximum.value
		if (multipleOf.value !== undefined) newSchema.multipleOf = multipleOf.value
	}

	// Array validations
	if (showArrayValidations.value) {
		if (minItems.value !== undefined) newSchema.minItems = minItems.value
		if (maxItems.value !== undefined) newSchema.maxItems = maxItems.value
		if (uniqueItems.value === true) newSchema.uniqueItems = uniqueItems.value
		if (items.value !== undefined && items.value !== null) newSchema.items = items.value
	}

	// Object validations
	if (showObjectValidations.value) {
		if (Object.keys(properties.value).length > 0) newSchema.properties = properties.value
		if (required.value.length > 0) newSchema.required = required.value
		if (additionalProperties.value !== true) newSchema.additionalProperties = additionalProperties.value
	}

	schema.value = newSchema
	emit('update:modelValue', newSchema)
}

function addProperty() {
	const newKey = `property_${Object.keys(properties.value).length + 1}`
	properties.value[newKey] = { type: 'string' }
	updateSchema()
}

function removeProperty(key: string) {
	const { [key]: _removed, ...newProps } = properties.value
	properties.value = newProps
	required.value = required.value.filter(r => r !== key)
	updateSchema()
}

function updatePropertySchema(key: string, newSchema: JSONSchema) {
	properties.value[key] = newSchema
	updateSchema()
}

function toggleRequired(key: string) {
	const index = required.value.indexOf(key)
	if (index === -1) {
		required.value.push(key)
	} else {
		required.value.splice(index, 1)
	}
	updateSchema()
}

function updateArrayItems(newSchema: JSONSchema) {
	items.value = newSchema
	updateSchema()
}

// Initialize from modelValue
watch(() => props.modelValue, (newValue) => {
	if (newValue !== null && newValue !== undefined && Object.keys(newValue).length > 0) {
		schema.value = { ...newValue }
		type.value = Array.isArray(schema.value.type) ? schema.value.type[0] : (schema.value.type ?? 'object')
		title.value = schema.value.title ?? ''
		description.value = schema.value.description ?? ''
		required.value = schema.value.required ?? []
		minLength.value = schema.value.minLength
		maxLength.value = schema.value.maxLength
		pattern.value = schema.value.pattern ?? ''
		format.value = schema.value.format ?? ''
		minimum.value = schema.value.minimum
		maximum.value = schema.value.maximum
		multipleOf.value = schema.value.multipleOf
		minItems.value = schema.value.minItems
		maxItems.value = schema.value.maxItems
		uniqueItems.value = schema.value.uniqueItems === true
		properties.value = schema.value.properties ?? {}
		additionalProperties.value = schema.value.additionalProperties !== undefined
			? schema.value.additionalProperties
			: true
		items.value = schema.value.items
		schemaVersion.value = schema.value.$schema ?? 'https://json-schema.org/draft/2020-12/schema'
		schemaId.value = schema.value.$id ?? ''
	}
}, { immediate: true })
</script>

<template>
	<Card class="bg-input JSONSchema p-0 overflow-hidden testMe1">
		<!-- :style="{ 'margin-inline-start': level > 0 ? '1rem' : '0' }" -->
		<!-- Root level metadata -->
		<div v-if="isRootLevel && !minimal" class="mb-1 p-1 border rounded bg-white">
			<TextInput
				v-model="schemaVersion"
				label="$schema"
				placeholder="https://json-schema.org/draft/2020-12/schema"
				@update:model-value="updateSchema"
			/>

			<TextInput
				v-model="schemaId"
				label="$id"
				placeholder="https://example.com/schema.json"
				@update:model-value="updateSchema"
			/>
		</div>

		<!-- Property name (for nested schemas) -->
		<p v-if="propertyName && level > 0" class="txt-12 py-025 px-1 alpha-10 bg-black">
			{{ propertyName }}
		</p>
		<SelectBtn
			v-model="type" class="mb-05 jsonFormat border-bottom p-1" label="Type"
			:options="[
				{ label: 'String', value: 'string', icon: 'title' },
				{ label: 'Number', value: 'number', icon: 'numbers' },
				{ label: 'Integer', value: 'integer', icon: 'timer_1' },
				{ label: 'Boolean', value: 'boolean', icon: 'mode_off_on' },
				{ label: 'Object', value: 'object', icon: 'data_object' },
				{ label: 'Array', value: 'array', icon: 'data_array' },
				{ label: 'Null', value: 'null', icon: 'block' },
			]" @update:model-value="updateSchema"
		/>
		<div v-if="!minimal">
			<TextInput
				v-model="title"
				placeholder="Schema title"
				label="Title"
				@update:model-value="updateSchema"
			/>
			<TextInput
				v-model="description"
				label="Description"
				multiline
				placeholder="Schema description"
				class="m-0"
				@update:model-value="updateSchema"
			/>
		</div>
		<TransitionGroup name="schema-transition">
			<!-- String validations -->
			<div v-if="showStringValidations" class="grid grid-wrap-3 gap-col-1 px-1 pb-1">
				<p class="txt-18 grid-span-3 m_grid-span-1 pb-05">
					String Constraints
				</p>
				<NumberInput
					v-model="minLength"
					label="Min Length"
					:min="0"
					class="light-input"
					@update:model-value="updateSchema"
				/>
				<NumberInput
					v-model="maxLength"
					label="Max Length"
					:min="0"
					class="light-input"
					@update:model-value="updateSchema"
				/>

				<SelectInput
					v-model="format"
					class="light-input"
					label="Format"
					:options="[{ label: 'None', value: '' }, { label: 'Date', value: 'date' }, { label: 'Time', value: 'time' }, { label: 'Date-Time', value: 'date-time' }, { label: 'Email', value: 'email' }, { label: 'URI', value: 'uri' }, { label: 'UUID', value: 'uuid' }, { label: 'IPv4', value: 'ipv4' }, { label: 'IPv6', value: 'ipv6' }]"
					@update:model-value="updateSchema"
				/>

				<TextInput
					v-model="pattern"
					placeholder="^[a-z]+$"
					label="Pattern (regex)"
					class="light-input grid-span-3 m_grid-span-1"
					@update:model-value="updateSchema"
				/>
			</div>

			<!-- Number validations -->
			<transition name="number-section" mode="out-in" class="min-h-100px">
				<div v-if="showNumberValidations" :key="type" class="grid grid-wrap-3 gap-col-1 px-1 pb-1">
					<p class="txt-18 grid-span-3 m_grid-span-1 pb-05">
						{{ type === 'integer' ? 'Integer Constraints' : 'Number Constraints' }}
					</p>
					<NumberInput
						v-model="minimum"
						label="Minimum"
						:min="0"
						class="light-input"
						@update:model-value="updateSchema"
					/>
					<NumberInput
						v-model="maximum"
						label="Maximum"
						:min="0"
						class="light-input"
						@update:model-value="updateSchema"
					/>
					<NumberInput
						v-model="multipleOf"
						label="Multiple Of"
						:min="0"
						class="light-input"
						@update:model-value="updateSchema"
					/>
				</div>
			</transition>

			<!-- Array validations -->
			<div v-if="showArrayValidations" class="grid grid-wrap-3 gap-col-1 px-1 pb-1">
				<p class="txt-18 grid-span-3 m_grid-span-1 pb-05">
					Array Constraints
				</p>
				<NumberInput
					v-model="minItems"
					label="Min Items"
					:min="0"
					class="light-input"
					@update:model-value="updateSchema"
				/>
				<NumberInput
					v-model="maxItems"
					label="Max Items"
					:min="0"
					class="light-input"
					@update:model-value="updateSchema"
				/>
				<CheckInput
					v-model="uniqueItems"
					label="Unique Items"
					class="mt-1 jsonCheckbox"
					@update:model-value="updateSchema"
				/>
				<div class="grid-span-3 m_grid-span-1 mt-1 border bg-white p-1 rounded arrayJsonSchema">
					<div class="txt-14">
						Items Schema
					</div>
					<div>
						<JSONSchema
							:model-value="items || { type: 'string' }"
							:level="level + 1"
							@update:model-value="updateArrayItems"
						/>
					</div>
				</div>
			</div>

			<!-- Boolean validations -->
			<div v-if="type === 'boolean'" class="px-1 pb-1">
				<p class="txt-18 pb-05">
					Boolean Constraints
				</p>
				<p class="txt-12 opacity-5">
					Boolean type has no additional validations.
				</p>
			</div>

			<!-- Object properties -->
			<div v-if="showObjectValidations" class="px-1 pb-1">
				<div class="flex space-between pb-05 m_flex-wrap gap-05">
					<p class="txt-18">
						Object Properties
					</p>
					<CheckInput
						:model-value="additionalProperties === true"
						label="Allow Additional Properties"
						class="mb-0 jsonCheckbox w-240px"
						@update:model-value="updateSchema"
					/>
				</div>

				<div v-if="propertyKeys.length === 0" class="txt-center txt-12 txt-gray-50 py-5 border rounded mb-1 bg-white">
					No properties defined
				</div>

				<div v-for="key in propertyKeys" :key="key" class="mb-1 p-05 bg-white rounded border">
					<div class="flex gap-05 mb-05">
						<TextInput
							:model-value="key"
							class="m-0"
						/>
						<CheckInput class="mb-0 jsonCheckbox" :model-value="required.includes(key)" label="Required" @update:model-value="toggleRequired(key)" />
						<Btn
							icon="close"
							size="s"
							@click="removeProperty(key)"
						/>
					</div>
					<JSONSchema
						:model-value="properties[key]"
						:level="level + 1"
						:property-name="key"
						@update:model-value="(newSchema: JSONSchema) => updatePropertySchema(key, newSchema)"
					/>
				</div>
				<Btn
					value="Add Property"
					icon="add"
					size="s"
					color="purple"
					@click="addProperty"
				/>
			</div>

			<!-- Null validations -->
			<div v-if="type === 'null'" class="px-1 pb-1">
				<p class="txt-18 pb-05">
					Null Constraints
				</p>
				<p class="txt-12 opacity-5">
					Null type has no additional validations.
				</p>
			</div>
		</TransitionGroup>
	</Card>
</template>

<style>
.JSONSchema{
	--input-height: 38px;
	--bgl-accent-color: var(--bgl-purple);
	container-type: inline-size;
	container-name: json-schema;
}
.jsonCheckbox{
		--input-height: 44px;
}
.jsonFormat {
	gap: 0.25rem !important;
	display: grid !important;
	grid-template-columns: repeat(auto-fit, minmax(50px, 1fr)) !important;
}
.jsonFormat .bgl_btn-flex{
	flex-direction: column;
}
.jsonFormat .bgl_btn{
	height: auto !important;
	font-size: 9px !important;
	line-height: 0.75 !important;
	border-radius: 0.5rem !important;
	padding: 0.5rem !important;
	background: var(--bgl-purple);
}
.jsonFormat .bgl_icon-font{
	font-size: 1.6rem !important;
}
.arrayJsonSchema .txt-18{
	font-size: 14px !important;
}

@container json-schema (max-width: 400px) {
	.JSONSchema .grid-wrap-3 {
		grid-template-columns: 1fr !important;
	}

	.JSONSchema .grid-span-3 {
		grid-column: 1 !important;
	}
}

/* transtion animations */

/* Schema transition animations */
.schema-transition-enter-active,
.schema-transition-leave-active {
	transition: all 0.4s ease;
	position: relative;
}

.schema-transition-enter-from {
	opacity: 0;
	transform: translateX(30px);
}

.schema-transition-leave-to {
	opacity: 0;
	transform: translateX(-30px);
}

.schema-transition-leave-active {
	position: absolute;
	width: 100%;
	z-index: 0;
}

.schema-transition-enter-active {
	z-index: 1;
}

/* Number section transition for number/integer switch */
.number-section-enter-active,
.number-section-leave-active {
	transition: all 0.25s ease;
	position: relative;
}

.number-section-enter-from {
	opacity: 0;
	transform: translateX(20px);
}

.number-section-leave-to {
	opacity: 0;
	transform: translateX(-20px);
}

.number-section-leave-active {
	position: absolute;
	width: 100%;
	z-index: 0;
}

.number-section-enter-active {
	z-index: 1;
}
</style>
