import { defineStore } from "pinia"
import { AxiosError } from "axios"
import ArrayService from "@service/array-service"
import ApiService from "@service/api-service"
import { Ref, ref } from "vue"

export interface BaseAction<T> {
	setError: (errors?: AxiosError) => void
	setItem: (item: T) => void
	addItem: (key: string | number, item: T) => void
	setCollection: (collection: Array<T>) => void
	getItemBy: (filters: { [cle: string]: unknown }[]) => Promise<T | null>
	fetchItem: (objectId: string | number) => Promise<T | null>
	fetchCollection: (params?: Record<string, string>) => Promise<T[] | null>
	post: (object: T | FormData) => Promise<T | null>
	delete: (objectId: string | number) => Promise<void>
	put: (object: T | FormData) => Promise<T | null>
}

export interface BaseState<T> {
	current: Ref<T | undefined>
	resource: string
	collection: Ref<T[] | undefined>
	errors: Ref<AxiosError<unknown, any> | undefined>
}

export interface BaseStoreOption {
	state?: object
	getters?: object
	actions?: object
}

export class StoreClass<T> {
	public current = ref<T>()
	public collection = ref<Array<T>>()
	public errors = ref<AxiosError>()

	constructor(
		public name: string,
		public resource: string,
		public resourceIdentifierKey: string = "id",
		public options?: BaseStoreOption
	) {
		this.errors = ref<AxiosError>()
	}

	public getStates(): BaseState<T> {
		return <BaseState<T>>{
			current: this.current,
			resource: this.resource,
			collection: this.collection,
			errors: this.errors,
			...this.options?.state
		}
	}

	public getGetters() {
		return {
			...this.options?.getters
		}
	}

	public getActions() {
		return <BaseAction<T>>{
			setError: this.setError,
			setItem: this.setItem,
			addItem: this.addItem,
			setCollection: this.setCollection,
			getItemBy: this.getItemBy,
			fetchItem: this.fetchItem,
			fetchCollection: this.fetchCollection,
			post: this.post,
			delete: this.delete,
			put: this.put,
			...this.options?.actions
		}
	}

	setError(errors?: AxiosError): void {
		if (this.errors !== undefined) {
			this.errors.value = errors
		}
	}

	setItem(item: T): void {
		this.current.value = item

		if (this.current.value[this.resourceIdentifierKey]) {
			this.addItem(this.current.value[this.resourceIdentifierKey], this.current.value)
		}
		this.setError(undefined)
	}

	addItem(key: string | number, item: T): void {
		const itemIndex = this.collection.value?.findIndex((item): boolean => item[this.resourceIdentifierKey] === key)
		if (itemIndex !== undefined) {
			if (this.collection.value === undefined) {
				this.collection.value = [item]
			} else {
				this.collection.value[itemIndex] = item
			}
		}
	}

	setCollection(collection: Array<T>): void {
		this.collection.value = collection
		this.setError(undefined)
	}

	async getItemBy(filters: { [cle: string]: unknown }[]): Promise<T | null> {
		let item: T | T[] | null = ArrayService.applyFilters(this.collection.value ?? [], filters)
		if (!item) {
			try {
				const { data }: { data: Array<T> } = await ApiService.get({
					resource: this.resource,
					config: { params: { [key]: identifier } }
				})
				item = data.find((item) => item[key] === identifier) ?? null
			} catch (error) {
				console.log(error)
			}

			if (!item) {
				return null
			}
		}

		return Array.isArray(item) && item.length > 0 ? item[0] : item
	}

	async fetchItem(objectId: string | number): Promise<T | null> {
		return ApiService.get({ resource: this.resource, slug: objectId.toString() })
			.then(({ data }: { data: T }) => {
				this.setItem(data)
				return data
			})
			.catch((error) => {
				this.setError(error)
				return null
			})
	}
	async fetchCollection(params?: Record<string, string>): Promise<T[] | null> {
		return ApiService.get({ resource: this.resource, config: { params } })
			.then(({ data }: { data: T[] }) => {
				this.setCollection(data)
				return data
			})
			.catch((error) => {
				this.setError(error)
				return null
			})
	}
	async put(object: T | FormData): Promise<T | null> {
		let objectId: FormDataEntryValue | number | null | string | undefined

		if (object instanceof FormData) {
			objectId = object.get(this.resourceIdentifierKey)
		} else {
			objectId = object[this.resourceIdentifierKey]
		}

		if (!objectId) {
			return null
		}

		return ApiService.put({ resource: this.resource, slug: objectId, data: object })
			.then(({ data }: { data: T }) => {
				this.setItem(data)
				return data
			})
			.catch((error) => {
				this.setError(error)
				return null
			})
	}
	async post(object: T | FormData): Promise<T | null> {
		return ApiService.post({ resource: this.resource, data: object })
			.then(({ data }: { data: T }) => {
				this.setItem(data)
				return data
			})
			.catch((error) => {
				this.setError(error)
				return null
			})
	}
	async delete(objectId: string | number): Promise<void> {
		ApiService.delete({ resource: this.resource, slug: objectId.toString() }).catch((error): void => {
			this.setError(error)
		})
	}
	public getStoreSetup = () => {
		return {
			...this.getStates(),
			...this.getActions(),
			...this.getGetters()
		}
	}

	public defineStore() {
		return defineStore(this.name, this.getStoreSetup)
	}
}
