<script lang="ts">
import type { FilterField, QueryOption, OptionsSource } from './FilterQuery.types'
export type { FilterField, QueryOption, OptionsSource }
</script>

<script setup lang="ts" generic="T extends Record<string, any>">
import type { ComparisonOperator, LogicalOperator, QueryConditions } from '../utils/queryFilter'
import { parseQuery } from '../utils/queryFilter'
import { Btn, DateInput, Dropdown, Icon, SelectInput, TextInput, useI18n, useDebounceFn } from '@bagelink/vue'
import { computed, getCurrentInstance, ref } from 'vue'

interface UICondition {
	id: string
	field: string
	operator: ComparisonOperator
	value: string | string[]
	connector: LogicalOperator | ''
}

const props = defineProps<{
	fields: FilterField[]
}>()

const emit = defineEmits<{
	change: [value: QueryConditions<T>]
	save: [value: QueryConditions<T>]
}>()

const model = defineModel<QueryConditions<T>>({ default: () => [] })

// Internal working copy — may contain incomplete conditions for UI purposes.
// On init, detect any queryOption presets from the incoming model.
const internalModel = ref<QueryConditions<T>>(collapseToPresets([...model.value] as QueryConditions<T>))

const { $t } = useI18n()
const instance = getCurrentInstance()
const hasSaveListener = computed(() => !!instance?.vnode.props?.onSave)

function isComplete(c: { field: string, op: ComparisonOperator, value?: any }) {
	if (!c.field || !c.op) return false
	// queryOption presets are complete if they have a selected value
	if (isQueryOptionField(c.field as string)) return c.value !== '' && c.value != null
	if (c.op === 'pr') return true
	if (c.op === 'in' || c.op === 'nin') return Array.isArray(c.value) && c.value.length > 0
	return c.value !== '' && c.value !== null && c.value !== undefined
}

const debouncedFlush = useDebounceFn(() => {
	const complete = internalModel.value.filter(isComplete) as QueryConditions<T>
	const expanded = expandQueryOptions(complete)
	model.value = expanded
	emit('change', expanded)
}, 400)

let conditionIdCounter = 0
function generateId() {
	return `cond_${++conditionIdCounter}_${Date.now()}`
}

// Computed texts using i18n
const currentTexts = computed(() => ({
	filter: $t('filter.filter'),
	noActiveFilters: $t('filter.noActiveFilters'),
	connectors: {
		and: $t('filter.connectors.and'),
		or: $t('filter.connectors.or'),
	},
	operators: {
		eq: $t('filter.operators.eq'),
		ne: $t('filter.operators.ne'),
		gt: $t('filter.operators.gt'),
		ge: $t('filter.operators.ge'),
		lt: $t('filter.operators.lt'),
		le: $t('filter.operators.le'),
		co: $t('filter.operators.co'),
		sw: $t('filter.operators.sw'),
		ew: $t('filter.operators.ew'),
		pr: $t('filter.operators.pr'),
		in: $t('filter.operators.in'),
		nin: $t('filter.operators.nin'),
	},
	placeholders: {
		selectField: $t('filter.placeholders.selectField'),
		selectOption: $t('filter.placeholders.selectOption'),
		selectDate: $t('filter.placeholders.selectDate'),
		enterValue: $t('filter.placeholders.enterValue'),
	},
	buttons: {
		addCondition: $t('filter.buttons.addCondition'),
		clearAll: $t('filter.buttons.clearAll'),
	},
	boolean: {
		true: $t('select.yes'),
		false: $t('select.no'),
	},
}))

// Map model conditions to UI conditions with stable IDs
const conditionIds = new Map<number, string>()

const conditions = computed(() => {
	const arr = internalModel.value || []
	return arr.map((cond, index) => {
		// Get or create stable ID for this index
		if (!conditionIds.has(index)) {
			conditionIds.set(index, generateId())
		}
		const id = conditionIds.get(index)!

		const uiCond: UICondition = {
			id,
			field: cond.field as string,
			operator: cond.op,
			value: Array.isArray(cond.value)
				? cond.value.map(String)
				: (cond.value === null ? 'null' : cond.value === undefined ? '' : String(cond.value)),
			connector: index > 0 ? (cond.connector || 'and') : '',
		}
		return uiCond
	})
})

