<template>
	<div class="member-list">
		<div class="member-list__header">
			<h2>Mitglieder</h2>
			<div class="member-list__actions">
				<ColumnPicker :columns="allColumns"
					:model-value="visibleKeys"
					@update:model-value="setVisibleKeys" />
				<NcButton :type="allergienVisible ? 'warning' : 'secondary'"
					:disabled="allergienLoading"
					:title="allergienVisible
						? 'Allergien-Spalte ausblenden und entschlüsselte Daten verwerfen'
						: 'Allergien-Spalte anzeigen (serverseitig entschlüsselt, auditiert)'"
					@click="toggleAllergien">
					<template #icon>
						<NcLoadingIcon v-if="allergienLoading" :size="18" />
						<EyeOff v-else-if="allergienVisible" :size="18" />
						<Eye v-else :size="18" />
					</template>
					{{ allergienVisible ? 'Allergien ausblenden' : 'Allergien anzeigen' }}
				</NcButton>
				<NcButton type="primary"
					@click="$router.push({ name: 'member-detail', params: { id: 'new' } })">
					<template #icon>
						<Plus :size="20" />
					</template>
					Neues Mitglied
				</NcButton>
			</div>
		</div>

		<!-- Quick filter chips (additive) -->
		<div class="member-list__filters">
			<button :class="{ 'member-list__filter--active': !hasActiveFilters }"
				class="member-list__filter"
				@click="resetFilters">
				Alle
			</button>

			<span class="member-list__filter-sep" />

			<button v-for="f in statusFilters"
				:key="f.value"
				:class="{ 'member-list__filter--active': store.activeFilters.status === f.value }"
				class="member-list__filter"
				@click="toggleExclusive('status', f.value)">
				{{ f.label }}
			</button>

			<span class="member-list__filter-sep" />

			<button v-for="f in rolleFilters"
				:key="f.value"
				:class="{ 'member-list__filter--active': store.activeFilters.rolle === f.value }"
				class="member-list__filter"
				@click="toggleExclusive('rolle', f.value)">
				{{ f.label }}
			</button>

			<span class="member-list__filter-sep" />

			<button :class="{ 'member-list__filter--active': store.activeFilters.birthdayThisMonth }"
				class="member-list__filter"
				@click="toggleFlag('birthdayThisMonth')">
				Geburtstage diesen Monat
			</button>
			<button :class="{ 'member-list__filter--active': store.activeFilters.unpaidFees }"
				class="member-list__filter"
				@click="toggleFlag('unpaidFees')">
				Unbezahlte Beiträge
			</button>

			<span class="member-list__filter-sep" />

			<!-- Save current filter combination -->
			<template v-if="hasActiveFilters">
				<template v-if="!showSaveInput">
					<button class="member-list__filter member-list__filter--save"
						@click="showSaveInput = true">
						Filter speichern
					</button>
				</template>
				<template v-else>
					<input ref="saveNameInput"
						v-model="saveFilterName"
						class="member-list__save-input"
						placeholder="Filtername..."
						@keyup.enter="saveCurrentFilter"
						@keyup.escape="cancelSave">
					<button class="member-list__filter member-list__filter--save"
						:disabled="!saveFilterName.trim()"
						@click="saveCurrentFilter">
						Speichern
					</button>
					<button class="member-list__filter"
						@click="cancelSave">
						Abbrechen
					</button>
				</template>
			</template>
		</div>

		<!-- Saved filters -->
		<div v-if="savedFilters.length > 0" class="member-list__saved-filters">
			<span class="member-list__saved-label">Gespeicherte Filter:</span>
			<span v-for="sf in savedFilters"
				:key="sf.id"
				class="member-list__saved-chip"
				:class="{ 'member-list__saved-chip--active': isMatchingSaved(sf) }">
				<button class="member-list__saved-chip-name"
					@click="applySaved(sf)">
					{{ sf.name }}
				</button>
				<button class="member-list__saved-chip-delete"
					title="Filter löschen"
					@click.stop="removeFilter(sf.id)">
					&times;
				</button>
			</span>
		</div>

		<!-- Error state -->
		<NcEmptyContent v-if="store.error && !store.loading"
			name="Fehler"
			:description="store.error">
			<template #icon>
				<AlertCircle :size="64" />
			</template>
			<template #action>
				<NcButton @click="reload">
					Erneut versuchen
				</NcButton>
			</template>
		</NcEmptyContent>

		<!-- Loading state -->
		<NcLoadingIcon v-else-if="store.loading && store.members.length === 0"
			:size="64"
			appearance="dark"
			class="member-list__loading" />

		<!-- Empty state -->
		<NcEmptyContent v-else-if="!store.loading && store.members.length === 0"
			name="Keine Mitglieder"
			description="Noch keine Mitglieder angelegt.">
			<template #icon>
				<AccountGroup :size="64" />
			</template>
			<template #action>
				<NcButton type="primary"
					@click="$router.push({ name: 'member-detail', params: { id: 'new' } })">
					Erstes Mitglied anlegen
				</NcButton>
			</template>
		</NcEmptyContent>

		<!-- Member table -->
		<table v-else class="member-list__table">
			<thead>
				<tr>
					<th v-for="col in displayColumns" :key="col.key"
						class="member-list__th"
						:class="{ 'member-list__th--sortable': col.sortable }"
						@click="col.sortable && toggleSort(col.sortField || col.key)">
						{{ col.label }}
						<SortIcon v-if="col.sortable"
							:field="col.sortField || col.key"
							:current-sort="sortField"
							:sort-asc="sortAsc" />
					</th>
				</tr>
			</thead>
			<tbody>
				<tr v-for="member in sortedMembers"
					:key="member.id"
					class="member-list__row"
					@click="$router.push({ name: 'member-detail', params: { id: member.id } })">
					<td v-for="col in displayColumns" :key="col.key"
						class="member-list__td"
						:class="col.key === 'allergien' ? 'member-list__td--allergien' : ''">
						<span v-if="col.key === 'status'"
							:class="'member-list__status member-list__status--' + member.status">
							{{ col.render(member) }}
						</span>
						<template v-else>{{ col.render(member) }}</template>
					</td>
				</tr>
			</tbody>
		</table>

		<!-- Pagination -->
		<PaginationControls
			:current-page="store.currentPage"
			:total-items="store.total"
			:page-size="pageSize"
			item-label="Mitglieder"
			@page="store.goToPage($event)"
			@update:page-size="onPageSizeChange" />
	</div>
