/*
 * Copyright (C) 2026 Fluxer Contributors
 *
 * This file is part of Fluxer.
 *
 * Fluxer is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Fluxer is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
 */

import type {ModalRender} from '@app/actions/ModalRender';
import {Logger} from '@app/lib/Logger';
import ToastStore from '@app/stores/ToastStore';
import {makeAutoObservable} from 'mobx';
import type React from 'react';

const logger = new Logger('ModalStore');

type KeyboardModeStateResolver = () => boolean;
type KeyboardModeRestoreCallback = (showIntro: boolean) => void;

let keyboardModeStateResolver: KeyboardModeStateResolver | undefined;
let keyboardModeRestoreCallback: KeyboardModeRestoreCallback | undefined;

export function registerKeyboardModeStateResolver(resolver: KeyboardModeStateResolver): void {
	keyboardModeStateResolver = resolver;
}

export function registerKeyboardModeRestoreCallback(callback: KeyboardModeRestoreCallback): void {
	keyboardModeRestoreCallback = callback;
}

const BASE_Z_INDEX = 10000;
const Z_INDEX_INCREMENT = 2;

export function getZIndexForStack(stackIndex: number): number {
	return BASE_Z_INDEX + stackIndex * Z_INDEX_INCREMENT;
}

export function getBackdropZIndexForStack(stackIndex: number): number {
	return BASE_Z_INDEX + stackIndex * Z_INDEX_INCREMENT - 1;
}

interface Modal {
	modal: ModalRender;
	key: string;
	focusReturnTarget: HTMLElement | null;
	keyboardModeEnabled: boolean;
	isBackground: boolean;
}

interface ModalWithStackInfo extends Modal {
	stackIndex: number;
	isVisible: boolean;
	needsBackdrop: boolean;
	isTopmost: boolean;
}

interface PushOptions {
	isBackground?: boolean;
}

class ModalStore {
	modals: Array<Modal> = [];
	private hasShownStackingToast = false;

	constructor() {
		makeAutoObservable(this, {}, {autoBind: true});
	}

	push(modal: ModalRender, key: string | number, options: PushOptions = {}): void {
		const isBackground = options.isBackground ?? false;

		const keyboardModeEnabled = keyboardModeStateResolver ? keyboardModeStateResolver() : false;

		this.modals.push({
			modal,
			key: key.toString(),
			focusReturnTarget: this.getActiveElement(),
			keyboardModeEnabled,
			isBackground,
		});

		this.checkAlternatingStackPattern();
	}

	private getModalSignature(modal: Modal): string {
		const element = modal.modal();
		const typeName = typeof element.type === 'function' ? element.type.name : String(element.type);
		try {
			return `${typeName}:${JSON.stringify(element.props)}`;
		} catch {
			return `${typeName}:${modal.key}`;
		}
	}

	private checkAlternatingStackPattern(): void {
		if (this.hasShownStackingToast) return;
		if (this.modals.length < 5) return;

		const lastFive = this.modals.slice(-5);
		const signatures = lastFive.map((m) => this.getModalSignature(m));

		const signatureA = signatures[0];
		const signatureB = signatures[1];

		if (signatureA === signatureB) return;

		const isAlternating = signatures[2] === signatureA && signatures[3] === signatureB && signatures[4] === signatureA;

		if (isAlternating) {
			this.hasShownStackingToast = true;
			ToastStore.createToast({type: 'info', children: 'Having fun?', timeout: 3000});
		}
	}

	update(key: string | number, updater: (currentModal: ModalRender) => ModalRender, options?: PushOptions): void {
		const modalIndex = this.modals.findIndex((modal) => modal.key === key.toString());
		if (modalIndex === -1) return;

		const existingModal = this.modals[modalIndex];
		this.modals[modalIndex] = {
			...existingModal,
			modal: updater(existingModal.modal),
			isBackground: options?.isBackground ?? existingModal.isBackground,
		};
	}

	pop(key?: string | number): void {
		let removed: Modal | undefined;
		let wasTopmost = false;
		if (key) {
			const keyStr = key.toString();
			const idx = this.modals.findIndex((modal) => modal.key === keyStr);
			if (idx !== -1) {
				wasTopmost = idx === this.modals.length - 1;
				[removed] = this.modals.splice(idx, 1);
			}
		} else {
			removed = this.modals.pop();
			wasTopmost = true;
		}

		if (removed && wasTopmost) {
			logger.debug(`ModalStore.pop restoring focus topmost=${wasTopmost} keyboardMode=${removed.keyboardModeEnabled}`);
			this.scheduleFocus(removed.focusReturnTarget, removed.keyboardModeEnabled);
		}
	}

	popAll(): void {
		const lastModal = this.modals.at(-1);
		this.modals = [];
		if (lastModal) {
			this.scheduleFocus(lastModal.focusReturnTarget, lastModal.keyboardModeEnabled);
		}
	}

	popByType<T>(component: React.ComponentType<T>): void {
		const modalIndex = this.modals.findLastIndex((modal) => modal.modal().type === component);
		if (modalIndex === -1) return;

		const wasTopmost = modalIndex === this.modals.length - 1;
		const [removed] = this.modals.splice(modalIndex, 1);

		if (removed && wasTopmost) {
			logger.debug(
				`ModalStore.popByType restoring focus topmost=${wasTopmost} keyboardMode=${removed.keyboardModeEnabled}`,
			);
			this.scheduleFocus(removed.focusReturnTarget, removed.keyboardModeEnabled);
		}
	}

	get orderedModals(): Array<ModalWithStackInfo> {
		const topmostRegularIndex = this.modals.findLastIndex((m) => !m.isBackground);
		const topmostIndex = this.modals.length - 1;

		return this.modals.map((modal, index) => {
			const isVisible = modal.isBackground || index === topmostRegularIndex;
			const needsBackdrop = modal.isBackground || (!modal.isBackground && index === topmostRegularIndex);

			return {
				...modal,
				stackIndex: index,
				isVisible,
				needsBackdrop,
				isTopmost: index === topmostIndex,
			};
		});
	}

	getModal(): Modal | undefined {
		return this.modals.at(-1);
	}

	hasModalOpen(): boolean {
		return this.modals.length > 0;
	}

	hasModal(key: string): boolean {
		return this.modals.some((modal) => modal.key === key);
	}

	hasModalOfType<T>(component: React.ComponentType<T>): boolean {
		return this.modals.some((modal) => modal.modal().type === component);
	}

	private getActiveElement(): HTMLElement | null {
		const active = document.activeElement;
		return active instanceof HTMLElement ? active : null;
	}

	private scheduleFocus(target: HTMLElement | null, keyboardModeEnabled: boolean): void {
		logger.debug(
			`ModalStore.scheduleFocus target=${target ? target.tagName : 'null'} keyboardMode=${keyboardModeEnabled}`,
		);
		if (!target) return;
		queueMicrotask(() => {
			if (!target.isConnected) {
				logger.debug('ModalStore.scheduleFocus aborted: target disconnected');
				return;
			}
			try {
				target.focus({preventScroll: true});
				logger.debug('ModalStore.scheduleFocus applied focus to target');
			} catch (error) {
				logger.error('ModalStore.scheduleFocus failed to focus target', error as Error);
				return;
			}
			if (keyboardModeEnabled && keyboardModeRestoreCallback) {
				logger.debug('ModalStore.scheduleFocus re-entering keyboard mode');
				keyboardModeRestoreCallback(false);
			}
		});
	}
}

export default new ModalStore();
