import { useClickOutside, useHotkeys } from "@mantine/hooks";
import { IconChevronDown, IconChevronUp } from "@tabler/icons-react";
import { transformObject, clampNumber } from "bknd/utils";
import { throttle } from "lodash-es";
import { ScrollArea } from "radix-ui";
import {
   type ComponentProps,
   type ComponentPropsWithoutRef,
   useEffect,
   useRef,
   useState,
} from "react";
import type { IconType } from "react-icons";
import { twMerge } from "tailwind-merge";
import { IconButton } from "ui/components/buttons/IconButton";
import { useRoutePathState } from "ui/hooks/use-route-path-state";
import { AppShellProvider, useAppShell } from "ui/layouts/AppShell/use-appshell";
import { appShellStore } from "ui/store";
import { useLocation } from "wouter";

export function Root({ children }: { children: React.ReactNode }) {
   const sidebarWidths = appShellStore((store) => store.sidebars);
   const style = transformObject(sidebarWidths, (value) => value.width);
   return (
      <AppShellProvider>
         <div
            id="app-shell"
            data-shell="root"
            className="flex flex-1 flex-col select-none h-dvh"
            style={Object.fromEntries(
               Object.entries(style).map(([key, value]) => [
                  `--sidebar-width-${key}`,
                  `${value}px`,
               ]),
            )}
         >
            {children}
         </div>
      </AppShellProvider>
   );
}

type NavLinkProps<E extends React.ElementType> = {
   Icon?: IconType;
   children: React.ReactNode;
   className?: string;
   to?: string; // @todo: workaround
   as?: E;
   disabled?: boolean;
};

export const NavLink = <E extends React.ElementType = "a">({
   children,
   as,
   className,
   Icon,
   disabled,
   ...otherProps
}: NavLinkProps<E> & Omit<React.ComponentProps<E>, keyof NavLinkProps<E>>) => {
   const Tag = as || "a";

   return (
      <Tag
         {...otherProps}
         className={twMerge(
            "px-6 py-2 [&.active]:bg-muted [&.active]:hover:bg-primary/15 hover:bg-primary/5 flex flex-row items-center rounded-full gap-2.5 link transition-colors",
            disabled && "opacity-50 cursor-not-allowed",
            className,
         )}
      >
         {Icon && <Icon size={18} />}
         {typeof children === "string" ? <span className="text-lg">{children}</span> : children}
      </Tag>
   );
};

export function Content({ children, center }: { children: React.ReactNode; center?: boolean }) {
   return (
      <main
         data-shell="content"
         className={twMerge(
            "flex flex-1 flex-row max-w-screen h-full",
            center && "justify-center items-center",
         )}
      >
         {children}
      </main>
   );
}

export function Main({ children }) {
   const { sidebar } = useAppShell();
   return (
      <div
         data-shell="main"
         className={twMerge(
            "flex flex-col flex-grow w-1 flex-shrink-1",
            sidebar.open && "md:max-w-[calc(100%-var(--sidebar-width))]",
         )}
      >
         {children}
      </div>
   );
}

export function Sidebar({
   children,
   name = "default",
   handle = "right",
   minWidth,
   maxWidth,
}: {
   children: React.ReactNode;
   name?: string;
   handle?: "right" | "left";
   minWidth?: number;
   maxWidth?: number;
}) {
   const open = appShellStore((store) => store.sidebars[name]?.open);
   const close = appShellStore((store) => store.closeSidebar(name));
   const width = appShellStore((store) => store.sidebars[name]?.width ?? 350);
   const ref = useClickOutside(close, ["mouseup", "touchend"]); //, [document.getElementById("header")]);
   const sidebarRef = useRef<HTMLDivElement>(null!);
   const [location] = useLocation();

   const closeHandler = () => {
      open && close();
   };

   // listen for window location change
   useEffect(closeHandler, [location]);

   // @todo: potentially has to be added to the root, as modals could be opened
   useHotkeys([["Escape", closeHandler]]);

   return (
      <>
         {handle === "left" && (
            <SidebarResize
               name={name}
               handle={handle}
               sidebarRef={sidebarRef}
               minWidth={minWidth}
               maxWidth={maxWidth}
            />
         )}
         <aside
            data-shell="sidebar"
            ref={sidebarRef}
            className="hidden md:flex flex-col flex-shrink-0 flex-grow-0 h-full bg-muted/10"
            style={{ width }}
         >
            {children}
         </aside>
         {handle === "right" && (
            <SidebarResize
               name={name}
               handle={handle}
               sidebarRef={sidebarRef}
               minWidth={minWidth}
               maxWidth={maxWidth}
            />
         )}
         <div
            data-open={open}
            className="absolute w-full md:hidden data-[open=true]:translate-x-0 translate-x-[-100%] transition-transform z-10 backdrop-blur-sm max-w-[90%]"
         >
            <aside
               ref={ref}
               data-shell="sidebar"
               className="flex-col w-[var(--sidebar-width)] flex-shrink-0 flex-grow-0 h-full border-muted border-r bg-background"
            >
               <MaxHeightContainer className="overflow-y-scroll md:overflow-y-hidden">
                  {children}
               </MaxHeightContainer>
            </aside>
         </div>
      </>
   );
}

