"use client";
import { Check, Copy } from "lucide-react";
import {
	ButtonHTMLAttributes,
	type ComponentProps,
	createContext,
	forwardRef,
	type HTMLAttributes,
	ReactElement,
	type ReactNode,
	type RefObject,
	useCallback,
	useContext,
	useMemo,
	useRef,
} from "react";
import { cn } from "@/lib/utils";
import { useCopyButton } from "./use-copy-button";
import { buttonVariants } from "@/components/ui/button";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { mergeRefs } from "@/lib/utils";
import { ScrollArea, ScrollBar, ScrollViewport } from "./scroll-area";

export interface CodeBlockProps extends ComponentProps<"figure"> {
	/**
	 * Icon of code block
	 *
	 * When passed as a string, it assumes the value is the HTML of icon
	 */
	icon?: ReactNode;

	/**
	 * Allow to copy code with copy button
	 *
	 * @defaultValue true
	 */
	allowCopy?: boolean;

	/**
	 * Keep original background color generated by Shiki or Rehype Code
	 *
	 * @defaultValue false
	 */
	keepBackground?: boolean;

	viewportProps?: HTMLAttributes<HTMLElement>;

	/**
	 * show line numbers
	 */
	"data-line-numbers"?: boolean;

	/**
	 * @defaultValue 1
	 */
	"data-line-numbers-start"?: number;

	Actions?: (props: { className?: string; children?: ReactNode }) => ReactNode;
}

const TabsContext = createContext<{
	containerRef: RefObject<HTMLDivElement | null>;
	nested: boolean;
} | null>(null);

export function Pre(props: ComponentProps<"pre">) {
	return (
		<pre
			{...props}
			className={cn("min-w-full w-max *:flex *:flex-col", props.className)}
		>
			{props.children}
		</pre>
	);
}

export function CodeBlock({
	ref,
	title,
	allowCopy,
	keepBackground = false,
	icon,
	viewportProps = {},
	children,
	Actions = (props) => (
		<div {...props} className={cn("empty:hidden", props.className)} />
	),
	...props
}: CodeBlockProps) {
	const isTab = useContext(TabsContext) !== null;
	const areaRef = useRef<HTMLDivElement>(null);
	allowCopy ??= !isTab;
	const bg = cn(
		"bg-fd-secondary",
		keepBackground && "bg-(--shiki-light-bg) dark:bg-(--shiki-dark-bg)",
	);
	const onCopy = useCallback(() => {
		const pre = areaRef.current?.getElementsByTagName("pre").item(0);
		if (!pre) return;
		const clone = pre.cloneNode(true) as HTMLElement;
		clone.querySelectorAll(".nd-copy-ignore").forEach((node) => {
			node.remove();
		});
		void navigator.clipboard.writeText(clone.textContent ?? "");
	}, []);
	return (
		<figure
			ref={ref}
			dir="ltr"
			{...props}
			className={cn(
				isTab ? [bg, "rounded-lg"] : "my-4 rounded-lg bg-fd-card",
				"group shiki relative border shadow-sm outline-none not-prose overflow-hidden text-sm",
				props.className,
			)}
		>
			{title ? (
				<div
					className={cn(
						"group flex text-fd-muted-foreground items-center gap-2 ps-3 h-9.5 pr-1 bg-fd-muted",
						isTab && "border-b",
					)}
				>
					{typeof icon === "string" ? (
						<div
							className="[&_svg]:size-3.5"
							dangerouslySetInnerHTML={{
								__html: icon,
							}}
						/>
					) : (
						icon
					)}
					<figcaption className="flex-1 truncate">{title}</figcaption>
					{Actions({
						children: allowCopy && <CopyButton onCopy={onCopy} />,
					})}
				</div>
			) : (
				Actions({
					className: "absolute top-1 right-1 z-2 text-fd-muted-foreground",
					children: allowCopy && <CopyButton onCopy={onCopy} />,
				})
			)}
			<div
				ref={areaRef}
				{...viewportProps}
				className={cn(
					!isTab && [bg, "rounded-none border border-x-0 border-b-0"],
					"text-[13px] overflow-auto max-h-[600px] bg-fd-muted/50 fd-scroll-container",
					viewportProps.className,
				)}
				style={
					{
						// space for toolbar
						"--padding-right": !title ? "calc(var(--spacing) * 8)" : undefined,
						counterSet: props["data-line-numbers"]
							? `line ${Number(props["data-line-numbers-start"] ?? 1) - 1}`
							: undefined,
						...viewportProps.style,
					} as object
				}
			>
				{children}
			</div>
		</figure>
	);
}
function CopyButton({
	className,
	onCopy,
	...props
}: ButtonHTMLAttributes<HTMLButtonElement> & {
	onCopy: () => void;
}): ReactElement {
	const [checked, onClick] = useCopyButton(onCopy);

	return (
		<button
			type="button"
			className={cn(
				buttonVariants({
					variant: "ghost",
					size: "icon",
				}),
				"transition-opacity border-none  group-hover:opacity-100",
				"opacity-0 group-hover:opacity-100",
				"group-hover:opacity-100",
				className,
			)}
			aria-label="Copy Text"
			onClick={onClick}
			{...props}
		>
			<Check
				className={cn("size-3.5 transition-transform", !checked && "scale-0")}
			/>
			<Copy
				className={cn(
					"absolute size-3.5 transition-transform",
					checked && "scale-0",
				)}
			/>
		</button>
	);
}
export function CodeBlockTabs({ ref, ...props }: ComponentProps<typeof Tabs>) {
	const containerRef = useRef<HTMLDivElement>(null);
	const nested = useContext(TabsContext) !== null;

	return (
		<Tabs
			ref={mergeRefs(containerRef, ref)}
			{...props}
			className={cn(
				"bg-fd-card p-1 rounded-xl border overflow-hidden",
				!nested && "my-4",
				props.className,
			)}
		>
			<TabsContext.Provider
				value={useMemo(
					() => ({
						containerRef,
						nested,
					}),
					[nested],
				)}
			>
				{props.children}
			</TabsContext.Provider>
		</Tabs>
	);
}
export function CodeBlockTabsList(props: ComponentProps<typeof TabsList>) {
	const { containerRef, nested } = useContext(TabsContext)!;

	return (
		<TabsList
			{...props}
			className={cn(
				"flex flex-row overflow-x-auto px-1 -mx-1 text-fd-muted-foreground",
				props.className,
			)}
		>
			{props.children}
		</TabsList>
	);
}