</template>

<script setup>
import { ref, computed, onMounted, onBeforeUnmount, nextTick, watch } from 'vue'
import axios from '@nextcloud/axios'
import { generateUrl } from '@nextcloud/router'
import { NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'
import Eye from 'vue-material-design-icons/Eye.vue'
import EyeOff from 'vue-material-design-icons/EyeOff.vue'
import { useMembersStore } from '../stores/members.js'
import { useStufenStore } from '../stores/stufen.js'
import { useColumnVisibility } from '../utils/useColumnVisibility.js'
import { usePageSize } from '../utils/usePageSize.js'
import { useSavedFilters } from '../utils/useSavedFilters.js'
import ColumnPicker from '../components/ColumnPicker.vue'
import PaginationControls from '../components/PaginationControls.vue'
import Plus from 'vue-material-design-icons/Plus.vue'
import AlertCircle from 'vue-material-design-icons/AlertCircle.vue'
import AccountGroup from 'vue-material-design-icons/AccountGroup.vue'
import SortIcon from '../components/SortIcon.vue'
import { formatDate } from '../utils/dateFormat.js'

const store = useMembersStore()
const stufenStore = useStufenStore()
const { pageSize, setPageSize } = usePageSize('mv_member_pagesize')
store.limit = pageSize.value

function onPageSizeChange(size) {
	setPageSize(size)
	store.setLimit(size)
}
const { savedFilters, addFilter, removeFilter } = useSavedFilters()

const sortField = ref('nachname')
const sortAsc = ref(true)

// ── Column definitions ──

function getStufeNameById(stufeId) {
	if (!stufeId) return '\u2014'
	const stufe = stufenStore.stufen.find(s => s.id === stufeId)
	return stufe ? stufe.name : '\u2014'
}

function calculateAge(geburtsdatum) {
	if (!geburtsdatum) return '\u2014'
	const birth = new Date(geburtsdatum)
	const today = new Date()
	let age = today.getFullYear() - birth.getFullYear()
	const monthDiff = today.getMonth() - birth.getMonth()
	if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
		age--
	}
	return age
}