const SidebarResize = ({
   name = "default",
   handle = "right",
   sidebarRef,
   minWidth = 250,
   maxWidth = window.innerWidth * 0.5,
}: {
   name?: string;
   handle?: "right" | "left";
   sidebarRef: React.RefObject<HTMLDivElement>;
   minWidth?: number;
   maxWidth?: number;
}) => {
   const setSidebarWidth = appShellStore((store) => store.setSidebarWidth(name));
   const [isResizing, setIsResizing] = useState(false);
   const [start, setStart] = useState(0);
   const [startWidth, setStartWidth] = useState(sidebarRef.current?.offsetWidth ?? 0);

   const handleMouseDown = (e: React.MouseEvent) => {
      e.preventDefault();
      setIsResizing(true);
      setStart(e.clientX);
      setStartWidth(sidebarRef.current?.offsetWidth ?? 0);
   };

   const handleMouseMove = (e: MouseEvent) => {
      if (!isResizing) return;

      const diff = handle === "right" ? e.clientX - start : start - e.clientX;
      const newWidth = clampNumber(startWidth + diff, minWidth, maxWidth);
      setSidebarWidth(newWidth);
   };

   const handleMouseUp = () => {
      setIsResizing(false);
   };

   useEffect(() => {
      if (isResizing) {
         window.addEventListener("mousemove", handleMouseMove);
         window.addEventListener("mouseup", handleMouseUp);
      }

      return () => {
         window.removeEventListener("mousemove", handleMouseMove);
         window.removeEventListener("mouseup", handleMouseUp);
      };
   }, [isResizing, start, startWidth, minWidth, maxWidth]);

   return (
      <div
         data-shell="sidebar-resize"
         data-active={isResizing ? 1 : undefined}
         className="w-px h-full hidden md:flex bg-muted after:transition-colors relative after:absolute after:inset-0 after:-left-px after:w-[2px] select-none data-[active]:after:bg-sky-400 data-[active]:cursor-col-resize hover:after:bg-sky-400 hover:cursor-col-resize after:z-2"
         onMouseDown={handleMouseDown}
         style={{ touchAction: "none" }}
      />
   );
};

export function SectionHeaderTitle({ children, className, ...props }: ComponentProps<"h2">) {
   return (
      <h2
         {...props}
         className={twMerge("text-lg dark:font-bold font-semibold select-text", className)}
      >
         {children}
      </h2>
   );
}

export function SectionHeader({ children, right, className, scrollable, sticky }: any = {}) {
   return (
      <div
         className={twMerge(
            "flex flex-row h-14 flex-shrink-0 py-2 pl-5 pr-3 border-muted border-b items-center justify-between bg-muted/10",
            sticky && "sticky top-0 bottom-10 z-10",
            className,
         )}
      >
         <div
            className={twMerge(
               "",
               scrollable && "overflow-x-scroll overflow-y-visible app-scrollbar",
            )}
         >
            {typeof children === "string" ? (
               <SectionHeaderTitle>{children}</SectionHeaderTitle>
            ) : (
               children
            )}
         </div>
         {right && !scrollable && <div className="flex flex-row gap-2.5">{right}</div>}
         {right && scrollable && (
            <div className="flex flex-row sticky z-10 right-0 h-full">
               <div className="h-full w-5 bg-gradient-to-l from-background" />
               <div className="flex flex-row gap-2.5 bg-background">{right}</div>
            </div>
         )}
      </div>
   );
}

