---
import "@fontsource/roboto/400.css";
import "@fontsource/roboto/500.css";
import "@fontsource/roboto/700.css";

import ConfigCarrier from "@components/ConfigCarrier.astro";
import { profileConfig, siteConfig } from "@/config";
import {
	AUTO_MODE,
	BANNER_HEIGHT,
	BANNER_HEIGHT_EXTEND,
	BANNER_HEIGHT_HOME,
	DARK_MODE,
	DEFAULT_THEME,
	LIGHT_MODE,
	PAGE_WIDTH,
} from "../constants/constants";
import { defaultFavicons } from "../constants/icon";
import type { Favicon } from "../types/config";
import { pathsEqual, url } from "../utils/url-utils";
import "katex/dist/katex.css";
import SpeedInsights from "@vercel/speed-insights/astro";

interface Props {
	title?: string;
	banner?: string;
	description?: string;
	lang?: string;
	setOGTypeArticle?: boolean;
}

let { title, banner, description, lang, setOGTypeArticle } = Astro.props;

// apply a class to the body element to decide the height of the banner, only used for initial page load
// Swup can update the body for each page visit, but it's after the page transition, causing a delay for banner height change
// so use Swup hooks instead to change the height immediately when a link is clicked
const isHomePage = pathsEqual(Astro.url.pathname, url("/"));

// defines global css variables
// why doing this in Layout instead of GlobalStyles: https://github.com/withastro/astro/issues/6728#issuecomment-1502203757
const configHue = siteConfig.themeColor.hue;
if (!banner || typeof banner !== "string" || banner.trim() === "") {
	banner = siteConfig.banner.src;
}

// TODO don't use post cover as banner for now
banner = siteConfig.banner.src;

const enableBanner = siteConfig.banner.enable;

let pageTitle: string;
if (title) {
	pageTitle = `${title} - ${siteConfig.title}`;
} else {
	pageTitle = `${siteConfig.title} - ${siteConfig.subtitle}`;
}

const favicons: Favicon[] =
	siteConfig.favicon.length > 0 ? siteConfig.favicon : defaultFavicons;

// const siteLang = siteConfig.lang.replace('_', '-')
if (!lang) {
	lang = `${siteConfig.lang}`;
}
const siteLang = lang.replace("_", "-");

const bannerOffsetByPosition = {
	top: `${BANNER_HEIGHT_EXTEND}vh`,
	center: `${BANNER_HEIGHT_EXTEND / 2}vh`,
	bottom: "0",
};
const bannerOffset =
	bannerOffsetByPosition[siteConfig.banner.position || "center"];
---

<!DOCTYPE html>
<html lang={siteLang} class="bg-[var(--page-bg)] transition text-[14px] md:text-[16px]"
	  data-overlayscrollbars-initialize