export function CodeBlockTabsTrigger({
	children,
	...props
}: ComponentProps<typeof TabsTrigger>) {
	return (
		<TabsTrigger
			{...props}
			className={cn(
				"relative group inline-flex text-sm font-medium text-nowrap items-center transition-colors gap-2 px-2 first:ms-1 py-1.5 hover:text-fd-accent-foreground data-[state=active]:text-fd-primary [&_svg]:size-3.5",
				props.className,
			)}
		>
			<div className="absolute inset-x-2 bottom-0 h-px group-data-[state=active]:bg-fd-primary" />
			{children}
		</TabsTrigger>
	);
}

// TODO: currently Vite RSC plugin has problem with adding `asChild` here, maybe revisit this in future
export const CodeBlockTab = TabsContent;

export const CodeBlockOld = forwardRef<HTMLElement, CodeBlockProps>(
	(
		{
			title,
			allowCopy = true,
			keepBackground = false,
			icon,
			viewportProps,
			...props
		},
		ref,
	) => {
		const areaRef = useRef<HTMLDivElement>(null);
		const onCopy = useCallback(() => {
			const pre = areaRef.current?.getElementsByTagName("pre").item(0);

			if (!pre) return;

			const clone = pre.cloneNode(true) as HTMLElement;
			clone.querySelectorAll(".nd-copy-ignore").forEach((node) => {
				node.remove();
			});

			void navigator.clipboard.writeText(clone.textContent ?? "");
		}, []);

		return (
			<figure
				ref={ref}
				{...props}
				className={cn(
					"not-prose group fd-codeblock relative my-6 overflow-hidden rounded-lg border bg-fd-secondary/50 text-sm",
					keepBackground &&
						"bg-[var(--shiki-light-bg)] dark:bg-[var(--shiki-dark-bg)]",
					props.className,
				)}
			>
				{title ? (
					<div className="flex flex-row items-center gap-2 border-b bg-fd-muted px-4 py-1.5">
						{icon ? (
							<div
								className="text-fd-muted-foreground [&_svg]:size-3.5"
								dangerouslySetInnerHTML={
									typeof icon === "string"
										? {
												__html: icon,
											}
										: undefined
								}
							>
								{typeof icon !== "string" ? icon : null}
							</div>
						) : null}
						<figcaption className="flex-1 truncate text-fd-muted-foreground">
							{title}
						</figcaption>
						{allowCopy ? (
							<CopyButton className="-me-2" onCopy={onCopy} />
						) : null}
					</div>
				) : (
					allowCopy && (
						<CopyButton
							className="absolute right-2 top-2 z-[2] backdrop-blur-md"
							onCopy={onCopy}
						/>
					)
				)}
				<ScrollArea ref={areaRef} dir="ltr">
					<ScrollViewport
						{...viewportProps}
						className={cn("max-h-[600px]", viewportProps?.className)}
					>
						{props.children}
					</ScrollViewport>
					<ScrollBar orientation="horizontal" />
				</ScrollArea>
			</figure>
		);
	},
);

CodeBlockOld.displayName = "CodeBlockOld";
