<template>
	<div class="query-builder">
		<div class="query-builder__group"
			:class="[
				'query-builder__group--depth-' + depth,
				{ 'query-builder__group--negated': localNot },
			]">
			<!-- Open bracket (visible for nested groups) -->
			<span v-if="depth > 0" class="query-builder__bracket query-builder__bracket--open">(</span>

			<!-- Group header -->
			<div class="query-builder__group-header">
				<div class="query-builder__group-toggles">
					<div class="query-builder__logic-toggle">
						<button :class="['query-builder__logic-btn', { 'query-builder__logic-btn--active': localLogic === 'and' }]"
							type="button"
							@click="setLogic('and')">
							UND
						</button>
						<button :class="['query-builder__logic-btn', { 'query-builder__logic-btn--active': localLogic === 'or' }]"
							type="button"
							@click="setLogic('or')">
							ODER
						</button>
					</div>
					<button :class="['query-builder__not-btn', { 'query-builder__not-btn--active': localNot }]"
						type="button"
						:title="localNot ? 'Negation aktiv — Klick zum Aufheben' : 'Gesamte Gruppe negieren'"
						@click="toggleNot">
						NICHT
					</button>
				</div>
				<div class="query-builder__group-actions">
					<NcButton type="secondary" @click="addCondition">
						<template #icon>
							<Plus :size="20" />
						</template>
						Bedingung
					</NcButton>
					<NcButton type="tertiary" @click="addGroup">
						<template #icon>
							<CodeBrackets :size="20" />
						</template>
						Gruppe
					</NcButton>
					<NcButton v-if="depth > 0"
						type="tertiary"
						:disabled="!canUnwrap"
						:title="unwrapHint"
						@click="unwrapGroup">
						<template #icon>
							<CloseBrackets :size="20" />
						</template>
						Klammer auflösen
					</NcButton>
					<NcButton v-if="depth > 0" type="error" @click="$emit('remove')">
						<template #icon>
							<Delete :size="20" />
						</template>
					</NcButton>
				</div>
			</div>

			<!-- Conditions -->
			<div class="query-builder__conditions">
				<div v-for="(item, index) in localConditions"
					:key="index"
					class="query-builder__condition-row">
					<!-- Nested group -->
					<QueryBuilder v-if="isGroupNode(item)"
						:model-value="item"
						:fields="fields"
						:depth="depth + 1"
						:parent-logic="localLogic"
						@update:model-value="updateCondition(index, $event)"
						@remove="removeCondition(index)" />

					<!-- Leaf condition -->
					<div v-else class="query-builder__leaf" :class="{ 'query-builder__leaf--negated': !!item.not }">
						<button :class="['query-builder__not-leaf-btn', { 'query-builder__not-leaf-btn--active': !!item.not }]"
							type="button"
							:title="item.not ? 'Bedingung ist negiert' : 'Bedingung negieren'"
							@click="toggleLeafNot(index)">
							¬
						</button>
						<select :value="item.field"
							class="query-builder__select"
							@change="updateLeafField(index, $event.target.value)">
							<option value="" disabled>Feld...</option>
							<optgroup v-for="(group, gName) in groupedFields" :key="gName" :label="gName">
								<option v-for="f in group"
									:key="f.key"
									:value="f.key">
									{{ f.label }}
								</option>
							</optgroup>
						</select>

						<select :value="item.op"
							class="query-builder__select query-builder__select--op"
							@change="updateLeafOp(index, $event.target.value)">
							<option v-for="op in operatorsForField(item.field)" :key="op.value" :value="op.value">
								{{ op.label }}
							</option>
						</select>

						<input v-if="!['is_empty', 'is_not_empty'].includes(item.op)"
							:value="item.value"
							:type="inputTypeForField(item.field)"
							class="query-builder__input"
							placeholder="Wert..."
							@input="updateLeafValue(index, $event.target.value)">

						<span v-if="isEncryptedField(item.field) && !['is_empty', 'is_not_empty'].includes(item.op)"
							class="query-builder__encrypted-badge"
							title="Feld ist verschlüsselt. Abfrage entschlüsselt serverseitig.">
							🔒
						</span>

						<NcButton type="error"
							class="query-builder__remove-btn"
							@click="removeCondition(index)">
							<template #icon>
								<Close :size="16" />
							</template>
						</NcButton>
					</div>
				</div>

				<!-- Empty state -->
				<div v-if="localConditions.length === 0" class="query-builder__empty">
					Noch keine Bedingungen. Klicke "Bedingung" um eine hinzuzufügen.
				</div>
			</div>
		</div>

		<!-- Close bracket (visible for nested groups) -->
		<span v-if="depth > 0" class="query-builder__bracket query-builder__bracket--close">)</span>
	</div>
</template>

<script setup>
import { ref, watch, computed } from 'vue'
import { NcButton } from '@nextcloud/vue'
import Plus from 'vue-material-design-icons/Plus.vue'
import Close from 'vue-material-design-icons/Close.vue'
import Delete from 'vue-material-design-icons/Delete.vue'
import CodeBrackets from 'vue-material-design-icons/CodeBrackets.vue'
import CloseBrackets from 'vue-material-design-icons/CodeBracesBox.vue'