const allOperatorOptions = computed(() => [
	{ label: currentTexts.value.operators.eq, value: 'eq' as ComparisonOperator },
	{ label: currentTexts.value.operators.ne, value: 'ne' as ComparisonOperator },
	{ label: currentTexts.value.operators.gt, value: 'gt' as ComparisonOperator },
	{ label: currentTexts.value.operators.ge, value: 'ge' as ComparisonOperator },
	{ label: currentTexts.value.operators.lt, value: 'lt' as ComparisonOperator },
	{ label: currentTexts.value.operators.le, value: 'le' as ComparisonOperator },
	{ label: currentTexts.value.operators.co, value: 'co' as ComparisonOperator },
	{ label: currentTexts.value.operators.sw, value: 'sw' as ComparisonOperator },
	{ label: currentTexts.value.operators.ew, value: 'ew' as ComparisonOperator },
	{ label: currentTexts.value.operators.pr, value: 'pr' as ComparisonOperator },
	{ label: currentTexts.value.operators.in, value: 'in' as ComparisonOperator },
	{ label: currentTexts.value.operators.nin, value: 'nin' as ComparisonOperator },
])

function getOperatorsForType(fieldType: string, hasOptions: boolean, isMultiple: boolean) {
	if (isMultiple && hasOptions) {
		return allOperatorOptions.value.filter(o => ['in', 'nin', 'pr'].includes(o.value))
	}
	if (hasOptions) {
		return allOperatorOptions.value.filter(o => ['eq', 'ne', 'pr'].includes(o.value))
	}
	switch (fieldType) {
		case 'boolean':
			return allOperatorOptions.value.filter(o => ['eq', 'ne', 'pr'].includes(o.value))
		case 'number':
		case 'date':
			return allOperatorOptions.value.filter(o => ['eq', 'ne', 'gt', 'ge', 'lt', 'le', 'pr'].includes(o.value))
		default:
			return allOperatorOptions.value.filter(o => ['eq', 'ne', 'co', 'sw', 'ew', 'pr'].includes(o.value))
	}
}

function getFieldDef(fieldValue: string) {
	return props.fields.find(f => f.value === fieldValue)
}

function getFieldOptions(fieldValue: string): OptionsSource | undefined {
	return getFieldDef(fieldValue)?.options
}

function isQueryOptionField(fieldValue: string): boolean {
	const def = getFieldDef(fieldValue)
	return !!(def?.queryOptions && def.queryOptions.length > 0)
}

function isMultipleField(fieldValue: string): boolean {
	const def = getFieldDef(fieldValue)
	return !!(def?.multiple && def?.options)
}

/** On flush: replace queryOption markers with parsed conditions from the raw SCIM string */
function expandQueryOptions(conditions: QueryConditions<T>): QueryConditions<T> {
	const result: QueryConditions<T> = []
	for (const c of conditions) {
		const def = getFieldDef(c.field as string)
		const opt = def?.queryOptions?.find(o => o.value === c.value)
		if (opt) {
			const parsed = parseQuery(opt.query) as QueryConditions<T>
			if (parsed.length) {
				// Preserve the original connector on the first parsed condition
				parsed[0] = { ...parsed[0], connector: c.connector }
				result.push(...parsed)
			}
		} else {
			result.push(c)
		}
	}
	return result
}

/** On init: detect consecutive conditions that match a queryOption's SCIM string and collapse to a single marker */
function collapseToPresets(conditions: QueryConditions<T>): QueryConditions<T> {
	const queryOptionFields = props.fields.filter(f => f.queryOptions?.length)
	if (!queryOptionFields.length) return conditions

	const result: QueryConditions<T> = []
	let i = 0
	while (i < conditions.length) {
		let matched = false
		for (const fieldDef of queryOptionFields) {
			for (const opt of fieldDef.queryOptions!) {
				const parsed = parseQuery(opt.query)
				const qLen = parsed.length
				if (!qLen || i + qLen > conditions.length) continue
				const slice = conditions.slice(i, i + qLen)
				const matches = slice.every((c, idx) => {
					const expected = parsed[idx]
					return c.field === expected.field
						&& c.op === expected.op
						&& c.value === expected.value
						&& (idx === 0 || c.connector === expected.connector)
				})
				if (matches) {
					result.push({
						field: fieldDef.value,
						op: 'eq',
						value: opt.value,
						connector: slice[0].connector,
					} as any)
					i += qLen
					matched = true
					break
				}
			}
			if (matched) break
		}
		if (!matched) {
			result.push(conditions[i])
			i++
		}
	}
	return result
}

