import { moveItemInArray } from '@angular/cdk/drag-drop'
import { Injectable, inject } from '@angular/core'
import { Router } from '@angular/router'
import { sortCategories, sortMenuItemsInCategory } from '@core/utils'
import {
	CategoryResponse,
	CreateCategoryRequest,
	CreateMenuItemRequest,
	DefaultService,
	MenuItemResponse,
	UpdateCategoryRequest,
	UpdateMenuItemRequest,
	UpdateSortOrderRequest,
} from '@api-client/angular'
import { HotToastService } from '@ngxpert/hot-toast'
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'
import { ComponentStore } from '@ngrx/component-store'
import { Observable, forkJoin, map, tap } from 'rxjs'
import { I18nService } from '../../../../core'
import { extractHttpErrorMessage } from '../../../../shared'

export interface MenuSettingsState {
	categories: CategoryResponse[]
	selectedCategory?: CategoryResponse
	selectedSubcategory?: CategoryResponse
	menuItems: MenuItemResponse[]
	selectedItem?: MenuItemResponse
}

@UntilDestroy({ checkProperties: true })
@Injectable()
export class MenuSettingsStore extends ComponentStore<MenuSettingsState> {
	private readonly _router = inject(Router)
	private readonly _toast = inject(HotToastService)
	private readonly _i18nService = inject(I18nService)
	private readonly _apiService = inject(DefaultService)

	constructor() {
		super({
			menuItems: [],
			categories: [],
			selectedCategory: undefined,
			selectedSubcategory: undefined,
			selectedItem: undefined,
		})
		this.loadData()
	}

	//#region State Updaters
	private readonly setSelectedCategory = this.updater((state, category: CategoryResponse) => ({
		...state,
		selectedCategory: category,
		selectedSubcategory: undefined,
	}))

	private readonly setSelectedSubcategory = this.updater((state, category: CategoryResponse) => {
		const parentCategory = state.categories.find((_category) => _category.id === category.parentCategoryId)
		return {
			...state,
			selectedCategory: parentCategory,
			selectedSubcategory: category,
		}
	})

	private readonly setSelectedItem = this.updater((state, selectedItem: MenuItemResponse | undefined) => ({
		...state,
		selectedItem,
	}))
	//#endregion

	//#region Selectors
	readonly items = this.selectSignal((state) => {
		if (state.selectedSubcategory) {
			return sortMenuItemsInCategory(
				state.menuItems.filter((item) =>
					item.subcategories?.map((_category) => _category.categoryId).includes(state.selectedSubcategory!.id),
				),
				state.selectedSubcategory.id,
			)
		} else if (state.selectedCategory) {
			return sortMenuItemsInCategory(
				state.menuItems.filter((item) =>
					item.categories.map((_category) => _category.categoryId).includes(state.selectedCategory!.id),
				),
				state.selectedCategory.id,
			)
		} else {
			return []
		}
	})

	readonly selectedCategory = this.selectSignal((state) => state.selectedCategory)
	readonly selectedSubcategory = this.selectSignal((state) => state.selectedSubcategory)
	readonly selectedItem = this.selectSignal((state) => state.selectedItem)
	readonly menuItems = this.selectSignal((state) => state.menuItems)
	readonly categories = this.selectSignal((state) => sortCategories(state.categories))
	//#endregion

	//#region Public Methods
	selectItem(item: MenuItemResponse | undefined): void {
		this.onSelectItem(item)
	}

	saveItem(item: CreateMenuItemRequest): Observable<void> {
		return this._apiService.menuControllerCreateItem(item).pipe(
			tap((newItem) => {
				const menuItems = this.menuItems()
				menuItems.push(newItem)
				this.patchState({ menuItems, selectedItem: newItem })
			}),
			map(() => void 0),
		)
	}

	updateItem(id: string, item: UpdateMenuItemRequest): Observable<void> {
		return this._apiService.menuControllerUpdateItemById(id, item).pipe(
			tap((updatedItem) => {
				const menuItems = this.menuItems()
				const index = menuItems.findIndex((item) => item.id === id)
				menuItems[index] = updatedItem
				this.patchState({ menuItems, selectedItem: updatedItem })
			}),
			map(() => void 0),
		)
	}

	updateItemSortOrder(previousIndex: number, currentIndex: number, categoryId: string): void {
		let menuItems = this.menuItems()

		const request: UpdateSortOrderRequest[] = []

		const itemsByCategory = sortMenuItemsInCategory(
			menuItems.filter((item) => item.categories.map((category) => category.categoryId).includes(categoryId)),
			categoryId,
		)

		moveItemInArray(itemsByCategory, previousIndex, currentIndex)

		for (const [index, item] of itemsByCategory.entries()) {
			const category = item.categories.find((_category) => _category.categoryId === categoryId)!
			category.sortOrder = index + 1
			request.push({ id: item.id, sortOrder: category.sortOrder })
		}

		menuItems = menuItems.map((_item) => itemsByCategory.find((_xItem) => _xItem.id === _item.id) ?? _item)

		this._apiService.menuControllerUpdateItemSortOrder(categoryId, request).subscribe(() => {
			this.patchState({ menuItems })
		})
	}