>
	<head>

		<title>{pageTitle}</title>

		<meta charset="UTF-8" />
		<meta name="description" content={description || pageTitle}>
		<meta name="author" content={profileConfig.name}>

		<meta property="og:site_name" content={siteConfig.title}>
		<meta property="og:url" content={Astro.url}>
		<meta property="og:title" content={pageTitle}>
		<meta property="og:description" content={description || pageTitle}>
		{setOGTypeArticle ? (
        <meta property="og:type" content="article" />
        ) : (
        <meta property="og:type" content="website" />
        )}

		<meta name="twitter:card" content="summary_large_image">
		<meta property="twitter:url" content={Astro.url}>
		<meta name="twitter:title" content={pageTitle}>
		<meta name="twitter:description" content={description || pageTitle}>

		<meta name="viewport" content="width=device-width" />
		<meta name="generator" content={Astro.generator} />
		{favicons.map(favicon => (
			<link rel="icon"
				  href={favicon.src.startsWith('/') ? url(favicon.src) : favicon.src}
				  sizes={favicon.sizes}
				  media={favicon.theme && `(prefers-color-scheme: ${favicon.theme})`}
			/>
		))}

		<!-- Set the theme before the page is rendered to avoid a flash -->
		<script is:inline define:vars={{DEFAULT_THEME, LIGHT_MODE, DARK_MODE, AUTO_MODE, BANNER_HEIGHT_EXTEND, PAGE_WIDTH, configHue}}>
			// Load the theme from local storage
			const theme = localStorage.getItem('theme') || DEFAULT_THEME;
			switch (theme) {
				case LIGHT_MODE:
					document.documentElement.classList.remove('dark');
					break
				case DARK_MODE:
					document.documentElement.classList.add('dark');
					break
				case AUTO_MODE:
					if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
						document.documentElement.classList.add('dark');
					} else {
						document.documentElement.classList.remove('dark');
					}
			}

			// Load the hue from local storage
			const hue = localStorage.getItem('hue') || configHue;
			document.documentElement.style.setProperty('--hue', hue);

			// calculate the --banner-height-extend, which needs to be a multiple of 4 to avoid blurry text
			let offset = Math.floor(window.innerHeight * (BANNER_HEIGHT_EXTEND / 100));
			offset = offset - offset % 4;
			document.documentElement.style.setProperty('--banner-height-extend', `${offset}px`);
		</script>
		<style define:vars={{
			configHue,
			'page-width': `${PAGE_WIDTH}rem`,
		}}></style>  <!-- defines global css variables. This will be applied to <html> <body> and some other elements idk why -->


		<slot name="head"></slot>

		<link rel="alternate" type="application/rss+xml" title={profileConfig.name} href={`${Astro.site}rss.xml`}/>

	</head>
	<body class=" min-h-screen transition " class:list={[{"lg:is-home": isHomePage, "enable-banner": enableBanner}]}
		  data-overlayscrollbars-initialize
	>
		<ConfigCarrier></ConfigCarrier>
		<slot />

		<SpeedInsights />

		<!-- increase the page height during page transition to prevent the scrolling animation from jumping -->
		<div id="page-height-extend" class="hidden h-[300vh]"></div>
	</body>
</html>

<style is:global define:vars={{
	bannerOffset,
	'banner-height-home': `${BANNER_HEIGHT_HOME}vh`,
	'banner-height': `${BANNER_HEIGHT}vh`,
}}>
@tailwind components;
@layer components {
	.enable-banner.is-home #banner-wrapper {
		@apply h-[var(--banner-height-home)] translate-y-[var(--banner-height-extend)]
	}
	.enable-banner #banner-wrapper {
		@apply h-[var(--banner-height-home)]
	}

	.enable-banner.is-home #banner {
		@apply h-[var(--banner-height-home)] translate-y-0
	}
	.enable-banner #banner {
		@apply h-[var(--banner-height-home)] translate-y-[var(--bannerOffset)]
	}
	.enable-banner.is-home #main-grid {
		@apply translate-y-[var(--banner-height-extend)];
	}
	.enable-banner #top-row {
		@apply h-[calc(var(--banner-height-home)_-_4.5rem)] transition-all duration-300
	}
	.enable-banner.is-home #sidebar-sticky {
		@apply top-[calc(1rem_-_var(--banner-height-extend))]
	}
	.navbar-hidden {
		@apply opacity-0 -translate-y-16
	}
}
</style>

<script>
import 'overlayscrollbars/overlayscrollbars.css';
import {
	OverlayScrollbars,
	// ScrollbarsHidingPlugin,
	// SizeObserverPlugin,
	// ClickScrollPlugin
} from 'overlayscrollbars';
import {getHue, getStoredTheme, setHue, setTheme} from "../utils/setting-utils";
import {pathsEqual, url} from "../utils/url-utils";
import {
	BANNER_HEIGHT,
	BANNER_HEIGHT_HOME,
	BANNER_HEIGHT_EXTEND,
	MAIN_PANEL_OVERLAPS_BANNER_HEIGHT
} from "../constants/constants";
import { siteConfig } from '../config';