const booleanOptions = computed(() => [
	{ label: currentTexts.value.boolean.true, value: 'true' },
	{ label: currentTexts.value.boolean.false, value: 'false' },
])

function parseValue(value: string | number | boolean | null): string | number | boolean | null {
	if (typeof value === 'number' || typeof value === 'boolean' || value === null) return value
	if (value === 'true') return true
	if (value === 'false') return false
	if (value === 'null') return null
	if (!Number.isNaN(Number(value)) && value.trim() !== '') return Number(value)
	return value
}

function updateConditionAtIndex(index: number, updates: Partial<{ field: string, op: ComparisonOperator, value: string | number | boolean | null | string[], connector: LogicalOperator }>) {
	const newModel = [...internalModel.value]
	const existing = newModel[index]
	if (!existing) return

	const updated = { ...existing }
	if (updates.field !== undefined) updated.field = updates.field
	if (updates.op !== undefined) updated.op = updates.op
	if (updates.value !== undefined) updated.value = Array.isArray(updates.value) ? updates.value : parseValue(updates.value)
	if (updates.connector !== undefined) updated.connector = updates.connector

	newModel[index] = updated
	internalModel.value = newModel as QueryConditions<T>
	debouncedFlush()
}

function addCondition() {
	const field = props.fields[0]?.value || 'field'
	const multi = isMultipleField(field)
	const newCondition = {
		field,
		op: (multi ? 'in' : 'eq') as ComparisonOperator,
		value: (multi ? [] : '') as any,
		connector: model.value.length > 0 ? 'and' as LogicalOperator : undefined,
	}
	const newModel = [...internalModel.value, newCondition]
	internalModel.value = newModel as QueryConditions<T>
	console.log('[Filter] addCondition - skipping flush (incomplete)')
}

function removeCondition(index: number) {
	const newModel = [...internalModel.value]
	newModel.splice(index, 1)
	if (newModel.length > 0 && newModel[0].connector) {
		newModel[0] = { ...newModel[0], connector: undefined }
	}
	conditionIds.delete(index)
	internalModel.value = newModel as QueryConditions<T>
	debouncedFlush()
}

function clearAll() {
	conditionIds.clear()
	internalModel.value = [] as unknown as QueryConditions<T>
	model.value = [] as unknown as QueryConditions<T>
	console.log('[Filter] clearAll')
	emit('change', [] as unknown as QueryConditions<T>)
}

function getFieldType(fieldValue: string): string {
	return props.fields.find(f => f.value === fieldValue)?.type || 'string'
}

function needsValue(operator: ComparisonOperator): boolean {
	return operator !== 'pr'
}

function isMultiOperator(operator: ComparisonOperator): boolean {
	return operator === 'in' || operator === 'nin'
}

const activeFiltersCount = computed(() => {
	return conditions.value.filter((c: UICondition) => Boolean(c.field) && (c.operator === 'pr' || Boolean(c.value))).length
})

function getConditionIndex(id: string): number {
	return conditions.value.findIndex((c: UICondition) => c.id === id)
}

function onFieldChange(id: string, newField: string) {
	const index = getConditionIndex(id)
	if (index < 0) return
	const fieldDef = getFieldDef(newField)
	if (fieldDef?.queryOptions?.length) {
		updateConditionAtIndex(index, { field: newField, op: 'eq', value: '' })
		return
	}
	const multi = !!(fieldDef?.multiple && fieldDef?.options)
	const validOps = getOperatorsForType(fieldDef?.type || 'string', !!fieldDef?.options, multi)
	const defaultOp = (multi ? 'in' : validOps[0]?.value ?? 'eq') as ComparisonOperator
	updateConditionAtIndex(index, {
		field: newField,
		op: defaultOp,
		value: multi ? [] : '',
	})
}

