"use client";
import { useCopyButton } from "fumadocs-ui/utils/use-copy-button";
import { Check, Clipboard } from "lucide-react";
import {
  type ComponentProps,
  createContext,
  type HTMLAttributes,
  type ReactNode,
  type RefObject,
  useContext,
  useMemo,
  useRef,
} from "react";
import { buttonVariants } from "@/components/ui/button";
import { cn } from "@/lib/cn";
import { mergeRefs } from "@/lib/merge-refs";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./tabs.unstyled";

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 = true,
  keepBackground = false,
  icon,
  viewportProps = {},
  children,
  Actions = (props) => (
    <div {...props} className={cn("empty:hidden", props.className)} />
  ),
  ...props
}: CodeBlockProps) {
  const inTab = useContext(TabsContext) !== null;
  const areaRef = useRef<HTMLDivElement>(null);

  return (
    <figure
      ref={ref}
      dir="ltr"
      {...props}
      className={cn(
        inTab
          ? "bg-fd-secondary -mx-px -mb-px last:rounded-b-xl"
          : "my-4 bg-fd-card rounded-xl",
        keepBackground && "bg-(--shiki-light-bg) dark:bg-(--shiki-dark-bg)",

        "shiki relative border shadow-sm outline-none not-prose overflow-hidden text-sm",
        props.className,
      )}
    >
      {title ? (
        <div className="flex text-fd-muted-foreground items-center gap-2 h-9.5 border-b px-4">
          {typeof icon === "string" ? (
            <div
              className="[&_svg]:size-3.5"
              dangerouslySetInnerHTML={{
                __html: icon,
              }}
            />
          ) : (
            icon
          )}
          <figcaption className="flex-1 truncate">{title}</figcaption>
          {Actions({
            className: "-me-2",
            children: allowCopy && <CopyButton containerRef={areaRef} />,
          })}
        </div>
      ) : (
        Actions({
          className:
            "absolute top-2 right-2 z-2 backdrop-blur-lg rounded-lg text-fd-muted-foreground",
          children: allowCopy && <CopyButton containerRef={areaRef} />,
        })
      )}
      <div
        ref={areaRef}
        {...viewportProps}
        className={cn(
          "text-[13px] py-3.5 overflow-auto max-h-[600px] fd-scroll-container",
          viewportProps.className,
        )}
        // @ts-expect-error
        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,
  containerRef,
  ...props
}: ComponentProps<"button"> & {
  containerRef: RefObject<HTMLElement | null>;
}) {
  const [checked, onClick] = useCopyButton(() => {
    const pre = containerRef.current?.getElementsByTagName("pre").item(0);
    if (!pre) return;

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

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

  return (
    <button
      type="button"
      data-checked={checked || undefined}
      className={cn(
        buttonVariants({
          className:
            "hover:text-fd-accent-foreground data-checked:text-fd-accent-foreground",
          size: "icon",
        }),
        className,
      )}
      aria-label={checked ? "Copied Text" : "Copy Text"}
      onClick={onClick}
      {...props}
    >
      {checked ? <Check /> : <Clipboard />}
    </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 rounded-xl border",
        !nested && "my-4",
        props.className,
      )}
    >
      <TabsContext.Provider
        value={useMemo(
          () => ({
            containerRef,
            nested,
          }),
          [nested],
        )}
      >
        {props.children}
      </TabsContext.Provider>
    </Tabs>
  );
}

export function CodeBlockTabsList(props: ComponentProps<typeof TabsList>) {
  return (
    <TabsList
      {...props}
      className={cn(
        "flex flex-row px-2 overflow-x-auto 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 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 `asChild` due to children is automatically wrapped in <Fragment />, maybe revisit this in future
export function CodeBlockTab(props: ComponentProps<typeof TabsContent>) {
  return <TabsContent {...props} />;
}