function calculateMitgliedsdauer(eintritt) {
	if (!eintritt) return '\u2014'
	const start = new Date(eintritt)
	const today = new Date()
	let years = today.getFullYear() - start.getFullYear()
	const monthDiff = today.getMonth() - start.getMonth()
	if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < start.getDate())) {
		years--
	}
	if (years < 1) {
		const months = (today.getFullYear() - start.getFullYear()) * 12 + today.getMonth() - start.getMonth()
		return months <= 0 ? '< 1 Monat' : months + (months === 1 ? ' Monat' : ' Monate')
	}
	return years + (years === 1 ? ' Jahr' : ' Jahre')
}

function getPrimaryAddress(member) {
	if (!member.addresses || member.addresses.length === 0) return null
	return member.addresses.find(a => a.isPrimary) || member.addresses[0]
}

const statusMap = { aktiv: 'Aktiv', inaktiv: 'Inaktiv', geloescht: 'Gelöscht' }
const rolleMap = { mitglied: 'Mitglied', erziehungsberechtigter: 'Erziehungsberechtigter' }
const geschlechtMap = { maennlich: 'Männlich', weiblich: 'Weiblich', divers: 'Divers' }
const kvTypMap = { gesetzlich: 'Gesetzlich', privat: 'Privat' }

const allColumns = [
	{ key: 'name', label: 'Name', sortable: true, sortField: 'nachname', alwaysVisible: true, render: m => m.nachname + ', ' + m.vorname },
	{ key: 'stufe', label: 'Stufe', sortable: true, sortField: 'stufeId', render: m => getStufeNameById(m.stufeId) },
	{ key: 'status', label: 'Status', sortable: true, sortField: 'status', render: m => statusMap[m.status] || m.status },
	{ key: 'geburtsdatum', label: 'Geburtsdatum', sortable: true, sortField: 'geburtsdatum', render: m => formatDate(m.geburtsdatum) },
	{ key: 'alter', label: 'Alter', sortable: false, render: m => calculateAge(m.geburtsdatum) },
	{ key: 'rolle', label: 'Rolle', sortable: false, render: m => rolleMap[m.rolle] || m.rolle },
	{ key: 'geschlecht', label: 'Geschlecht', sortable: false, render: m => geschlechtMap[m.geschlecht] || m.geschlecht || '\u2014' },
	{ key: 'plz', label: 'PLZ', sortable: false, render: m => { const a = getPrimaryAddress(m); return a ? a.plz : '\u2014' } },
	{ key: 'ort', label: 'Wohnort', sortable: false, render: m => { const a = getPrimaryAddress(m); return a ? a.ort : '\u2014' } },
	{ key: 'eintritt', label: 'Eintrittsdatum', sortable: true, sortField: 'eintritt', render: m => formatDate(m.eintritt) },
	{ key: 'austritt', label: 'Austrittsdatum', sortable: true, sortField: 'austritt', render: m => formatDate(m.austritt) },
	{ key: 'mitgliedsdauer', label: 'Mitgliedsdauer', sortable: true, sortField: 'eintritt', render: m => calculateMitgliedsdauer(m.eintritt) },
	{ key: 'einwilligungDatum', label: 'Einwilligung', sortable: true, sortField: 'einwilligungDatum', render: m => formatDate(m.einwilligungDatum) },
	{ key: 'kvTyp', label: 'KV-Typ', sortable: false, render: m => kvTypMap[m.kvTyp] || m.kvTyp || '\u2014' },
	{ key: 'kvName', label: 'Krankenkasse', sortable: false, render: m => m.kvName || '\u2014' },
	{ key: 'juleicaNummer', label: 'Juleica-Nr.', sortable: false, render: m => m.juleicaNummer || '\u2014' },
	{ key: 'juleicaAblaufdatum', label: 'Juleica-Ablauf', sortable: true, sortField: 'juleicaAblaufdatum', render: m => formatDate(m.juleicaAblaufdatum) },
	{ key: 'notizen', label: 'Notizen', sortable: false, render: m => m.notizen ? (m.notizen.length > 40 ? m.notizen.substring(0, 40) + '\u2026' : m.notizen) : '\u2014' },
]

const { visibleKeys, visibleColumns, setVisibleKeys } = useColumnVisibility(
	'mv_member_columns',
	allColumns,
	['name', 'stufe', 'status', 'geburtsdatum', 'alter', 'rolle'],
)