const props = defineProps({
	modelValue: {
		type: Object,
		default: () => ({ and: [] }),
	},
	fields: {
		type: Array,
		default: () => [],
	},
	depth: {
		type: Number,
		default: 0,
	},
	parentLogic: {
		type: String,
		default: null,
	},
})

const emit = defineEmits(['update:modelValue', 'remove'])

const localLogic = ref(props.modelValue.or ? 'or' : 'and')
const localConditions = ref([...(props.modelValue.and || props.modelValue.or || [])])
const localNot = ref(!!props.modelValue.not)

watch(() => props.modelValue, (newVal) => {
	localLogic.value = newVal.or ? 'or' : 'and'
	localConditions.value = [...(newVal.and || newVal.or || [])]
	localNot.value = !!newVal.not
}, { deep: true })

function emitUpdate() {
	const result = {}
	result[localLogic.value] = [...localConditions.value]
	if (localNot.value) {
		result.not = true
	}
	emit('update:modelValue', result)
}

function setLogic(logic) {
	localLogic.value = logic
	emitUpdate()
}

function toggleNot() {
	localNot.value = !localNot.value
	emitUpdate()
}

function addCondition() {
	localConditions.value.push({
		field: props.fields[0]?.key || 'vorname',
		op: '=',
		value: '',
	})
	emitUpdate()
}

function addGroup() {
	localConditions.value.push({ and: [] })
	emitUpdate()
}

function removeCondition(index) {
	localConditions.value.splice(index, 1)
	emitUpdate()
}

function updateCondition(index, newValue) {
	if (newValue && newValue.__unwrap === true && Array.isArray(newValue.children)) {
		localConditions.value.splice(index, 1, ...newValue.children)
		emitUpdate()
		return
	}
	localConditions.value[index] = newValue
	emitUpdate()
}

function updateLeafField(index, field) {
	const existing = localConditions.value[index]
	localConditions.value[index] = {
		...(existing.not ? { not: true } : {}),
		field,
		op: '=',
		value: '',
	}
	emitUpdate()
}

function updateLeafOp(index, op) {
	const condition = { ...localConditions.value[index], op }
	if (['is_empty', 'is_not_empty'].includes(op)) {
		delete condition.value
	} else if (condition.value === undefined) {
		condition.value = ''
	}
	localConditions.value[index] = condition
	emitUpdate()
}

function updateLeafValue(index, value) {
	const field = props.fields.find(f => f.key === localConditions.value[index].field)
	const parsedValue = field?.type === 'number' && value !== '' ? Number(value) : value
	localConditions.value[index] = {
		...localConditions.value[index],
		value: parsedValue,
	}
	emitUpdate()
}

function toggleLeafNot(index) {
	const cur = localConditions.value[index]
	localConditions.value[index] = { ...cur, not: !cur.not }
	if (!localConditions.value[index].not) {
		delete localConditions.value[index].not
	}
	emitUpdate()
}

function isGroupNode(item) {
	return item && (Array.isArray(item.and) || Array.isArray(item.or))
}

function isEncryptedField(fieldKey) {
	const f = props.fields.find(x => x.key === fieldKey)
	return !!(f && f.encrypted)
}

// Unwrap: replace this group with its children in the parent, only if the
// parent's logic matches ours (semantically equivalent) and this group is
// not negated. Emitted to parent via a special sentinel.
const canUnwrap = computed(() => {
	if (props.depth === 0) return false
	if (localNot.value) return false
	if (props.parentLogic && props.parentLogic !== localLogic.value) return false
	return localConditions.value.length > 0
})

const unwrapHint = computed(() => {
	if (props.depth === 0) return ''
	if (localNot.value) return 'Negierte Gruppen können nicht aufgelöst werden'
	if (props.parentLogic && props.parentLogic !== localLogic.value) {
		return 'Logik der Gruppe (' + localLogic.value.toUpperCase() + ') passt nicht zur Eltern-Logik ('
			+ props.parentLogic.toUpperCase() + ')'
	}
	return 'Klammer auflösen und Bedingungen in Eltern-Gruppe übernehmen'
})

function unwrapGroup() {
	if (!canUnwrap.value) return
	emit('update:modelValue', { __unwrap: true, children: [...localConditions.value] })
}

const groupedFields = computed(() => {
	const out = {}
	for (const f of props.fields) {
		const g = f.group || 'Sonstiges'
		if (!out[g]) out[g] = []
		out[g].push(f)
	}
	return out
})

/**
 * Get appropriate operators for a field type.
 */
