import React, { useEffect, useRef, useState } from "react";
import clsx from "clsx";
import DOMPurify from "dompurify";
import { PreservationSkeleton } from "../Skeletons";
import { useTranslation } from "next-i18next";
import { useRouter } from "next/router";
import Link from "next/link";
import unescapeString from "@/lib/client/unescapeString";
import isValidUrl from "@/lib/shared/isValidUrl";
import LinkDate from "../LinkViews/LinkComponents/LinkDate";
import usePermissions from "@/hooks/usePermissions";
import {
  LinkIncludingShortenedCollectionAndTags,
  ArchivedFormat,
} from "@linkwarden/types/global";
import {
  readerViewCSS,
  READER_VIEW_DEFAULTS,
} from "@linkwarden/lib/readerViewStyles";
import ClickAwayHandler from "@/components/ClickAwayHandler";
import {
  useGetLinkHighlights,
  usePostHighlight,
  useRemoveHighlight,
} from "@linkwarden/router/highlights";
import { Highlight } from "@linkwarden/prisma/client";
import { useUser } from "@linkwarden/router/user";
import { Caveat } from "next/font/google";
import { Bentham } from "next/font/google";
import { Separator } from "../ui/separator";
import { Button } from "../ui/button";

const caveat = Caveat({ subsets: ["latin"] });
const bentham = Bentham({ subsets: ["latin"], weight: "400" });

type Props = {
  link: LinkIncludingShortenedCollectionAndTags;
};