// ── Allergien reveal (admin-only, audited) ──
// State is kept local and NEVER persisted to localStorage or the Pinia
// store. Toggling off wipes the plaintext map so it cannot be recovered
// from the page.
const allergienVisible = ref(false)
const allergienLoading = ref(false)
const allergienMap = ref(null) // { [memberId]: plaintext | null } | null

const displayColumns = computed(() => {
	if (!allergienVisible.value) return visibleColumns.value
	return [
		...visibleColumns.value,
		{
			key: 'allergien',
			label: 'Allergien',
			sortable: false,
			render: (m) => {
				if (allergienMap.value === null) return '\u2026'
				const val = allergienMap.value[m.id]
				if (val === null || val === undefined || val === '') return '\u2014'
				return val
			},
		},
	]
})

async function toggleAllergien() {
	if (allergienVisible.value) {
		discardAllergien()
		return
	}
	if (!confirm('Allergien aller Mitglieder entschlüsseln und anzeigen?\n\nSensible medizinische Daten werden serverseitig entschlüsselt und einmalig angezeigt. Der Zugriff wird im Audit-Log protokolliert.')) {
		return
	}
	allergienLoading.value = true
	try {
		const url = generateUrl('/apps/mitgliederverwaltung/api/v1/members/allergien/reveal')
		const response = await axios.post(url, {}, {
			headers: { 'Cache-Control': 'no-store' },
		})
		allergienMap.value = response.data?.data || {}
		allergienVisible.value = true
	} catch (err) {
		const msg = err.response?.data?.error || err.message || 'Entschlüsselung fehlgeschlagen'
		alert('Allergien konnten nicht geladen werden: ' + msg)
	} finally {
		allergienLoading.value = false
	}
}

function discardAllergien() {
	// Overwrite every stored value before dropping the ref so the
	// underlying strings are no longer reachable via Vue's reactive proxy.
	if (allergienMap.value) {
		for (const k of Object.keys(allergienMap.value)) {
			allergienMap.value[k] = null
		}
	}
	allergienMap.value = null
	allergienVisible.value = false
}

onBeforeUnmount(() => {
	discardAllergien()
})

// ── Filters ──

const statusFilters = [
	{ value: 'aktiv', label: 'Aktive' },
	{ value: 'inaktiv', label: 'Inaktive' },
]

const rolleFilters = [
	{ value: 'mitglied', label: 'Mitglieder' },
	{ value: 'erziehungsberechtigter', label: 'Erziehungsberechtigte' },
]

const hasActiveFilters = computed(() => {
	const f = store.activeFilters
	return !!(f.status || f.rolle || f.birthdayThisMonth || f.unpaidFees)
})

function resetFilters() {
	store.fetchWithFilters({ status: null, rolle: null, birthdayThisMonth: false, unpaidFees: false })
}

function toggleExclusive(field, value) {
	const next = { ...store.activeFilters }
	next[field] = next[field] === value ? null : value
	store.fetchWithFilters(next)
}

function toggleFlag(field) {
	const next = { ...store.activeFilters }
	next[field] = !next[field]
	store.fetchWithFilters(next)
}

// ── Saved filters ──

const showSaveInput = ref(false)
const saveFilterName = ref('')
const saveNameInput = ref(null)

watch(showSaveInput, (show) => {
	if (show) {
		nextTick(() => saveNameInput.value?.focus())
	}
})

function saveCurrentFilter() {
	const name = saveFilterName.value.trim()
	if (!name) return
	addFilter(name, store.activeFilters)
	cancelSave()
}

function cancelSave() {
	showSaveInput.value = false
	saveFilterName.value = ''
}

function applySaved(sf) {
	store.fetchWithFilters({ ...sf.filters })
}

function isMatchingSaved(sf) {
	const a = store.activeFilters
	const b = sf.filters
	return a.status === b.status
		&& a.rolle === b.rolle
		&& a.birthdayThisMonth === b.birthdayThisMonth
		&& a.unpaidFees === b.unpaidFees
}

onMounted(() => {
	store.fetchMembers()
	stufenStore.fetchStufen()
})

// ── Sorting ──