function operatorsForField(fieldKey) {
	const field = props.fields.find(f => f.key === fieldKey)
	const type = field?.type || 'string'

	const base = [
		{ value: '=', label: 'ist gleich' },
		{ value: '!=', label: 'ist ungleich' },
		{ value: 'is_empty', label: 'ist leer' },
		{ value: 'is_not_empty', label: 'ist nicht leer' },
	]

	if (type === 'number' || type === 'date') {
		return [
			...base,
			{ value: '<', label: 'kleiner als' },
			{ value: '>', label: 'größer als' },
			{ value: '<=', label: 'kleiner oder gleich' },
			{ value: '>=', label: 'größer oder gleich' },
		]
	}

	return [
		...base,
		{ value: 'contains', label: 'enthält' },
		{ value: 'starts_with', label: 'beginnt mit' },
	]
}

/**
 * Get input type for a field.
 */
function inputTypeForField(fieldKey) {
	const field = props.fields.find(f => f.key === fieldKey)
	if (field?.type === 'number') return 'number'
	if (field?.type === 'date') return 'date'
	return 'text'
}
</script>

<style scoped>
.query-builder {
	position: relative;
}

.query-builder__group {
	border: 1px solid var(--color-border);
	border-radius: var(--border-radius-large);
	padding: 12px;
	background: var(--color-main-background);
}

.query-builder__group--depth-1 {
	background: var(--color-background-dark);
	margin: 8px 0;
}

.query-builder__group--depth-2,
.query-builder__group--depth-3,
.query-builder__group--depth-4,
.query-builder__group--depth-5 {
	background: var(--color-background-darker, var(--color-background-dark));
	margin: 8px 0;
}

.query-builder__group--negated {
	border-color: var(--color-warning, #e9a23b);
	box-shadow: inset 0 0 0 1px var(--color-warning, #e9a23b);
}

.query-builder__bracket {
	display: inline-block;
	font-family: monospace;
	font-size: 1.6em;
	font-weight: 700;
	color: var(--color-text-lighter);
	line-height: 1;
	vertical-align: top;
	padding: 0 4px;
}

.query-builder__bracket--open {
	float: left;
	margin-right: 2px;
}

.query-builder__bracket--close {
	display: block;
	margin-top: -10px;
}

.query-builder__group-header {
	display: flex;
	justify-content: space-between;
	align-items: center;
	margin-bottom: 12px;
	gap: 8px;
	flex-wrap: wrap;
}

.query-builder__group-toggles {
	display: flex;
	gap: 8px;
	align-items: center;
}

.query-builder__logic-toggle {
	display: flex;
	border: 1px solid var(--color-border);
	border-radius: var(--border-radius);
	overflow: hidden;
}

.query-builder__logic-btn {
	padding: 4px 12px;
	border: none;
	background: none;
	cursor: pointer;
	font-size: 0.85em;
	font-weight: 600;
	color: var(--color-text-lighter);
}

.query-builder__logic-btn--active {
	background: var(--color-primary);
	color: white;
}

.query-builder__not-btn {
	padding: 4px 10px;
	border: 1px solid var(--color-border);
	border-radius: var(--border-radius);
	background: none;
	cursor: pointer;
	font-size: 0.8em;
	font-weight: 700;
	color: var(--color-text-lighter);
	letter-spacing: 0.05em;
}

.query-builder__not-btn--active {
	background: var(--color-warning, #e9a23b);
	color: white;
	border-color: var(--color-warning, #e9a23b);
}

.query-builder__group-actions {
	display: flex;
	gap: 8px;
	flex-wrap: wrap;
}

.query-builder__conditions {
	display: flex;
	flex-direction: column;
	gap: 8px;
}

.query-builder__leaf {
	display: flex;
	align-items: center;
	gap: 8px;
	flex-wrap: wrap;
}

.query-builder__leaf--negated {
	padding: 4px;
	border-radius: var(--border-radius);
	background: color-mix(in srgb, var(--color-warning, #e9a23b) 10%, transparent);
}

.query-builder__not-leaf-btn {
	width: 28px;
	height: 28px;
	border: 1px solid var(--color-border);
	border-radius: var(--border-radius);
	background: none;
	cursor: pointer;
	font-size: 1em;
	font-weight: 700;
	color: var(--color-text-lighter);
	flex-shrink: 0;
}

.query-builder__not-leaf-btn--active {
	background: var(--color-warning, #e9a23b);
	color: white;
	border-color: var(--color-warning, #e9a23b);
}

.query-builder__select {
	padding: 6px 8px;
	border: 1px solid var(--color-border);
	border-radius: var(--border-radius);
	background: var(--color-main-background);
	font-size: 0.9em;
	min-width: 140px;
}

.query-builder__select--op {
	min-width: 160px;
}

.query-builder__input {
	padding: 6px 8px;
	border: 1px solid var(--color-border);
	border-radius: var(--border-radius);
	font-size: 0.9em;
	flex: 1;
	min-width: 120px;
}

.query-builder__encrypted-badge {
	font-size: 1em;
	cursor: help;
}

.query-builder__remove-btn {
	flex-shrink: 0;
}

.query-builder__empty {
	padding: 16px;
	text-align: center;
	color: var(--color-text-lighter);
	font-size: 0.9em;
}
</style>
