import { getLocalizedString, getUserImage } from "@olenbetong/appframe-core";
import type { DataObject } from "@olenbetong/appframe-data";
import { useData } from "@olenbetong/appframe-react";
import { useEffect, useId, type KeyboardEvent, useRef, useState } from "react";

import type { AppMenuLink } from "./types.js";

/** Fields required from `aviw_Personnel_Employees_Lookup`. */
export type AppMenuUser = {
	PrimKey: string;
	PersonID: string;
	FirstName: string | null;
	LastName: string | null;
	Fullname: string | null;
	ImageHash: number | null;
};

/** Same entries as the sidebar user disclosure menu. */
export function defaultUserMenuItems(): AppMenuLink[] {
	return [
		{ label: getLocalizedString("My profile"), href: "/edit-profile" },
		{ label: getLocalizedString("Change domain"), href: "/change-domain" },
		{ label: getLocalizedString("Change theme"), href: "/change-theme" },
		{ label: getLocalizedString("Change password"), href: "/change-password" },
		{ label: getLocalizedString("Troubleshooting"), href: "/diagnostics", separatorBefore: true },
		{ label: getLocalizedString("Log out"), href: "/logout", separatorBefore: true },
	];
}

export type AppMenuUserMenuProps = {
	/**
	 * Data object over `aviw_Personnel_Employees_Lookup` filtered to the current
	 * user (`PersonID = af.userSession.login`). See `AppMenuUser`.
	 */
	dataObject: DataObject<AppMenuUser>;
	/** Overrides the default menu entries. */
	items?: AppMenuLink[];
	/** Shown below the name. Defaults to `af.userSession.domain`. */
	subtitle?: string;
};

/**
 * Disclosure menu showing the signed in user's photo and full name, with the
 * account related links from the old sidebar.
 *
 * Users without an employee record still get the menu, using the name from the
 * Appframe user session, so log out and password changes stay reachable.
 */
export function AppMenuUserMenu({ dataObject, items, subtitle }: AppMenuUserMenuProps) {
	let containerRef = useRef<HTMLDivElement>(null);
	let triggerRef = useRef<HTMLButtonElement>(null);
	let listRef = useRef<HTMLUListElement>(null);
	let panelId = useId();
	let [isOpen, setIsOpen] = useState(false);
	let [hasPhotoError, setHasPhotoError] = useState(false);

	let [user] = useData<AppMenuUser>(dataObject);
	let session = globalThis.af?.userSession;
	let menuItems = items ?? defaultUserMenuItems();

	// Close when the pointer goes down outside the disclosure.
	useEffect(() => {
		if (!isOpen) return;

		function onDocumentPointerDown(event: PointerEvent) {
			if (!containerRef.current?.contains(event.target as Node)) setIsOpen(false);
		}

		document.addEventListener("pointerdown", onDocumentPointerDown);
		return () => document.removeEventListener("pointerdown", onDocumentPointerDown);
	}, [isOpen]);

	function menuLinks() {
		return [...(listRef.current?.querySelectorAll<HTMLElement>("a, button") ?? [])];
	}

	/** Focus a menu entry once the list has been shown. */
	function focusEntry(position: "first" | "last") {
		requestAnimationFrame(() => {
			let links = menuLinks();
			(position === "first" ? links[0] : links[links.length - 1])?.focus();
		});
	}

	function close({ focusTrigger = true } = {}) {
		setIsOpen(false);
		if (focusTrigger) triggerRef.current?.focus();
	}

	function onTriggerKeyDown(event: KeyboardEvent<HTMLButtonElement>) {
		if (event.key === "ArrowDown" || event.key === "ArrowUp") {
			event.preventDefault();
			setIsOpen(true);
			focusEntry(event.key === "ArrowDown" ? "first" : "last");
		}
	}

	function onListKeyDown(event: KeyboardEvent<HTMLUListElement>) {
		let links = menuLinks();
		let index = links.indexOf(document.activeElement as HTMLElement);
		if (index < 0 || links.length === 0) return;

		let next: number;
		if (event.key === "ArrowDown") next = (index + 1) % links.length;
		else if (event.key === "ArrowUp") next = (index - 1 + links.length) % links.length;
		else if (event.key === "Home") next = 0;
		else if (event.key === "End") next = links.length - 1;
		else return;

		event.preventDefault();
		event.stopPropagation();
		links[next].focus();
	}

	let fullName =
		user?.Fullname?.trim() ||
		[user?.FirstName, user?.LastName].filter(Boolean).join(" ").trim() ||
		session?.name ||
		session?.login ||
		getLocalizedString("Account");
	let photo = user && !hasPhotoError ? getUserImage(user.PrimKey, user.PersonID, user.ImageHash, "sm") : "";
	// The domain is set server side when the session is created.
	let domain = subtitle ?? session?.domain ?? "";

	return (
		<div
			ref={containerRef}
			className="ob-app-menu-user"
			onKeyDown={(event) => {
				// Don't let Escape bubble to the dialog and close the whole menu.
				if (event.key === "Escape" && isOpen) {
					event.stopPropagation();
					event.preventDefault();
					close();
				}
			}}
			onBlur={(event) => {
				// Tabbing (or clicking) out of the disclosure closes it.
				if (isOpen && !event.currentTarget.contains(event.relatedTarget)) close({ focusTrigger: false });
			}}
		>
			<button
				ref={triggerRef}
				type="button"
				className="ob-app-menu-user__trigger"
				aria-expanded={isOpen}
				aria-controls={panelId}
				onClick={() => setIsOpen((open) => !open)}
				onKeyDown={onTriggerKeyDown}
			>
				<span className="ob-app-menu-user__avatar">
					{photo ? (
						<img className="ob-app-menu-user__image" src={photo} alt="" onError={() => setHasPhotoError(true)} />
					) : (
						<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" focusable="false">
							<path d="M12 12a5 5 0 1 0 0-10 5 5 0 0 0 0 10m0 2c-4.42 0-8 2.24-8 5v1a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1v-1c0-2.76-3.58-5-8-5" />
						</svg>
					)}
				</span>
				<span className="ob-app-menu-user__meta">
					<span className="ob-app-menu-user__name">{fullName}</span>
					{domain && <span className="ob-app-menu-user__domain">{domain}</span>}
				</span>
			</button>

			<ul id={panelId} ref={listRef} className="ob-app-menu-user__list" hidden={!isOpen} onKeyDown={onListKeyDown}>
				{menuItems.map((item) => (
					<li key={item.label} className={item.separatorBefore ? "ob-app-menu-user__item--divided" : undefined}>
						{item.href ? (
							<a href={item.href} target={item.target} rel={item.rel} onClick={item.onClick}>
								{item.label}
							</a>
						) : (
							<button
								type="button"
								{...item.attributes}
								onClick={() => {
									item.onClick?.();
									if (!item.keepOpen) close();
								}}
							>
								{item.label}
							</button>
						)}
					</li>
				))}
			</ul>
		</div>
	);
}