type SidebarLinkProps<E extends React.ElementType> = {
   children: React.ReactNode;
   as?: E;
   to?: string; // @todo: workaround
   params?: Record<string, string>; // @todo: workaround
   disabled?: boolean;
};

export const SidebarLink = <E extends React.ElementType = "a">({
   children,
   as,
   className,
   disabled = false,
   ...otherProps
}: SidebarLinkProps<E> & Omit<React.ComponentProps<E>, keyof SidebarLinkProps<E>>) => {
   const Tag = as || "a";

   return (
      <Tag
         {...otherProps}
         className={twMerge(
            "flex flex-row px-4 items-center gap-2 h-12",
            !disabled &&
               "cursor-pointer rounded-md [&.active]:bg-primary/10 [&.active]:hover:bg-primary/15 [&.active]:font-medium hover:bg-primary/5 focus:bg-primary/5 link",
            disabled && "opacity-50 cursor-not-allowed pointer-events-none",
            className,
         )}
      >
         {children}
      </Tag>
   );
};

type SectionHeaderLinkProps<E extends React.ElementType> = {
   children: React.ReactNode;
   as?: E;
   to?: string; // @todo: workaround
   disabled?: boolean;
   active?: boolean;
   badge?: string | number;
};

export const SectionHeaderLink = <E extends React.ElementType = "a">({
   children,
   as,
   className,
   disabled = false,
   active = false,
   badge,
   ...props
}: SectionHeaderLinkProps<E> & Omit<React.ComponentProps<E>, keyof SectionHeaderLinkProps<E>>) => {
   const Tag = as || "a";

   return (
      <Tag
         {...props}
         className={twMerge(
            "hover:bg-primary/5 flex flex-row items-center justify-center gap-2.5 px-5 h-12 leading-none font-medium text-primary/80 rounded-tr-lg rounded-tl-lg cursor-pointer border border-transparent border-b-0",
            active
               ? "bg-background hover:bg-background text-primary border-muted border-b-0"
               : "link opacity-80",
            badge && "pr-4",
            className,
         )}
      >
         <span className="truncate">{children}</span>
         {badge ? (
            <span className="px-3 py-1 rounded-full font-mono bg-primary/5 text-sm leading-none">
               {badge}
            </span>
         ) : null}
      </Tag>
   );
};

export type SectionHeaderTabsProps = {
   title?: string;
   items?: (Omit<SectionHeaderLinkProps<any>, "children"> & {
      label: string;
   })[];
};
export const SectionHeaderTabs = ({ title, items }: SectionHeaderTabsProps) => {
   return (
      <div className="relative">
         <div className="absolute left-0 right-0 bottom-0 z-0 h-px bg-muted" />
         <div className="overflow-x-scroll app-scrollbar mt-10 border-t border-t-muted">
            <SectionHeader className="pl-3 pb-0 items-end ">
               <div className="flex flex-row items-center gap-6 -mb-px">
                  {title && (
                     <SectionHeaderTitle className="pl-2 hidden md:block">
                        {title}
                     </SectionHeaderTitle>
                  )}
                  <div className="flex flex-row items-center gap-3 pr-3">
                     {items?.map(({ label, ...item }, key) => (
                        <SectionHeaderLink key={key} {...item} className="relative z-2">
                           {label}
                        </SectionHeaderLink>
                     ))}
                  </div>
               </div>
            </SectionHeader>
         </div>
      </div>
   );
};

export function MaxHeightContainer(props: ComponentPropsWithoutRef<"div">) {
   const scrollRef = useRef<React.ElementRef<"div">>(null);
   const [offset, setOffset] = useState(0);
   const [height, setHeight] = useState(window.innerHeight);

   function updateHeaderHeight() {
      if (scrollRef.current) {
         // get offset to top of window
         const offset = scrollRef.current.getBoundingClientRect().top;
         const height = window.innerHeight;
         setOffset(offset);
         setHeight(height);
      }
   }

   useEffect(() => {
      updateHeaderHeight();
      const resize = throttle(updateHeaderHeight, 500);

      window.addEventListener("resize", resize);

      return () => {
         window.removeEventListener("resize", resize);
      };
   }, []);

   return (
      <div ref={scrollRef} style={{ height: `${height - offset}px` }} {...props}>
         {props.children}
      </div>
   );
}

