import { defineStore } from "pinia";
import { ref, shallowRef, shallowReactive } from "vue";

import { log } from "@/log";
import { SSEManager, SSEMessageResource } from "@/SSEManager";

import { apiClient } from "@/client";
import {
	Communication,
	type CommunicationDTO,
	Contact,
	type ContactDTO,
	PublicReport,
	type PublicReportDTO,
} from "@/type/api";

interface uriHaver {
	uri: string;
}
type jsonConverter<dto, full> = (arg: dto) => full;
function createResourceStore<dto, full extends uriHaver>(
	resource_name: string,
	api_base: string,
	from_json: jsonConverter<dto, full>,
) {
	const byURI = shallowReactive<Map<string, full>>(new Map());
	const _resourceFetchAll = ref<Promise<full[]> | null>(null);
	const _resourceFetchByURI = shallowRef<Map<string, Promise<full> | null>>(
		new Map(),
	);

	// Subscription
	SSEManager.subscribe((msg: SSEMessageResource) => {
		if (msg.resource.startsWith(resource_name)) {
			if (msg.type == "created") {
				console.log("New resource", resource_name, msg.resource, msg.uri);
				fetchByURI(msg.uri);
			} else if (msg.type == "updated") {
				console.log("Updated resource", resource_name, msg.resource, msg.uri);
				fetchByURI(msg.uri);
			}
		}
	});

	async function byID(id: string): Promise<full> {
		const uri = uriFromID(id);
		return ensureURI(uri);
	}
	async function ensureURI(uri: string): Promise<full> {
		// Check if we already have the data
		const existing = byURI.get(uri);
		if (existing) {
			return existing;
		}

		// Check if we're already fetching it
		let fetchPromise = _resourceFetchByURI.value.get(uri);
		if (fetchPromise) {
			return fetchPromise;
		}

		// Start fetching
		fetchPromise = fetchByURI(uri);
		_resourceFetchByURI.value.set(uri, fetchPromise);

		try {
			const result = await fetchPromise;
			return result;
		} finally {
			// Clean up the promise after it resolves
			_resourceFetchByURI.value.delete(uri);
		}
	}
	async function fetchAll(): Promise<full[]> {
		/*
		const sessionStore = useSessionStore();
		const session = await sessionStore.get();
		const params = new URLSearchParams();
		params.append("sort", "-created");
		const url = `${session.urls.api.mailer}?${params}`;
	   */
		const url = `/api${api_base}`;
		const dtos = (await apiClient.JSONGet(url)) as dto[];
		const resources = dtos.map((m: dto) => from_json(m));
		//let new_all = new Map<string, full>();
		resources.forEach((r: full) => {
			byURI.set(r.uri, r);
			//new_all.set(r.uri, r);
			log.info("Set", resource_name, r);
		});
		//byURI.value = new_all;
		return resources;
	}
	async function fetchByID(id: string): Promise<full> {
		const uri = uriFromID(id);
		return fetchByURI(uri);
	}
	async function fetchByURI(uri: string): Promise<full> {
		try {
			const response = await fetch(uri);

			if (!response.ok) {
				throw new Error(`HTTP error! status: ${response.status}`);
			}
			const body: dto = await response.json();
			const report = from_json(body);
			//let new_all = new Map<string, full>(byURI.value);
			//new_all.set(report.uri, report);
			byURI.set(report.uri, report);
			//byURI.value = new_all;
			return report;
		} catch (err) {
			console.error("Error loading users:", err);
			throw err;
		}
	}
	function hasAll(): boolean {
		return !!_resourceFetchAll.value;
	}
	function loadingAll(): boolean {
		return !!_resourceFetchAll.value;
	}
	function loadingURI(uri: string | undefined): boolean {
		if (uri === undefined) {
			return false;
		}
		return !!_resourceFetchByURI.value.get(uri);
	}
	function uriFromID(id: string): string {
		return `${api_base}/${id}`;
	}
	return {
		byID,
		byURI,
		ensureURI,
		fetchAll,
		fetchByID,
		fetchByURI,
		hasAll,
		loadingAll,
		loadingURI,
	};
}
export const useStoreResource = defineStore("resource", () => {
	return {
		communication: createResourceStore<CommunicationDTO, Communication>(
			"sync:communication",
			"/communication",
			Communication.fromJSON,
		),
		contact: createResourceStore<ContactDTO, Contact>(
			"sync:contact",
			"/contact",
			Contact.fromJSON,
		),
		publicreport: createResourceStore<PublicReportDTO, PublicReport>(
			"sync:publicreport",
			"/publicreport",
			PublicReport.fromJSON,
		),
	};
});