	toggleItemArchive(item: MenuItemResponse): void {
		const isArchived = !item.isArchived
		this._apiService
			.menuControllerToggleItemArchive(item.id as string, { isArchived })
			.pipe(
				untilDestroyed(this),
				this._toast.observe({
					success: this._i18nService.translate(isArchived ? 'item_archive_success' : 'item_restore_success', {
						itemName: item.name,
					}),
					error: (error) => extractHttpErrorMessage(error),
				}),
			)
			.subscribe({
				next: () => {
					const menuItems = this.menuItems()
					const index = menuItems.findIndex((_item) => _item.id === item.id)
					const updatedItem = { ...item, isArchived }
					menuItems[index] = updatedItem
					this.patchState({ menuItems, selectedItem: updatedItem })
				},
			})
	}

	toggleItemVisibility(item: MenuItemResponse, categoryId: string) {
		const isVisible = !item.categories
			// eslint-disable-next-line unicorn-x/prefer-spread
			.concat(...(item.subcategories ?? []))
			.find((_category) => _category.categoryId === categoryId)?.isVisible

		this._apiService
			.menuControllerToggleItemVisible(item.id, categoryId, { isVisible })
			.pipe(untilDestroyed(this))
			.subscribe({
				next: () => {
					const menuItems = this.menuItems()
					const index = menuItems.findIndex((_item) => _item.id === item.id)
					// eslint-disable-next-line unicorn-x/prefer-spread
					item.categories = item.categories.concat(...(item.subcategories ?? [])).map((_category) => {
						if (_category.categoryId === categoryId) _category.isVisible = isVisible
						return _category
					})
					menuItems[index] = item
					this.patchState({ menuItems, selectedItem: item })
				},
				error: (error) => {
					this._toast.error(error.error.message)
				},
			})
	}

	deleteItem(item: MenuItemResponse): void {
		this._apiService
			.menuControllerDeleteItemById(item.id)
			.pipe(
				untilDestroyed(this),
				this._toast.observe({
					success: this._i18nService.translate('item_delete_success', { itemName: item.name }),
					error: (error) => extractHttpErrorMessage(error),
				}),
			)
			.subscribe({
				next: () => {
					const menuItems = this.menuItems()
					const index = menuItems.findIndex((_item) => _item.id === item.id)
					menuItems.splice(index, 1)
					this.patchState({ menuItems, selectedItem: undefined })
				},
			})
	}

	selectCategory(category: CategoryResponse): void {
		this.setSelectedCategory(category)
	}

	selectSubcategory(category: CategoryResponse): void {
		this.setSelectedSubcategory(category)
	}

	selectNextCategory(): void {
		const categories = this.categories()
		const selectedCategory = this.selectedCategory()
		const selectedSubcategory = this.selectedSubcategory()

		if (!selectedCategory) return

		if (selectedSubcategory) {
			const subcategories = categories.filter((_category) => _category.parentCategoryId === selectedCategory.id)
			if (subcategories.length === 0) return

			const index = subcategories.findIndex((_category) => _category.id === selectedSubcategory.id)
			if (index === subcategories.length - 1) return
			this.selectSubcategory(subcategories[index + 1])
		} else {
			const rootCategories = categories.filter((_category) => !_category.parentCategoryId)
			const index = rootCategories.findIndex((_category) => _category.id === selectedCategory.id)
			if (index === rootCategories.length - 1) return
			this.setSelectedCategory(rootCategories[index + 1])
		}
	}

	selectPreviousCategory(): void {
		const categories = this.categories()
		const selectedCategory = this.selectedCategory()
		const selectedSubCategory = this.selectedSubcategory()

		if (!selectedCategory) return

		if (selectedSubCategory) {
			const subcategories = categories.filter((_category) => _category.parentCategoryId === selectedCategory.id)
			if (subcategories.length === 0) return

			const index = subcategories.findIndex((_category) => _category.id === selectedSubCategory.id)
			if (index === 0) return
			this.selectSubcategory(subcategories[index - 1])
		} else {
			const rootCategories = categories.filter((_category) => !_category.parentCategoryId)
			const index = rootCategories.findIndex((_category) => _category.id === selectedCategory.id)
			if (index === 0) return
			this.setSelectedCategory(rootCategories[index - 1])
		}
	}

	selectCategoryById(id: string): void {
		const categories = this.categories()

		const category = categories.find((category) => id === category.id)
		if (!category) return

		this.setSelectedCategory(category)
	}

	saveCategory(category: CreateCategoryRequest): void {
		this._apiService
			.menuControllerCreateCategory(category)
			.pipe(untilDestroyed(this))
			.subscribe({
				next: (newCategory) => {
					const categories = this.categories()
					categories.push(newCategory)
					const state: Partial<MenuSettingsState> = { categories }

					if (category.parentCategoryId) {
						state.selectedSubcategory = newCategory
					} else {
						state.selectedCategory = newCategory
					}

					this.patchState(state)

					this._toast.success(this._i18nService.translate('category_create_success'))
				},
			})
	}