/* Preload fonts */
// (async function() {
// 	try {
// 		await Promise.all([
// 			document.fonts.load("400 1em Roboto"),
// 			document.fonts.load("700 1em Roboto"),
// 		]);
// 		document.body.classList.remove("hidden");
// 	} catch (error) {
// 		console.log("Failed to load fonts:", error);
// 	}
// })();

/* TODO This is a temporary solution for style flicker issue when the transition is activated */
/* issue link: https://github.com/withastro/astro/issues/8711, the solution get from here too */
/* update: fixed in Astro 3.2.4 */
/*
function disableAnimation() {
	const css = document.createElement('style')
	css.appendChild(
		document.createTextNode(
			`*{
              -webkit-transition:none!important;
              -moz-transition:none!important;
              -o-transition:none!important;
              -ms-transition:none!important;
              transition:none!important
              }`
		)
	)
	document.head.appendChild(css)

	return () => {
		// Force restyle
		;(() => window.getComputedStyle(document.body))()

		// Wait for next tick before removing
		setTimeout(() => {
			document.head.removeChild(css)
		}, 1)
	}
}
*/

const bannerEnabled = !!document.getElementById('banner-wrapper')

function setClickOutsideToClose(panel: string, ignores: string[]) {
	document.addEventListener("click", event => {
		let panelDom = document.getElementById(panel);
		let tDom = event.target;
		if (!(tDom instanceof Node)) return;		// Ensure the event target is an HTML Node
		for (let ig of ignores) {
			let ie = document.getElementById(ig)
			if (ie == tDom || (ie?.contains(tDom))) {
				return;
			}
		}
		panelDom!.classList.add("float-panel-closed");
	});
}
setClickOutsideToClose("display-setting", ["display-setting", "display-settings-switch"])
setClickOutsideToClose("nav-menu-panel", ["nav-menu-panel", "nav-menu-switch"])
setClickOutsideToClose("search-panel", ["search-panel", "search-bar", "search-switch"])


function loadTheme() {
	const theme = getStoredTheme()
	setTheme(theme)
}

function loadHue() {
	setHue(getHue())
}

function initCustomScrollbar() {
	const bodyElement = document.querySelector('body');
	if (!bodyElement) return;
	OverlayScrollbars(
		// docs say that a initialization to the body element would affect native functionality like window.scrollTo
		// but just leave it here for now
		{
			target: bodyElement,
			cancel: {
				nativeScrollbarsOverlaid: true,    // don't initialize the overlay scrollbar if there is a native one
			}
		}, {
		scrollbars: {
			theme: 'scrollbar-base scrollbar-auto py-1',
			autoHide: 'move',
			autoHideDelay: 500,
			autoHideSuspend: false,
		},
	});

	const katexElements = document.querySelectorAll('.katex-display') as NodeListOf<HTMLElement>;

	const katexObserverOptions = {
		root: null,
		rootMargin: '100px',
		threshold: 0.1
	};

	const processKatexElement = (element: HTMLElement) => {
		if (!element.parentNode) return;
		if (element.hasAttribute('data-scrollbar-initialized')) return;

		const container = document.createElement('div');
		container.className = 'katex-display-container';
		container.setAttribute('aria-label', 'scrollable container for formulas');

		element.parentNode.insertBefore(container, element);
		container.appendChild(element);

		OverlayScrollbars(container, {
			scrollbars: {
				theme: 'scrollbar-base scrollbar-auto',
				autoHide: 'leave',
				autoHideDelay: 500,
				autoHideSuspend: false
			}
		});

		element.setAttribute('data-scrollbar-initialized', 'true');
	};

	const katexObserver = new IntersectionObserver((entries, observer) => {
		entries.forEach(entry => {
			if (entry.isIntersecting) {
			processKatexElement(entry.target as HTMLElement);
			observer.unobserve(entry.target);
			}
		});
	}, katexObserverOptions);

	katexElements.forEach(element => {
		katexObserver.observe(element);
	});
}