const sortedMembers = computed(() => {
	const members = [...store.members]
	const field = sortField.value
	const asc = sortAsc.value

	return members.sort((a, b) => {
		let valA = a[field] ?? ''
		let valB = b[field] ?? ''

		if (field === 'geburtsdatum') {
			return asc
				? String(valA).localeCompare(String(valB))
				: String(valB).localeCompare(String(valA))
		}

		if (typeof valA === 'string') {
			valA = valA.toLowerCase()
			valB = String(valB).toLowerCase()
		}

		if (valA < valB) return asc ? -1 : 1
		if (valA > valB) return asc ? 1 : -1
		return 0
	})
})

function toggleSort(field) {
	if (sortField.value === field) {
		sortAsc.value = !sortAsc.value
	} else {
		sortField.value = field
		sortAsc.value = true
	}
}

function reload() {
	store.clearError()
	store.fetchMembers()
}
</script>

<style scoped>
.member-list { padding: 20px; }
.member-list__header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; flex-wrap: wrap; gap: 12px; }
.member-list__header h2 { margin: 0; }
.member-list__actions { display: flex; gap: 12px; align-items: center; }
.member-list__loading { display: flex; justify-content: center; margin-top: 60px; }
.member-list__table { width: 100%; border-collapse: collapse; }
.member-list__th { text-align: left; padding: 12px 16px; border-bottom: 2px solid var(--color-border-dark); font-weight: bold; white-space: nowrap; }
.member-list__th--sortable { cursor: pointer; user-select: none; }
.member-list__th--sortable:hover { background-color: var(--color-background-hover); }
.member-list__row { cursor: pointer; transition: background-color 0.15s ease; }
.member-list__row:hover { background-color: var(--color-background-hover); }
.member-list__td { padding: 10px 16px; border-bottom: 1px solid var(--color-border); }
.member-list__td--allergien { color: var(--color-warning-text, #b86e00); font-weight: 500; max-width: 280px; overflow: hidden; text-overflow: ellipsis; }
.member-list__status { display: inline-block; padding: 2px 8px; border-radius: var(--border-radius-pill); font-size: 0.85em; font-weight: 500; }
.member-list__status--aktiv { background-color: var(--color-success); color: white; }
.member-list__status--inaktiv { background-color: var(--color-warning); color: white; }
.member-list__status--geloescht { background-color: var(--color-error); color: white; }
.member-list__filters { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; }
.member-list__filter { padding: 6px 14px; border: 1px solid var(--color-border); border-radius: var(--border-radius-pill); background: var(--color-main-background); color: var(--color-text); cursor: pointer; font-size: 0.85em; font-weight: 500; transition: all 0.15s ease; }
.member-list__filter:hover { background: var(--color-background-hover); border-color: var(--color-primary); }
.member-list__filter--active { background: var(--color-primary); color: white; border-color: var(--color-primary); }
.member-list__filter--active:hover { background: var(--color-primary-element-hover); }
.member-list__filter-sep { width: 1px; height: 24px; background: var(--color-border); margin: 0 4px; align-self: center; flex-shrink: 0; }
.member-list__filter--save { border-style: dashed; }
.member-list__save-input { padding: 5px 10px; border: 1px solid var(--color-border); border-radius: var(--border-radius-pill); font-size: 0.85em; outline: none; width: 160px; }
.member-list__save-input:focus { border-color: var(--color-primary); }
.member-list__saved-filters { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; align-items: center; }
.member-list__saved-label { font-size: 0.82em; color: var(--color-text-maxcontrast); white-space: nowrap; }
.member-list__saved-chip { display: inline-flex; align-items: center; border: 1px solid var(--color-border); border-radius: var(--border-radius-pill); overflow: hidden; transition: all 0.15s ease; }
.member-list__saved-chip--active { border-color: var(--color-primary); background: var(--color-primary-element-light); }
.member-list__saved-chip-name { padding: 4px 8px 4px 12px; background: none; border: none; cursor: pointer; font-size: 0.85em; font-weight: 500; color: var(--color-text); }
.member-list__saved-chip-name:hover { background: var(--color-background-hover); }
.member-list__saved-chip-delete { padding: 4px 10px 4px 4px; background: none; border: none; cursor: pointer; font-size: 1.1em; line-height: 1; color: var(--color-text-maxcontrast); }
.member-list__saved-chip-delete:hover { color: var(--color-error); }
</style>