	updateCategory(id: string, category: UpdateCategoryRequest): void {
		this._apiService
			.menuControllerUpdateCategoryById(id, category)
			.pipe(untilDestroyed(this))
			.subscribe({
				next: (updatedCategory) => {
					const categories = this.categories()

					const index = categories.findIndex((x) => x.id === updatedCategory.id)
					categories[index] = updatedCategory

					this.patchState({ categories })
					this._toast.success(this._i18nService.translate('category_rename_success'))
				},
			})
	}

	toggleCategoryVisible(category: CategoryResponse): void {
		category.isVisible = !category.isVisible
		this._apiService
			.menuControllerToggleCategoryVisible(category.id, { isVisible: category.isVisible })
			.pipe(untilDestroyed(this))
			.subscribe({
				next: () => {
					const categories = this.categories()
					const index = categories.findIndex((x) => x.id === category.id)
					categories[index] = category
					this.patchState({ categories })
					this._toast.success(this._i18nService.translate('item_update_success', { itemName: category.name }))
				},
			})
	}

	toggleCategoryArchive(category: CategoryResponse): void {
		category.isArchived = !category.isArchived
		this._apiService
			.menuControllerToggleCategoryArchive(category.id, { isArchived: category.isArchived })
			.pipe(untilDestroyed(this))
			.subscribe({
				next: () => {
					const categories = this.categories()
					const index = categories.findIndex((_category) => _category.id === category.id)
					categories[index] = category

					this.patchState({ categories })
					this._toast.success(
						this._i18nService.translate(category.isArchived ? 'item_archive_success' : 'item_restore_success', {
							itemName: category.name,
						}),
					)
				},
			})
	}

	updateCategorySortOrder(previousIndex: number, currentIndex: number, parentCategoryId?: string): void {
		let categories = this.categories()

		if (parentCategoryId) {
			const subcategories = categories.filter((category) => category.parentCategoryId === parentCategoryId)
			moveItemInArray(subcategories, previousIndex, currentIndex)
			for (const [index, category] of subcategories.entries()) category.sortOrder = index + 1
			categories = categories.map(
				(category) => subcategories.find((_subcategory) => _subcategory.id === category.id) ?? category,
			)
			this.patchState({ categories })
			const request = subcategories.map((category) => ({ id: category.id, sortOrder: category.sortOrder }))
			this._apiService.menuControllerUpdateCategorySortOrder(request).pipe(untilDestroyed(this)).subscribe()
		} else {
			moveItemInArray(categories, previousIndex, currentIndex)
			for (const [index, category] of categories.entries()) category.sortOrder = index + 1
			this.patchState({ categories })
			const request = categories.map((category) => ({ id: category.id, sortOrder: category.sortOrder }))
			this._apiService.menuControllerUpdateCategorySortOrder(request).pipe(untilDestroyed(this)).subscribe()
		}
	}

	deleteCategory(category: CategoryResponse): void {
		this._apiService
			.menuControllerDeleteCategoryById(category.id)
			.pipe(untilDestroyed(this))
			.subscribe({
				next: () => {
					const categories = this.categories()
					const index = categories.findIndex((category) => category.id === category.id)
					categories.splice(index, 1)

					const state: Partial<MenuSettingsState> = { categories }

					if (category.parentCategoryId) {
						const subcategories = categories.filter(
							(_category) => _category.parentCategoryId === category.parentCategoryId,
						)
						if (subcategories.length === 0) {
							state.selectedSubcategory = undefined
						} else {
							const subIndex = subcategories.findIndex((_category) => _category.id === category.id)
							const nextIndex = subIndex === 0 ? 1 : subIndex - 1
							state.selectedSubcategory = subcategories[nextIndex]
						}
					} else {
						const nextIndex = index === 0 ? 1 : index - 1
						state.selectedCategory = categories[nextIndex]
					}

					this.patchState(state)
					this._toast.success(this._i18nService.translate('category_delete_success'))
				},
			})
	}
	//#endregion

	//#region Private Methods
	private loadData(): void {
		forkJoin([
			this._apiService.menuControllerFindAllCategories(),
			this._apiService.menuControllerFindAllItem(),
		]).subscribe({
			next: ([categories, menuItems]) => {
				this.patchState({ categories, menuItems })
				// eslint-disable-next-line unicorn-x/prefer-array-find
				this.setSelectedCategory(categories.filter((_category) => !_category.parentCategoryId)[0])
			},
		})
	}

	private onSelectItem(item: MenuItemResponse | undefined): void {
		const url = item ? '/settings/menu/edit' : '/settings/menu'
		this._router.navigateByUrl(url)
		this.setSelectedItem(item)
	}
	//#endregion
}
