import { getLocalizedString } from "@olenbetong/appframe-core";
import { AppErrorBoundary } from "@olenbetong/appframe-ds";
import { type ReactNode, useCallback, useMemo, useState } from "react";
import { createPortal } from "react-dom";

import { AppMenu, type AppMenuArticle } from "../AppMenu/AppMenu.js";
import { AppMenuToggle } from "../AppMenu/AppMenuToggle.js";
import type { AppMenuUser } from "../AppMenu/AppMenuUserMenu.js";
import { createDeveloperCategories } from "../AppMenu/developerCategories.js";
import { useServiceWorkerUpdate } from "../AppMenu/useServiceWorkerUpdate.js";

const hostName = globalThis.af?.article?.hostName ?? globalThis.location?.hostname ?? "";

/*
 * `aviw_WebShared_Articles` stores every published version of an article as its
 * own row, and rows for all sites share the view. Without the host name filter
 * and `ArticleVersion IS NULL` (which keeps only the development row, i.e. the
 * canonical article) every app shows up once per published version.
 */
const menuArticlesWhereClause = [
	`HostName = '${hostName.replace(/'/g, "''")}'`,
	"ArticleVersion IS NULL",
	"LastPublished IS NOT NULL",
	"Authenticated = 1",
	"ListOnFrontPage = 1",
	"SysApp = 0",
	"HideFromNav = 0",
	"Hidden = 0",
	"ISNULL(ParentArticleId, '') <> 'news'",
].join(" AND ");

// Created once — shared across all AppShell instances on a page.
const dsMenuArticles = af.data.generateApiDataObject<AppMenuArticle>({
	resource: "aviw_WebShared_Articles",
	id: "dsMenuArticles",
	allowUpdate: false,
	allowInsert: false,
	allowDelete: false,
	dynamicLoading: false,
	fields: [
		{ name: "ArticleId", type: "string", nullable: false },
		{ name: "Title", type: "string", nullable: true },
		{ name: "MenuGroup", type: "string", nullable: true },
		{ name: "Path", type: "string", nullable: true },
		{ name: "IsFavorite", type: "boolean", nullable: true },
	],
	parameters: {
		maxRecords: -1,
		whereClause: menuArticlesWhereClause,
		sortOrder: [{ MenuGroup: af.data.SortOrder.Asc }, { Title: af.data.SortOrder.Asc }],
	},
});

// Toggles the current user's favorite state for an article.
const procSetArticleFavorite = new af.ProcedureAPI<{ HostName: string; ArticleID: string; Favorite: boolean }, unknown>(
	{
		procedureId: "astp_WebShared_SetArticleFavorite",
		parameters: [
			{ name: "HostName", type: "string", required: true, hasDefault: false },
			{ name: "ArticleID", type: "string", required: true, hasDefault: false },
			{ name: "Favorite", type: "boolean", required: false, hasDefault: true },
		],
		timeout: 30000,
	},
);

// The current user's employee record, used for the photo and full name.
const dsAppMenuUser = af.data.generateApiDataObject<AppMenuUser>({
	resource: "aviw_Personnel_Employees_Lookup",
	id: "dsAppMenuUser",
	allowUpdate: false,
	allowInsert: false,
	allowDelete: false,
	dynamicLoading: false,
	fields: [
		{ name: "PrimKey", type: "string", nullable: false },
		{ name: "PersonID", type: "string", nullable: false },
		{ name: "FirstName", type: "string", nullable: true },
		{ name: "LastName", type: "string", nullable: true },
		{ name: "Fullname", type: "string", nullable: true },
		{ name: "ImageHash", type: "number", nullable: true },
	],
	parameters: {
		maxRecords: 1,
		// Resolved server side so it doesn't depend on the session being hydrated.
		whereClause: "PersonID = SUSER_SNAME()",
	},
});

export type AppShellProps = {
	children: ReactNode;
};

/**
 * Standard Synergi app chrome: error boundary, app menu and the toolbar toggle.
 *
 * This component is Designsystemet-only. Apps that still render MUI components
 * should additionally wrap it in `MuiThemeProvider` from
 * `@olenbetong/synergi-react/mui`, which bridges the Designsystemet colour
 * tokens into a MUI theme.
 */
export function AppShell({ children }: AppShellProps) {
	const [isAppMenuOpen, setIsAppMenuOpen] = useState(false);
	const { isUpdateAvailable, installUpdate } = useServiceWorkerUpdate();

	// The developer tools are only reachable for developers, so hide them for
	// everyone else instead of showing links that all return 403.
	const categories = useMemo(() => (globalThis.af?.userSession?.isDeveloper ? createDeveloperCategories() : []), []);

	// The portal isn't listed with the apps, so link to it from the menu itself
	// unless that's the article we're already on.
	const homeLink = useMemo(() => {
		const articleId = (globalThis.af?.article?.id ?? "").replace(/\.\d+$/, "");
		return articleId === "portal" ? undefined : { label: getLocalizedString("Front page"), href: "/portal" };
	}, []);

	const toggleFavorite = useCallback(async (articleId: string, favorite: boolean) => {
		await procSetArticleFavorite.execute({ HostName: hostName, ArticleID: articleId, Favorite: favorite });
	}, []);

	const appMenuSlot = document.getElementById("ob-toolbar-app-menu");

	return (
		<AppErrorBoundary>
			{appMenuSlot &&
				createPortal(
					<AppMenuToggle
						open={isAppMenuOpen}
						hasUpdate={isUpdateAvailable}
						aria-controls="ob-app-menu"
						onClick={() => setIsAppMenuOpen((open) => !open)}
					/>,
					appMenuSlot,
				)}
			<AppMenu
				id="ob-app-menu"
				dataObject={dsMenuArticles}
				userDataObject={dsAppMenuUser}
				categories={categories}
				homeLink={homeLink}
				onToggleFavorite={toggleFavorite}
				hasUpdate={isUpdateAvailable}
				onInstallUpdate={installUpdate}
				open={isAppMenuOpen}
				onClose={() => setIsAppMenuOpen(false)}
			/>
			{children}
		</AppErrorBoundary>
	);
}
