import { useId, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { Link, Outlet, useNavigate } from "@tanstack/react-router";
import * as stylex from "@stylexjs/stylex";
import { Button, Disclosure, DisclosurePanel } from "react-aria-components";
import { useAuth } from "@/auth/store";
import InlineError from "@/lib/InlineError";
import { healthQuery, versionQuery } from "@/lib/queries";
import PauseControl from "@/features/pause/PauseControl";
import { diagnosticsBadge } from "./diagnosticsBadge";
import ConfigStatusNotices from "./ConfigStatusNotices";
import AuthorityLine from "@/features/configuration/AuthorityLine";
import { styles as shared } from "@/ui/styles";
import { colors } from "@/ui/tokens.stylex";

/** The one breakpoint the shell has: below it the sidebar becomes a drawer. */
const WIDE = "@media (min-width: 768px)";
const DARK = "@media (prefers-color-scheme: dark)";

/**
 * The four operational surfaces, then the configuration group. Configuration
 * is a labelled group rather than a collapsible tree: three items do not earn
 * a disclosure, and a tree would hide the authority line under it.
 */
const NAV_ITEMS = [
	{ to: "/overview", label: "Overview" },
	{ to: "/activity", label: "Activity" },
	{ to: "/clients", label: "Clients" },
	{ to: "/diagnostics", label: "Diagnostics" },
] as const;

const CONFIGURATION_ITEMS = [
	{ to: "/configuration/protection", label: "Protection" },
	{ to: "/configuration/resolution", label: "Resolution" },
	{ to: "/configuration/system", label: "System" },
] as const;

const styles = stylex.create({
	navList: {
		display: "flex",
		flexDirection: "column",
		gap: "0.25rem",
	},
	navLink: {
		display: "flex",
		alignItems: "center",
		gap: "0.5rem",
		borderRadius: "0.25rem",
		paddingInline: "0.75rem",
		paddingBlock: "0.375rem",
		textDecorationLine: "none",
	},
	navLabel: {
		flex: 1,
	},
	navGroup: {
		marginTop: "1rem",
	},
	navGroupLabel: {
		paddingInline: "0.75rem",
		paddingBlock: "0.25rem",
		fontSize: "0.75rem",
		lineHeight: "1rem",
		fontWeight: 600,
		textTransform: "uppercase",
		letterSpacing: "0.05em",
		color: colors.textMuted,
	},
	/**
	 * Neutral chrome: the mark is the message, and a coloured pill here would be
	 * the page's loudest element on every route. Text and shape carry it.
	 */
	badge: {
		minWidth: "1.25rem",
		borderRadius: "0.625rem",
		borderWidth: 1,
		borderStyle: "solid",
		borderColor: colors.borderStrong,
		backgroundColor: colors.surfaceHover,
		paddingInline: "0.375rem",
		fontSize: "0.75rem",
		lineHeight: "1.125rem",
		fontWeight: 500,
		textAlign: "center",
		color: colors.textSecondary,
	},
	/** The control sits with the footer, not in the scrolling nav list above it. */
	sidebarFooter: {
		paddingInline: "1rem",
		paddingTop: "0.75rem",
	},
	/** The current page reads as a filled chip, heavier than the hover fill. */
	navActive: {
		backgroundColor: { default: "oklch(92% 0.004 286.32)", [DARK]: "oklch(27.4% 0.006 286.033)" },
		color: colors.text,
		fontWeight: 500,
	},
	navIdle: {
		backgroundColor: { default: "transparent", ":hover": colors.surfaceHover },
		color: { default: colors.textSecondary, ":hover": colors.text },
	},
	versionFooter: {
		paddingInline: "1rem",
		paddingBlock: "0.75rem",
		fontSize: "0.75rem",
		lineHeight: "1rem",
		color: colors.textMuted,
	},
	logoutRow: {
		display: "flex",
		alignItems: "center",
		gap: "0.5rem",
	},
	/**
	 * In a 14rem column the button and its error stack, so the button spans the
	 * sidebar and matches the Pause trigger below it.
	 */
	sidebarLogout: {
		flexDirection: "column",
		alignItems: "stretch",
		gap: "0.25rem",
		paddingInline: "1rem",
		paddingTop: "0.75rem",
	},
	shell: {
		minHeight: "100dvh",
		height: { default: null, [WIDE]: "100dvh" },
		overflow: { default: null, [WIDE]: "hidden" },
		backgroundColor: colors.surface,
		color: colors.text,
		display: { default: "block", [WIDE]: "grid" },
		gridTemplateColumns: { default: null, [WIDE]: "14rem 1fr" },
	},
	sidebar: {
		display: { default: "none", [WIDE]: "flex" },
		flexDirection: { default: null, [WIDE]: "column" },
		minHeight: { default: null, [WIDE]: 0 },
		overflow: { default: null, [WIDE]: "hidden" },
		borderRightWidth: 1,
		borderRightStyle: "solid",
		borderRightColor: colors.border,
	},
	brand: {
		paddingInline: "1rem",
		paddingBlock: "1rem",
		fontSize: "1.125rem",
		lineHeight: "1.75rem",
		fontWeight: 600,
	},
	sidebarNav: {
		flex: 1,
		minHeight: { default: null, [WIDE]: 0 },
		overflowY: { default: null, [WIDE]: "auto" },
		paddingInline: "0.5rem",
	},
	column: {
		display: "flex",
		minHeight: { default: "100dvh", [WIDE]: 0 },
		minWidth: { default: null, [WIDE]: 0 },
		flexDirection: "column",
	},
	/** Narrow only: on WIDE the sidebar carries everything this row held. */
	header: {
		display: { default: "flex", [WIDE]: "none" },
		alignItems: "center",
		gap: "0.75rem",
		borderBottomWidth: 1,
		borderBottomStyle: "solid",
		borderBottomColor: colors.border,
		paddingInline: "1rem",
		paddingBlock: "0.5rem",
	},
	narrowOnly: {
		display: { default: null, [WIDE]: "none" },
	},
	narrowBrand: {
		fontSize: "1.125rem",
		lineHeight: "1.75rem",
		fontWeight: 600,
		display: { default: null, [WIDE]: "none" },
	},
	headerRight: {
		marginLeft: "auto",
		display: "flex",
		alignItems: "center",
		gap: "0.75rem",
	},
	drawer: {
		borderBottomWidth: 1,
		borderBottomStyle: "solid",
		borderBottomColor: colors.border,
		display: { default: null, [WIDE]: "none" },
	},
	drawerNav: {
		paddingInline: "0.5rem",
		paddingBlock: "0.5rem",
	},
	main: {
		flex: 1,
		minHeight: { default: null, [WIDE]: 0 },
		minWidth: { default: null, [WIDE]: 0 },
		overflowY: { default: null, [WIDE]: "auto" },
		padding: "1rem",
	},
});

function NavItem({
	to,
	label,
	onNavigate,
	badge,
}: {
	to: string;
	label: string;
	onNavigate?: () => void;
	badge?: { text: string; label: string } | null;
}) {
	return (
		<li>
			<Link
				to={to}
				onClick={onNavigate}
				activeProps={{
					"aria-current": "page",
					className: stylex.props(styles.navActive).className,
				}}
				inactiveProps={{ className: stylex.props(styles.navIdle).className }}
				{...stylex.props(styles.navLink, shared.focusRing)}
			>
				<span {...stylex.props(styles.navLabel)}>{label}</span>
				{badge !== undefined && badge !== null && (
					<span aria-label={badge.label} {...stylex.props(styles.badge)}>
						{badge.text}
					</span>
				)}
			</Link>
		</li>
	);
}

function NavLinks({ onNavigate }: { onNavigate?: () => void }) {
	const health = useQuery(healthQuery());
	const badge = diagnosticsBadge(health.data, health.isError);
	const groupHeadingId = useId();
	return (
		<>
			<ul {...stylex.props(styles.navList)}>
				{NAV_ITEMS.map((item) => (
					<NavItem
						key={item.to}
						to={item.to}
						label={item.label}
						onNavigate={onNavigate}
						badge={item.to === "/diagnostics" ? badge : undefined}
					/>
				))}
			</ul>
			<div {...stylex.props(styles.navGroup)}>
				{/* A span, not a heading: the sidebar label is not a section of the
				    page, and an h2 here lands in the middle of the page's own outline. */}
				<span id={groupHeadingId} {...stylex.props(styles.navGroupLabel)}>
					Configuration
				</span>
				<ul aria-labelledby={groupHeadingId} {...stylex.props(styles.navList)}>
					{CONFIGURATION_ITEMS.map((item) => (
						<NavItem key={item.to} to={item.to} label={item.label} onNavigate={onNavigate} />
					))}
				</ul>
				<AuthorityLine />
			</div>
		</>
	);
}

/**
 * The sidebar's foot, in both renderings. Pause is a runtime action on the whole
 * resolver rather than on the page in front of the reader, which is why it sits
 * with the version label instead of in the header of every route.
 */
function SidebarFooter() {
	return (
		<>
			<div {...stylex.props(styles.sidebarFooter)}>
				<PauseControl />
			</div>
			<VersionFooter />
		</>
	);
}

function VersionFooter() {
	const { data } = useQuery(versionQuery());
	return (
		<footer {...stylex.props(styles.versionFooter)}>
			{data === undefined ? "nxdns" : `nxdns v${data.version} (${data.git_commit.slice(0, 7)})`}
		</footer>
	);
}

function LogoutButton({ style }: { style?: stylex.StyleXStyles }) {
	const { authRequired, logout } = useAuth();
	const navigate = useNavigate();
	const [error, setError] = useState<unknown>(null);
	if (authRequired !== true) return null;
	return (
		<div {...stylex.props(styles.logoutRow, style)}>
			<button
				type="button"
				onClick={() => {
					setError(null);
					void logout().then(
						() => navigate({ to: "/login" }),
						(logoutError: unknown) => setError(logoutError),
					);
				}}
				{...stylex.props(shared.button, shared.focusRing)}
			>
				Log out
			</button>
			{error !== null && <InlineError error={error} />}
		</div>
	);
}

export default function AppShell() {
	const [drawerOpen, setDrawerOpen] = useState(false);
	return (
		<div {...stylex.props(styles.shell)}>
			<aside {...stylex.props(styles.sidebar)}>
				<div {...stylex.props(styles.brand)}>nxdns</div>
				<nav aria-label="Main" {...stylex.props(styles.sidebarNav)}>
					<NavLinks />
				</nav>
				<LogoutButton style={styles.sidebarLogout} />
				<SidebarFooter />
			</aside>
			{/* The column itself is the disclosure: the trigger sits in the header
			    and the panel opens below the notices, so any wrapper around only
			    the two would have to cut across the column's own flex children. */}
			<Disclosure isExpanded={drawerOpen} onExpandedChange={setDrawerOpen} {...stylex.props(styles.column)}>
				<header {...stylex.props(styles.header)}>
					<Button
						slot="trigger"
						className={() =>
							stylex.props(shared.button, styles.narrowOnly, shared.focusRing).className ?? ""
						}
					>
						Menu
					</Button>
					<span {...stylex.props(styles.narrowBrand)}>nxdns</span>
					<div {...stylex.props(styles.headerRight)}>
						<LogoutButton />
					</div>
				</header>
				<ConfigStatusNotices />
				<DisclosurePanel id="mobile-nav" {...stylex.props(styles.drawer)}>
					{/* React Aria only hides a collapsed panel; the shell drops it
					    instead, so a closed drawer keeps no second health poll and
					    no second Pause control alive behind the header. */}
					{drawerOpen && (
						<>
							<nav aria-label="Main" {...stylex.props(styles.drawerNav)}>
								<NavLinks onNavigate={() => setDrawerOpen(false)} />
							</nav>
							<SidebarFooter />
						</>
					)}
				</DisclosurePanel>
				<main id="main-content" data-scroll-restoration-id="main" {...stylex.props(styles.main)}>
					<Outlet />
				</main>
			</Disclosure>
		</div>
	);
}