function showBanner() {
	if (!siteConfig.banner.enable) return;

	const banner = document.getElementById('banner');
	if (!banner) {
		console.error('Banner element not found');
		return;
	}

	banner.classList.remove('opacity-0', 'scale-105');
}

function init() {
	// disableAnimation()()		// TODO
	loadTheme();
	loadHue();
	initCustomScrollbar();
	showBanner();
}

/* Load settings when entering the site */
init();

const setup = () => {
	// TODO: temp solution to change the height of the banner
/*
	window.swup.hooks.on('animation:out:start', () => {
		const path = window.location.pathname
		const body = document.querySelector('body')
		if (path[path.length - 1] === '/' && !body.classList.contains('is-home')) {
			body.classList.add('is-home')
		} else if (path[path.length - 1] !== '/' && body.classList.contains('is-home')) {
			body.classList.remove('is-home')
		}
	})
*/
	window.swup.hooks.on('link:click', () => {
		// Remove the delay for the first time page load
		document.documentElement.style.setProperty('--content-delay', '0ms')

		// prevent elements from overlapping the navbar
		if (!bannerEnabled) {
			return
		}
		let threshold = window.innerHeight * (BANNER_HEIGHT / 100) - 72 - 16
		let navbar = document.getElementById('navbar-wrapper')
		if (!navbar || !document.body.classList.contains('lg:is-home')) {
			return
		}
		if (document.body.scrollTop >= threshold || document.documentElement.scrollTop >= threshold) {
			navbar.classList.add('navbar-hidden')
		}
	})
	window.swup.hooks.on('content:replace', initCustomScrollbar)
	window.swup.hooks.on('visit:start', (visit: {to: {url: string}}) => {
		// change banner height immediately when a link is clicked
		const bodyElement = document.querySelector('body')
		if (pathsEqual(visit.to.url, url('/'))) {
			bodyElement!.classList.add('lg:is-home');
		} else {
			bodyElement!.classList.remove('lg:is-home');
		}

		// increase the page height during page transition to prevent the scrolling animation from jumping
		const heightExtend = document.getElementById('page-height-extend')
		if (heightExtend) {
			heightExtend.classList.remove('hidden')
		}

		// Hide the TOC while scrolling back to top
		let toc = document.getElementById('toc-wrapper');
		if (toc) {
			toc.classList.add('toc-not-ready')
		}
	});
	window.swup.hooks.on('page:view', () => {
		// hide the temp high element when the transition is done
		const heightExtend = document.getElementById('page-height-extend')
		if (heightExtend) {
			heightExtend.classList.remove('hidden')
		}
	});
	window.swup.hooks.on('visit:end', (_visit: {to: {url: string}}) => {
		setTimeout(() => {
			const heightExtend = document.getElementById('page-height-extend')
			if (heightExtend) {
				heightExtend.classList.add('hidden')
			}

            // Just make the transition looks better
            const toc = document.getElementById('toc-wrapper');
            if (toc) {
                toc.classList.remove('toc-not-ready')
            }
        }, 200)
	});
}
if (window?.swup?.hooks) {
	setup()
} else {
	document.addEventListener('swup:enable', setup)
}