export default function ReadableView({ link }: Props) {
  const { t } = useTranslation();
  const router = useRouter();
  const isPublicRoute = router.pathname.startsWith("/public");
  const permissions = usePermissions(link?.collection?.id as number);

  const postHighlight = usePostHighlight(link?.id as number);
  const { data: linkHighlights } = useGetLinkHighlights(link?.id as number);
  const deleteHighlight = useRemoveHighlight(link?.id as number);

  const [isCommenting, setIsCommenting] = useState(false);

  const [selectionInfo, setSelectionInfo] = useState<{
    highlightId?: number | null;
    linkId: number;
    text: string;
    startOffset: number;
    endOffset: number;
    color: "yellow" | "red" | "blue" | "green";
    comment?: string;
  } | null>(null);
  const [linkContent, setLinkContent] = useState("");
  const [menuOpen, setMenuOpen] = useState(false);
  const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 });

  useEffect(() => {
    async function fetchLinkContent() {
      if (link?.readable?.startsWith("archives")) {
        const response = await fetch(
          `/api/v1/archives/${link?.id}?format=${ArchivedFormat.readability}&_=${link.updatedAt}`
        );
        const data = await response.json();

        setLinkContent(DOMPurify.sanitize(data?.content ?? ""));
      }
    }
    fetchLinkContent();
  }, [link]);

  const { data: user } = useUser();

  useEffect(() => {
    if (selectionInfo?.highlightId) {
      const comment = linkHighlights?.find(
        (h) => h.id === selectionInfo.highlightId
      )?.comment;

      if (selectionInfo) {
        setSelectionInfo({ ...selectionInfo, comment: comment || "" });
      }
    }
  }, [selectionInfo?.highlightId, linkHighlights, isCommenting]);

  const handleMouseUp = (e: React.MouseEvent) => {
    const containerEl = containerRef.current;
    if (!containerEl) return;

    const containerRect = containerEl.getBoundingClientRect();
    const target = e.target as HTMLElement;
    const highlightId = Number(target.dataset.highlightId);
    const selection = window.getSelection();

    const color = linkHighlights?.find((e) => e.id == highlightId)?.color;

    const info = getHighlightedSection();

    if (!menuOpen)
      setSelectionInfo({
        highlightId: highlightId || null,
        linkId: link.id as number,
        text: info?.text || "",
        startOffset: info?.startOffset || -1,
        endOffset: info?.endOffset || -1,
        color: color as any,
      });

    if (highlightId) {
      const rect = target.getBoundingClientRect();
      const relativeX = rect.left - containerRect.left + rect.width / 2;
      const relativeY = rect.top - containerRect.top - 5;

      setMenuOpen(true);
      setMenuPosition({
        x: relativeX,
        y: relativeY,
      });

      return;
    }

    if (selection && selection.rangeCount > 0 && !selection.isCollapsed) {
      const range = selection.getRangeAt(0);
      const rect = range.getBoundingClientRect();

      if (rect && rect.width && rect.height) {
        const relativeX = rect.left - containerRect.left + rect.width / 2;
        const relativeY = rect.top - containerRect.top - 5;

        setMenuPosition({
          x: relativeX,
          y: relativeY,
        });
        setMenuOpen(true);
      }
    }
  };

  function getHighlightedSection() {
    const selection = window.getSelection?.();

    if (!selection || selection.isCollapsed) return null;

    const range = selection.getRangeAt(0);
    if (!range) return null;

    const container = document.getElementById("readable-view");
    if (!container || !container.contains(range.commonAncestorContainer)) {
      return null;
    }

    let startOffset = -1;
    let endOffset = -1;
    let currentOffset = 0;

    const treeWalker = document.createTreeWalker(
      container,
      NodeFilter.SHOW_TEXT
    );
    while (treeWalker.nextNode()) {
      const node = treeWalker.currentNode;
      const nodeLength = node.textContent?.length ?? 0;

      if (node === range.startContainer) {
        startOffset = currentOffset + range.startOffset;
      }

      if (node === range.endContainer) {
        endOffset = currentOffset + range.endOffset;
        break;
      }

      currentOffset += nodeLength;
    }

    if (startOffset === -1 || endOffset === -1) {
      return null;
    }

    return {
      linkId: link.id as number,
      text: range.toString(),
      startOffset,
      endOffset,
    };
  }

  function getHighlightedHtml(
    htmlContent: string,
    highlights: Highlight[]
  ): string {
    if (!htmlContent || !highlights || highlights.length === 0) {
      return htmlContent;
    }

    const container = document.createElement("div");
    container.innerHTML = htmlContent;

    const sortedHighlights = [...highlights].sort(
      (a, b) => a.startOffset - b.startOffset
    );

    for (const highlight of sortedHighlights) {
      applyHighlight(container, highlight);
    }

    return container.innerHTML;
  }

  function applyHighlight(container: HTMLElement, highlight: Highlight) {
    let currentOffset = 0;
    const treeWalker = document.createTreeWalker(
      container,
      NodeFilter.SHOW_TEXT
    );

    const rangesToWrap: Array<{
      node: Text;
      start: number;
      end: number;
    }> = [];

    while (treeWalker.nextNode()) {
      const node = treeWalker.currentNode as Text;
      const nodeLength = node.textContent?.length ?? 0;
      const nodeStart = currentOffset;
      const nodeEnd = nodeStart + nodeLength;

      if (nodeStart < highlight.endOffset && nodeEnd > highlight.startOffset) {
        rangesToWrap.push({
          node,
          start: Math.max(0, highlight.startOffset - nodeStart),
          end: Math.min(nodeLength, highlight.endOffset - nodeStart),
        });
      }

      currentOffset += nodeLength;
    }

    rangesToWrap.forEach(({ node, start, end }) => {
      if (start > 0) {
        node.splitText(start);
        node = node.nextSibling as Text;
        end -= start;
      }

      if (end < node.length) {
        node.splitText(end);
      }

      const highlightWrapper = document.createElement("span");

      highlightWrapper.id = `highlight-${highlight.id}`;

      highlightWrapper.dataset.highlightId = highlight.id.toString();

      highlightWrapper.classList.add(
        "cursor-pointer",
        "bg-opacity-60",
        "hover:bg-opacity-80",
        "duration-150"
      );

      if (highlight.comment) highlightWrapper.classList.add("border-b-2");

      if (highlight.color === "yellow") {
        highlightWrapper.classList.add("bg-yellow-500", "border-yellow-500");
      } else if (highlight.color === "red") {
        highlightWrapper.classList.add("bg-red-500", "border-red-500");
      } else if (highlight.color === "blue") {
        highlightWrapper.classList.add("bg-blue-500", "border-blue-500");
      } else if (highlight.color === "green") {
        highlightWrapper.classList.add("bg-green-500", "border-green-500");
      }

      node.parentNode?.insertBefore(highlightWrapper, node);
      highlightWrapper.appendChild(node);
    });
  }

  const highlightedHtml = React.useMemo(() => {
    return getHighlightedHtml(linkContent, linkHighlights || []);
  }, [linkContent, linkHighlights]);

  useEffect(() => {
    if (!user) return;
    const container = containerRef.current;
    if (!container) return;

    const getFont = () => {
      if (user.readableFontFamily === "caveat") {
        return caveat.style.fontFamily;
      } else if (user.readableFontFamily === "bentham") {
        return bentham.style.fontFamily;
      } else return user.readableFontFamily;
    };

    const pFontSize =
      user.readableFontSize || `${READER_VIEW_DEFAULTS.fontSize}px`;
    const ratio = parseInt(pFontSize) / READER_VIEW_DEFAULTS.fontSize;

    container.style.setProperty("--rv-p-font-size", pFontSize);
    container.style.setProperty(
      "--rv-p-line-height",
      user.readableLineHeight || String(READER_VIEW_DEFAULTS.lineHeight)
    );
    container.style.setProperty(
      "--rv-h1-font-size",
      READER_VIEW_DEFAULTS.h1Size * ratio + "px"
    );
    container.style.setProperty(
      "--rv-h2-font-size",
      READER_VIEW_DEFAULTS.h2Size * ratio + "px"
    );
    container.style.setProperty(
      "--rv-h3-font-size",
      READER_VIEW_DEFAULTS.h3Size * ratio + "px"
    );
    container.style.setProperty(
      "--rv-h4-font-size",
      READER_VIEW_DEFAULTS.h4Size * ratio + "px"
    );
    container.style.setProperty(
      "--rv-h5-font-size",
      READER_VIEW_DEFAULTS.h5Size * ratio + "px"
    );
    container.style.fontFamily = `${getFont()}`;
  }, [
    user?.theme,
    user?.readableFontFamily,
    user?.readableFontSize,
    user?.readableLineHeight,
  ]);

  const handleHighlightSelection = async (
    highlightId?: number | null,
    color?: "yellow" | "red" | "blue" | "green"
  ) => {
    if (
      !highlightId &&
      (!selectionInfo?.text ||
        selectionInfo.startOffset === -1 ||
        selectionInfo.endOffset === -1)
    )
      return;

    if (selectionInfo)
      setSelectionInfo({
        ...selectionInfo,
        color: color as "yellow" | "red" | "blue" | "green",
      });

    let selection = selectionInfo;

    if (highlightId) {
      selection = (linkHighlights?.find(
        (h) => h.id === selectionInfo?.highlightId
      ) ?? null) as any;
    }

    if (!selection && !highlightId) return;

    postHighlight.mutate(
      {
        ...selection,
        color: color || selectionInfo?.color || "yellow",
        comment: selectionInfo?.comment,
      } as Highlight,
      {
        onSuccess: (data) => {
          if (data) {
            setMenuOpen(true);
            if (selectionInfo) {
              setSelectionInfo({
                ...selectionInfo,
                highlightId: data.id,
                linkId: selectionInfo.linkId,
                text: selectionInfo.text,
                startOffset: selectionInfo.startOffset,
                endOffset: selectionInfo.endOffset,
                color: data.color as any,
                comment: selectionInfo.comment,
              });
            }
          }
        },
      }
    );
  };

  const handleMenuClickOutside = () => {
    setMenuOpen(false);
    setSelectionInfo(null);

    setIsCommenting(false);
    if (selectionInfo)
      setSelectionInfo({
        ...selectionInfo,
        comment: "",
      });

    if (window.getSelection) {
      window.getSelection()?.removeAllRanges();
    }
  };

  const containerRef = useRef<HTMLDivElement>(null);

  return (
    <>
      <style dangerouslySetInnerHTML={{ __html: readerViewCSS }} />
      <div
        ref={containerRef}
        className={clsx(
          "flex flex-col gap-3 items-start p-3 mx-auto bg-base-200 mt-5 relative",
          user?.readableLineWidth === "narrower"
            ? "max-w-screen-sm"
            : user?.readableLineWidth === "narrow"
              ? "max-w-screen-md"
              : user?.readableLineWidth === "normal"
                ? "max-w-screen-lg"
                : user?.readableLineWidth === "wide"
                  ? "max-w-screen-xl"
                  : user?.readableLineWidth === "wider"
                    ? "max-w-screen-2xl"
                    : ""
        )}
      >
        <div className="reader-view">
          <h1>
            {unescapeString(link?.name || link?.description || link?.url || "")}
          </h1>
        </div>

        {link?.url && (
          <Link
            href={link?.url || ""}
            title={link?.url}
            target="_blank"
            className="hover:opacity-60 duration-100 break-all text-sm flex items-center gap-1 text-neutral w-fit"
          >
            <i className="bi-link-45deg" />
            {isValidUrl(link?.url || "") && new URL(link?.url as string).host}
          </Link>
        )}

        <div className="text-sm text-neutral flex justify-between w-full gap-2">
          <LinkDate link={link} />
        </div>

        <Separator className="mt-5 mb-2" />

        {link?.readable?.startsWith("archives") ? (
          <>
            {linkContent ? (
              <div
                className={clsx("p-3 rounded-md w-full bg-base-200")}
                onMouseUp={handleMouseUp}
              >
                <div
                  id="readable-view"
                  className="line-break px-1 reader-view read-only"
                  style={{ contentVisibility: "auto" }}
                  dangerouslySetInnerHTML={{ __html: highlightedHtml }}
                />

                {menuOpen &&
                  !isPublicRoute &&
                  (permissions === true || permissions?.canUpdate) && (
                    <ClickAwayHandler
                      onClickOutside={handleMenuClickOutside}
                      className="absolute bg-base-100 p-2 z-[9999] 
                               whitespace-nowrap -translate-x-1/2 
                               -translate-y-full rounded-lg shadow-md border border-neutral-content"
                      style={{
                        left: menuPosition.x,
                        top: menuPosition.y,
                      }}
                    >
                      {isCommenting ? (
                        <div>
                          <textarea
                            value={selectionInfo?.comment}
                            onChange={(e) => {
                              if (selectionInfo)
                                setSelectionInfo({
                                  ...selectionInfo,
                                  comment: e.target.value,
                                });
                            }}
                            placeholder={t("link_description_placeholder")}
                            className="resize-none w-52 rounded-md p-2 h-32 border-neutral-content bg-base-200 focus:border-primary border-solid border outline-none duration-100"
                          />
                          <div className="flex items-center justify-end gap-2">
                            <Button
                              variant="ghost"
                              size="sm"
                              onClick={() => {
                                setIsCommenting(false);
                                if (selectionInfo)
                                  setSelectionInfo({
                                    ...selectionInfo,
                                    comment: "",
                                  });
                              }}
                            >
                              {t("cancel")}
                            </Button>
                            <Button
                              variant="primary"
                              size="sm"
                              onClick={() => {
                                handleHighlightSelection(
                                  selectionInfo?.highlightId
                                );
                                setIsCommenting(false);
                                if (selectionInfo)
                                  setSelectionInfo({
                                    ...selectionInfo,
                                    comment: "",
                                  });
                              }}
                            >
                              {t("save")}
                            </Button>
                          </div>
                        </div>
                      ) : (
                        <div className="flex items-center gap-3 justify-between select-none">
                          {["yellow", "red", "blue", "green"].map((color) => (
                            <button
                              key={color}
                              onClick={() =>
                                handleHighlightSelection(
                                  selectionInfo?.highlightId,
                                  color as "yellow" | "red" | "blue" | "green"
                                )
                              }
                              className={`w-5 h-5 rounded-full ${
                                color === "yellow"
                                  ? "bg-yellow-300"
                                  : color === "red"
                                    ? "bg-red-500"
                                    : color === "blue"
                                      ? "bg-blue-500"
                                      : "bg-green-500"
                              } hover:opacity-70 duration-100 relative`}
                              title={`${
                                color.charAt(0).toUpperCase() + color.slice(1)
                              } Highlight`}
                            >
                              {selectionInfo?.highlightId &&
                                linkHighlights?.find(
                                  (h) => h.id === selectionInfo.highlightId
                                )?.color === color && (
                                  <i className="bi-check2 text-sm text-black absolute inset-0 flex items-center justify-center" />
                                )}
                            </button>
                          ))}

                          <button
                            className="hover:opacity-70 duration-100"
                            onClick={() => setIsCommenting(true)}
                          >
                            <i className="bi-chat-text" />
                          </button>

                          {selectionInfo?.highlightId && (
                            <button
                              onClick={() => {
                                deleteHighlight.mutate(
                                  selectionInfo.highlightId as number
                                );
                                setSelectionInfo(null);
                              }}
                              className="hover:opacity-70 duration-100"
                              title="Delete"
                            >
                              <i className="bi-trash" />
                            </button>
                          )}
                        </div>
                      )}
                    </ClickAwayHandler>
                  )}
              </div>
            ) : (
              <PreservationSkeleton className="h-fit" />
            )}
          </>
        ) : (
          <div className={`w-full h-full flex flex-col justify-center`}>
            <svg
              xmlns="http://www.w3.org/2000/svg"
              width="16"
              height="16"
              fill="currentColor"
              className="w-1/4 min-w-[7rem] max-w-[15rem] h-auto mx-auto mb-5"
              viewBox="0 0 16 16"
            >
              <path d="m14.12 10.163 1.715.858c.22.11.22.424 0 .534L8.267 15.34a.598.598 0 0 1-.534 0L.165 11.555a.299.299 0 0 1 0-.534l1.716-.858 5.317 2.659c.505.252 1.1.252 1.604 0l5.317-2.66zM7.733.063a.598.598 0 0 1 .534 0l7.568 3.784a.3.3 0 0 1 0 .535L8.267 8.165a.598.598 0 0 1-.534 0L.165 4.382a.299.299 0 0 1 0-.535L7.733.063z" />
              <path d="m14.12 6.576 1.715.858c.22.11.22.424 0 .534l-7.568 3.784a.598.598 0 0 1-.534 0L.165 7.968a.299.299 0 0 1 0-.534l1.716-.858 5.317 2.659c.505.252 1.1.252 1.604 0l5.317-2.659z" />
            </svg>
            <p className="text-center text-xl">
              {t("link_preservation_in_queue")}
            </p>
            <p className="text-center text-lg mt-2">{t("check_back_later")}</p>
          </div>
        )}
      </div>
    </>
  );
}