function onOperatorChange(id: string, operator: ComparisonOperator) {
	const index = getConditionIndex(id)
	if (index < 0) return
	const current = conditions.value[index]
	const wasMulti = current?.operator === 'in' || current?.operator === 'nin'
	const isMulti = operator === 'in' || operator === 'nin'
	const updates: Parameters<typeof updateConditionAtIndex>[1] = { op: operator }
	if (wasMulti && !isMulti) updates.value = ''
	else if (!wasMulti && isMulti) updates.value = []
	updateConditionAtIndex(index, updates)
}

function onValueChange(id: string, value: string | string[]) {
	const index = getConditionIndex(id)
	if (index >= 0) updateConditionAtIndex(index, { value })
}

function onQueryOptionChange(id: string, optionValue: string) {
	const index = getConditionIndex(id)
	if (index >= 0) updateConditionAtIndex(index, { value: optionValue })
}

function getQueryOptionValue(condition: UICondition): string {
	return Array.isArray(condition.value) ? '' : condition.value
}

function onConnectorChange(id: string, connector: LogicalOperator) {
	const index = getConditionIndex(id)
	if (index >= 0) updateConditionAtIndex(index, { connector })
}
</script>

<template>
	<Dropdown
		flat placement="bottom-end" icon="filter_alt"
		:value="activeFiltersCount > 0 ? `${currentTexts.filter} (${activeFiltersCount})` : currentTexts.filter" thin
	>
		<div class="p-1 m_p-05">
			<div class="p-025 bg-bg radius-1 mb-05 m_max-h-300px m_overflow m_border">
				<!-- Empty state -->
				<div v-if="conditions.length === 0" class="opacity-3 flex gap-05 p-025 min-w-450px m_min-0">
					<Icon name="info" />
					<div class="">
						{{ currentTexts.noActiveFilters }}
					</div>
				</div>
				<TransitionGroup name="condition">
					<div
						v-for="(condition, index) in conditions" :key="condition.id"
						class="grid gap-025 align-items-center pt-025" :class="{
							'': index > 0 && condition.connector === 'or',
							'pt-075 border-top-or': index > 0 && condition.connector === 'or',
							'first-filter-row': index === 0,
							'filter-row': index > 0,
						}"
					>
						<!-- Connector (AND/OR) -->
						<div v-if="index > 0" class="w-fit">
							<SelectInput
								:model-value="condition.connector || 'and'" :options="[
									{ label: currentTexts.connectors.and, value: 'and' },
									{ label: currentTexts.connectors.or, value: 'or' },
								]" class="m-0 and-or-select txt-12"
								@update:model-value="(v: LogicalOperator) => onConnectorChange(condition.id, v)"
							/>
						</div>
						<!-- <div v-else /> -->

						<!-- Field selector -->
					<SelectInput
						:model-value="condition.field" :options="fields"
						:placeholder="currentTexts.placeholders.selectField" class="m-0 light-input borderHover" searchable
						@update:model-value="(v: string) => onFieldChange(condition.id, v)"
					/>

						<!-- Query-option preset: single select spanning operator+value columns -->
						<template v-if="isQueryOptionField(condition.field)">
							<SelectInput
								:key="`${condition.id}-${condition.field}-qpreset`"
								:model-value="getQueryOptionValue(condition)"
								:options="getFieldDef(condition.field)!.queryOptions!"
								:placeholder="currentTexts.placeholders.selectOption"
								class="m-0 light-input borderHover" style="grid-column: span 2"
								@update:model-value="(v: string) => onQueryOptionChange(condition.id, v)"
							/>
						</template>

						<!-- Standard: Operator + Value -->
						<template v-else>
						<!-- Operator selector -->
						<SelectInput
							:model-value="condition.operator"
							:options="getOperatorsForType(getFieldType(condition.field), !!getFieldOptions(condition.field), isMultipleField(condition.field))"
							class="m-0 light-input borderHover"
							@update:model-value="(v: ComparisonOperator) => onOperatorChange(condition.id, v)"
						/>

						<!-- Value input - type-specific -->
						<template v-if="needsValue(condition.operator)">
							<SelectInput
								v-if="getFieldOptions(condition.field) && isMultiOperator(condition.operator)"
								:key="`${condition.id}-${condition.field}-multi`"
								:model-value="Array.isArray(condition.value) ? condition.value : []"
								:options="getFieldOptions(condition.field)!"
								:placeholder="currentTexts.placeholders.selectOption"
								class="m-0 light-input borderHover" searchable multiselect
								@update:model-value="(v: string[]) => onValueChange(condition.id, v)"
							/>
							<SelectInput
								v-else-if="getFieldOptions(condition.field)"
								:key="`${condition.id}-${condition.field}-single`"
								:model-value="condition.value"
								:options="getFieldOptions(condition.field)!"
								:placeholder="currentTexts.placeholders.selectOption"
								class="m-0 light-input borderHover" searchable
								@update:model-value="(v: string) => onValueChange(condition.id, v)"
							/>
							<SelectInput
								v-else-if="getFieldType(condition.field) === 'boolean'"
								:key="`${condition.id}-${condition.field}-bool`"
								:model-value="condition.value" :options="booleanOptions"
								:placeholder="currentTexts.placeholders.selectOption"
								class="m-0 light-input borderHover"
								@update:model-value="(v: string) => onValueChange(condition.id, v)"
							/>
							<DateInput
								v-else-if="getFieldType(condition.field) === 'date'"
								:key="`${condition.id}-${condition.field}-date`"
								:model-value="(condition.value as string)"
								:placeholder="currentTexts.placeholders.selectDate" class="m-0 light-input borderHover"
								@update:model-value="(v: any) => onValueChange(condition.id, String(v))"
							/>
							<TextInput
								v-else-if="getFieldType(condition.field) === 'number'"
								:key="`${condition.id}-${condition.field}-number`"
								:model-value="(condition.value as string)" placeholder="0" type="number"
								class="m-0 light-input borderHover"
								@update:model-value="(v: string) => onValueChange(condition.id, v)"
							/>
							<TextInput
								v-else :model-value="(condition.value as string)"
								:placeholder="currentTexts.placeholders.enterValue" class="m-0 light-input borderHover"
								@update:model-value="(v: string) => onValueChange(condition.id, v)"
							/>
						</template>
						<div v-else />
						</template>

						<!-- Remove button -->
						<Btn
							icon="close" thin flat size="sm" class="m_bg-gray-40 m_px-3 m_mx-auto"
							@click="removeCondition(index)"
						/>
					</div>
				</TransitionGroup>
			</div>

			<div class="flex gap-1 space-between m_flex-wrap">
				<Btn icon="add" flat thin :value="currentTexts.buttons.addCondition" @click="addCondition" />
				<div class="flex gap-05">
					<Btn
						v-if="conditions.length > 0" icon="delete_sweep" flat thin color="gray"
						:value="currentTexts.buttons.clearAll" @click="clearAll"
					/>
					<Btn
						v-if="hasSaveListener" :disabled="conditions.length === 0" class="color-green" icon="save" thin
						color="green-light" :value="$t('filter.buttons.save')"
						@click="emit('save', internalModel.filter(isComplete))"
					/>
				</div>
			</div>
		</div>
	</Dropdown>