let backToTopBtn = document.getElementById('back-to-top-btn');
let toc = document.getElementById('toc-wrapper');
let navbar = document.getElementById('navbar-wrapper')
function scrollFunction() {
	let bannerHeight = window.innerHeight * (BANNER_HEIGHT / 100)

	if (backToTopBtn) {
		if (document.body.scrollTop > bannerHeight || document.documentElement.scrollTop > bannerHeight) {
			backToTopBtn.classList.remove('hide')
		} else {
			backToTopBtn.classList.add('hide')
		}
	}

	if (bannerEnabled && toc) {
		if (document.body.scrollTop > bannerHeight || document.documentElement.scrollTop > bannerHeight) {
			toc.classList.remove('toc-hide')
		} else {
			toc.classList.add('toc-hide')
		}
	}

	if (!bannerEnabled) return
	if (navbar) {
		const NAVBAR_HEIGHT = 72
		const MAIN_PANEL_EXCESS_HEIGHT = MAIN_PANEL_OVERLAPS_BANNER_HEIGHT * 16			// The height the main panel overlaps the banner

		let bannerHeight = BANNER_HEIGHT
		if (document.body.classList.contains('lg:is-home') && window.innerWidth >= 1024) {
			bannerHeight = BANNER_HEIGHT_HOME
		}
		let threshold = window.innerHeight * (bannerHeight / 100) - NAVBAR_HEIGHT - MAIN_PANEL_EXCESS_HEIGHT - 16
		if (document.body.scrollTop >= threshold || document.documentElement.scrollTop >= threshold) {
			navbar.classList.add('navbar-hidden')
		} else {
			navbar.classList.remove('navbar-hidden')
		}
	}
}
window.onscroll = scrollFunction

window.onresize = () => {
	// calculate the --banner-height-extend, which needs to be a multiple of 4 to avoid blurry text
	let offset = Math.floor(window.innerHeight * (BANNER_HEIGHT_EXTEND / 100));
	offset = offset - offset % 4;
	document.documentElement.style.setProperty('--banner-height-extend', `${offset}px`);
}

</script>

<script>
import PhotoSwipeLightbox from "photoswipe/lightbox"
import "photoswipe/style.css"

let lightbox: PhotoSwipeLightbox
let pswp = import("photoswipe")

function createPhotoSwipe() {
	lightbox = new PhotoSwipeLightbox({
		gallery: ".custom-md img, #post-cover img",
		pswpModule: () => pswp,
		closeSVG: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#ffffff"><path d="M480-424 284-228q-11 11-28 11t-28-11q-11-11-11-28t11-28l196-196-196-196q-11-11-11-28t11-28q11-11 28-11t28 11l196 196 196-196q11-11 28-11t28 11q11 11 11 28t-11 28L536-480l196 196q11 11 11 28t-11 28q-11 11-28 11t-28-11L480-424Z"/></svg>',
		zoomSVG: '<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#ffffff"><path d="M340-540h-40q-17 0-28.5-11.5T260-580q0-17 11.5-28.5T300-620h40v-40q0-17 11.5-28.5T380-700q17 0 28.5 11.5T420-660v40h40q17 0 28.5 11.5T500-580q0 17-11.5 28.5T460-540h-40v40q0 17-11.5 28.5T380-460q-17 0-28.5-11.5T340-500v-40Zm40 220q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l224 224q11 11 11 28t-11 28q-11 11-28 11t-28-11L532-372q-30 24-69 38t-83 14Zm0-80q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z"/></svg>',
		padding: { top: 20, bottom: 20, left: 20, right: 20 },
		wheelToZoom: true,
		arrowPrev: false,
		arrowNext: false,
		imageClickAction: 'close',
		tapAction: 'close',
		doubleTapAction: 'zoom',
	})

	lightbox.addFilter("domItemData", (itemData, element) => {
		if (element instanceof HTMLImageElement) {
			itemData.src = element.src

			itemData.w = Number(element.naturalWidth || window.innerWidth)
			itemData.h = Number(element.naturalHeight || window.innerHeight)

			itemData.msrc = element.src
		}

		return itemData
	})

	lightbox.init()
}

const setup = () => {
	if (!lightbox) {
		createPhotoSwipe()
	}
	window.swup.hooks.on("page:view", () => {
		createPhotoSwipe()
	})

	window.swup.hooks.on(
		"content:replace",
		() => {
			lightbox?.destroy?.()
		},
		{ before: true },
	)
}

if (window.swup) {
	setup()
} else {
	document.addEventListener("swup:enable", setup)
}
</script>
