<style scoped>
.filters-section {
	font-size: 0.875rem;
}
</style>
<template>
	<div class="card shadow-sm h-100 reports-list">
		<div class="card-header bg-light pane-header">
			<div class="filters-section">
				<slot
					:active-filters="activeFilters"
					:apply-filter="applyFilter"
					:clear-filters="clearFilters"
					name="filters"
				/>
			</div>
		</div>
		<div class="card-body scroll-pane">
			<div class="list-group list-group-flush">
				<div class="loading list-group-item" v-if="loading || items == null">
					Loading...
				</div>
				<div class="list-group-item" v-else-if="items.length == 0">
					No items
				</div>
				<div
					v-else-if="items.length == 0"
					class="list-group-item text-center text-muted p-4"
				>
					<i class="bi bi-inbox fs-1"></i>
					<p class="mt-2">No items match the current filters</p>
					<button class="btn btn-sm btn-outline-primary" @click="clearFilters">
						Reset Filters
					</button>
				</div>
				<div v-else v-for="item in filteredItems" :key="item.id">
					<slot
						name="item"
						:item="item"
						:index="item.id"
						:is-selected="isSelected(item)"
						:toggle-selection="() => toggleSelection(item)"
					></slot>
				</div>
			</div>
		</div>
	</div>
</template>
<script setup generic="T extends Record<string, any>" lang="ts">
import { ref, computed } from "vue";

interface Props {
	items: T[];
	loading?: boolean;
	itemKey?: keyof T | ((item: T) => string | number);
	selectionMode?: "single" | "multiple" | "none";
}

const props = withDefaults(defineProps<Props>(), {
	loading: false,
	itemKey: "id",
	selectionMode: "none",
});

defineSlots<{
	filters(props: {
		applyFilter: (filterFn: (item: T) => boolean) => void;
		clearFilters: () => void;
		activeFilters: number;
	}): any;
	item(props: {
		item: T;
		index: string | number;
		isSelected: boolean;
		toggleSelection: () => void;
	}): any;
	footer(props: { total: number }): any;
}>();

// State
const emit = defineEmits<{
	"update:selectedItems": [items: T[]];
	"selection-change": [items: T[]];
}>();
const filterFunctions = ref<Array<(item: T) => boolean>>([]);
const selectedItemKeys = ref<Set<string | number>>(new Set());

// Computed
const filteredItems = computed(() => {
	if (filterFunctions.value.length === 0) {
		return props.items;
	}

	return props.items.filter((item) =>
		filterFunctions.value.every((fn) => fn(item)),
	);
});
const activeFilters = computed(() => filterFunctions.value.length);
const selectedItems = computed(() => {
	return props.items.filter((item) =>
		selectedItemKeys.value.has(getItemKey(item)),
	);
});

// Methods
const applyFilter = (filterFn: (item: T) => boolean) => {
	// Add or replace filter (you can modify this logic as needed)
	filterFunctions.value.push(filterFn);
};

const clearFilters = () => {
	filterFunctions.value = [];
};

const getItemKey = (item: T): string | number => {
	if (typeof props.itemKey === "function") {
		return props.itemKey(item);
	}
	return item[props.itemKey] as string | number;
};

const isSelected = (item: T): boolean => {
	return selectedItemKeys.value.has(getItemKey(item));
};

const toggleSelection = (item: T) => {
	if (props.selectionMode === "none") return;

	const key = getItemKey(item);

	if (props.selectionMode === "single") {
		if (selectedItemKeys.value.has(key)) {
			selectedItemKeys.value.clear();
		} else {
			selectedItemKeys.value.clear();
			selectedItemKeys.value.add(key);
		}
	} else {
		if (selectedItemKeys.value.has(key)) {
			selectedItemKeys.value.delete(key);
		} else {
			selectedItemKeys.value.add(key);
		}
	}

	emit("update:selectedItems", selectedItems.value);
	emit("selection-change", selectedItems.value);
};

const clearSelection = () => {
	selectedItemKeys.value.clear();
	emit("update:selectedItems", []);
	emit("selection-change", []);
};

defineExpose({
	clearSelection,
	selectedItems,
});
</script>