export function Scrollable({
   children,
   initialOffset = 64,
}: {
   children: React.ReactNode;
   initialOffset?: number;
}) {
   const scrollRef = useRef<React.ElementRef<"div">>(null);
   const [offset, setOffset] = useState(initialOffset);

   function updateHeaderHeight() {
      if (scrollRef.current) {
         // get offset to top of window
         const offset = scrollRef.current.getBoundingClientRect().top;
         setOffset(offset);
      }
   }

   useEffect(updateHeaderHeight, []);

   if (typeof window !== "undefined") {
      window.addEventListener("resize", throttle(updateHeaderHeight, 500));
   }

   return (
      <ScrollArea.Root style={{ height: `calc(100dvh - ${offset}px` }} ref={scrollRef}>
         <ScrollArea.Viewport className="w-full h-full">{children}</ScrollArea.Viewport>
         <ScrollArea.Scrollbar
            forceMount
            className="flex select-none touch-none bg-transparent w-0.5"
            orientation="vertical"
         >
            <ScrollArea.Thumb className="flex-1 bg-primary/50" />
         </ScrollArea.Scrollbar>
         <ScrollArea.Scrollbar
            forceMount
            className="flex select-none touch-none bg-muted flex-col h-0.5"
            orientation="horizontal"
         >
            <ScrollArea.Thumb className="flex-1 bg-primary/50 " />
         </ScrollArea.Scrollbar>
      </ScrollArea.Root>
   );
}

type SectionHeaderAccordionItemProps = {
   title: string;
   open: boolean;
   toggle: () => void;
   ActiveIcon?: any;
   children?: React.ReactNode;
   renderHeaderRight?: (props: { open: boolean }) => React.ReactNode;
   scrollContainerRef?: React.RefObject<HTMLDivElement>;
};

export const SectionHeaderAccordionItem = ({
   title,
   open,
   toggle,
   ActiveIcon = IconChevronUp,
   children,
   renderHeaderRight,
   scrollContainerRef,
}: SectionHeaderAccordionItemProps) => (
   <div
      style={{ minHeight: 49 }}
      className={twMerge(
         "flex flex-col flex-animate overflow-hidden",
         open
            ? "flex-open border-b border-b-muted"
            : "flex-initial cursor-pointer hover:bg-primary/5",
      )}
   >
      {/** biome-ignore lint/a11y/noStaticElementInteractions: . */}
      {/** biome-ignore lint/a11y/useKeyWithClickEvents: . */}
      <div
         className={twMerge(
            "flex flex-row bg-muted/10 border-muted border-b h-14 py-4 pr-4 pl-2 items-center gap-2",
         )}
         onClick={toggle}
      >
         <IconButton Icon={open ? ActiveIcon : IconChevronDown} disabled={open} />
         <h2 className="text-lg dark:font-bold font-semibold select-text">{title}</h2>
         <div className="flex grow" />
         {renderHeaderRight?.({ open })}
      </div>
      <div
         ref={scrollContainerRef}
         className={twMerge("overflow-y-scroll transition-all", open ? " grow" : "h-0 opacity-0")}
      >
         {children}
      </div>
   </div>
);

export const RouteAwareSectionHeaderAccordionItem = ({
   routePattern,
   identifier,
   renderHeaderRight,
   ...props
}: Omit<SectionHeaderAccordionItemProps, "open" | "toggle" | "renderHeaderRight"> & {
   renderHeaderRight?: (props: { open: boolean; active: boolean }) => React.ReactNode;
   // it's optional because it could be provided using the context
   routePattern?: string;
   identifier: string;
}) => {
   const { active, toggle } = useRoutePathState(routePattern, identifier);
   return (
      <SectionHeaderAccordionItem
         {...props}
         open={active}
         toggle={toggle}
         renderHeaderRight={
            renderHeaderRight && ((props) => renderHeaderRight?.({ open: props.open, active }))
         }
      />
   );
};

export const Separator = ({ className, ...props }: ComponentPropsWithoutRef<"hr">) => (
   <hr {...props} className={twMerge("border-muted my-3", className)} />
);

export { Header } from "./Header";