</template>

<style>

.and-or-select {
--bgl-input-font-size: 12px !important;
--input-background-color: transparent !important;
}

.and-or-select .selectinput-btn {
border: none;
width: 100%;
box-shadow: none;
padding: 0 0.5rem !important;
font-size: 12px !important;
width: 100%;
}

.first-filter-row {
grid-template-columns: minmax(calc(140px + 0.25rem), 0.5fr) minmax(170px, 0.75fr) minmax(120px, 0.5fr) auto;
padding-top: 0 !important;
}

.filter-row {
grid-template-columns: 50px minmax(90px, 0.5fr) minmax(170px, 0.75fr) minmax(120px, 0.5fr) auto;
}

/* Transition animations */
.condition-enter-active,
.condition-leave-active {
transition: all 0.25s ease;
}

.condition-enter-from {
opacity: 0;
transform: translateX(-10px);
}

.condition-leave-to {
opacity: 0;
transform: translateX(10px);
}

:dir(rtl) .condition-enter-from {
transform: translateX(10px);
}

:dir(rtl) .condition-leave-to {
transform: translateX(-10px);
}

@media (max-width: 910px) {

.filter-row,
.first-filter-row {
	grid-template-columns: auto;
}
}

</style>

<style>

.borderHover * {
transition: all 0.2s ease-in-out !important;
}

.borderHover:hover {
--bgl-border-color: var(--bgl-primary);
}

</style>
