/*
This file is part of the Notesnook project (https://notesnook.com/)

Copyright (C) 2023 Streetwriters (Private) Limited

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

import { ToolProps } from "../types.js";
import { ToolButton } from "../components/tool-button.js";
import { useEffect, useRef, useState } from "react";
import { ResponsivePresenter } from "../../components/responsive/index.js";
import { LinkPopup } from "../popups/link-popup.js";
import { useToolbarLocation } from "../stores/toolbar-store.js";
import { MoreTools } from "../components/more-tools.js";
import { useRefValue } from "../../hooks/use-ref-value.js";
import { findMark, selectionToOffset } from "../../utils/prosemirror.js";
import { Flex, Link, Text } from "@theme-ui/components";
import { ImageNode } from "../../extensions/image/index.js";
import { Link as LinkNode } from "../../extensions/link/index.js";
import { getMarkAttributes } from "@tiptap/core";
import { useHoverPopupContext } from "../floating-menus/hover-popup/context.js";
import { strings } from "@notesnook/intl";
import { find } from "linkifyjs";
import { Icons } from "../icons.js";
import { mdiNoteOutline, mdiBookOutline, mdiPound } from "@mdi/js";
import { Icon } from "@notesnook/ui";
import { LinkData } from "../../types.js";

export function LinkSettings(props: ToolProps) {
  const { editor } = props;
  const isBottom = useToolbarLocation() === "bottom";
  if (!editor.isActive("link") || !isBottom) return null;

  return (
    <MoreTools
      {...props}
      autoOpen
      autoCloseOnUnmount
      popupId="linkSettings"
      tools={
        editor.isEditable
          ? ["openLink", "editLink", "removeLink", "copyLink"]
          : ["openLink", "copyLink"]
      }
    />
  );
}

export function AddLink(props: ToolProps) {
  const { editor } = props;

  const isActive = editor.isActive("link");

  if (isActive) return <EditLink {...props} icon={"linkEdit"} />;
  return (
    <LinkTool
      {...props}
      onDone={(attributes) => editor.commands.toggleLink(attributes)}
      onClick={() => {
        if (!editor) return;
        const selectedText = editor.state.doc.textBetween(
          editor.state.selection.from,
          editor.state.selection.to
        );
        const href =
          find(selectedText).find(
            (item) => item.isLink && item.href === selectedText
          )?.href ?? "";
        return { title: selectedText, href };
      }}
    />
  );
}

export function AddInternalLink(props: ToolProps) {
  const { editor } = props;
  const isActive = editor.isActive(LinkNode.name);

  return (
    <ToolButton
      icon={props.icon}
      title={props.title}
      disabled={isActive}
      onClick={async () => {
        const link = await editor.storage.createInternalLink?.();
        if (!link) return;

        const selectedText = editor.state.doc.textBetween(
          editor.state.selection.from,
          editor.state.selection.to
        );
        editor.commands.setLink({ ...link, title: selectedText || link.title });
      }}
    />
  );
}

export function EditLink(props: ToolProps) {
  const { editor } = props;
  const { selectedNode: _selectedNode, hide } = useHoverPopupContext();
  const selectedNode = useRefValue(
    _selectedNode || selectionToOffset(editor.state)
  );
  const { node } = _selectedNode || {};
  const link = node ? findMark(node, LinkNode.name) : null;
  const attrs = link?.attrs || getMarkAttributes(editor.state, LinkNode.name);

  if (!editor.isEditable) return null;
  if (attrs && isInternalLink(attrs.href))
    return (
      <ToolButton
        icon={props.icon}
        title={props.title}
        onClick={async () => {
          hide();
          const link = await editor.storage.createInternalLink?.();
          if (!link) return;
          const { from, to } = editor.state.selection;
          if (selectedNode.current)
            editor.commands.setTextSelection(selectedNode.current);

          const selectedText =
            !!selectedNode.current &&
            editor.state.doc.textBetween(
              selectedNode.current.from,
              selectedNode.current.to
            );
          editor.commands.setLink({
            ...link,
            title: selectedText || link.title
          });
          if (selectedNode.current)
            editor.commands.setTextSelection({ from, to });
        }}
      />
    );

  return (
    <LinkTool
      editor={props.editor}
      icon={props.icon}
      title={props.title}
      isEditing
      onDone={(attributes) => {
        if (selectedNode.current)
          editor.chain().focus().setTextSelection(selectedNode.current).run();
        editor.commands.setLink(attributes);
        hide();
      }}
      onClick={() => {
        if (!selectedNode.current) return;

        const { node } = selectedNode.current;
        if (!node) return;

        const selectedText = node.textContent;
        const mark = findMark(node, "link");

        if (!mark) return;
        return {
          title: selectedText,
          href: mark.attrs.href
        };
      }}
    />
  );
}

export function RemoveLink(props: ToolProps) {
  const { editor } = props;
  const { selectedNode, hide } = useHoverPopupContext();
  if (!editor.isEditable) return null;
  return (
    <ToolButton
      icon={props.icon}
      title={props.title}
      toggled={false}
      onClick={() => {
        if (selectedNode)
          editor.commands.setTextSelection({
            from: selectedNode.from,
            to: selectedNode.to
          });
        editor.chain().focus().unsetLink().run();
        hide();
      }}
    />
  );
}

export function OpenLink(props: ToolProps) {
  const { editor } = props;
  const { selectedNode: _selectedNode, hide } = useHoverPopupContext();
  const selectedNode = useRefValue(
    _selectedNode || selectionToOffset(editor.state)
  );
  const { node } = selectedNode.current || {};
  const link = node ? findMark(node, "link") : null;
  const href = link?.attrs.href ?? null;
  const [loading, setLoading] = useState(false);
  const [linkData, setLinkData] = useState<LinkData | undefined>(undefined);

  useEffect(() => {
    if (!href) return;

    (async () => {
      try {
        setLoading(true);
        const result = await editor.storage.getLinkData?.(href);
        setLinkData(result);
        setLoading(false);
      } catch (e) {
        setLoading(false);
      }
    })();
  }, [href]);

  if (!link || !href) return null;

  const title = linkData?.title || href;

  return (
    <Flex sx={{ alignItems: "center" }}>
      {linkData?.type && (
        <LinkTypeIcon type={linkData.type} metadata={linkData.metadata} />
      )}
      {loading ? (
        <Text sx={{ fontSize: "subBody" }}>{strings.loading()}</Text>
      ) : (
        <Link
          href={href}
          onClick={(e) => {
            e.preventDefault();
            editor.storage.openLink?.(href);
            hide();
          }}
          target="_blank"
          variant="body"
          sx={{
            fontSize: "subBody",
            fontFamily: "body",
            mr: 4,
            color: "accent",
            maxWidth: [150, 250],
            overflow: "hidden",
            textOverflow: "ellipsis",
            whiteSpace: "nowrap",
            ":visited": { color: "accent" },
            ":hover": { color: "accent", opacity: 0.8 }
          }}
        >
          {title}
        </Link>
      )}
      <ToolButton
        icon={props.icon}
        title={props.title}
        toggled={false}
        onClick={() => {
          editor.storage.openLink?.(href);
          hide();
        }}
      />
    </Flex>
  );
}

export function CopyLink(props: ToolProps) {
  const { editor } = props;
  const { selectedNode: _selectedNode, hide } = useHoverPopupContext();
  const selectedNode = useRefValue(
    _selectedNode || selectionToOffset(editor.state)
  );
  const { node } = selectedNode.current || {};
  const link = node ? findMark(node, "link") : null;
  if (!link) return null;
  const href = link?.attrs.href;
  if (!href) return null;

  return (
    <ToolButton
      {...props}
      toggled={false}
      onClick={async () => {
        const linkData = await editor.storage.getLinkData?.(href);
        if (linkData?.title) {
          editor.storage.copyToClipboard?.(
            href,
            `<a href="${href}">${linkData.title}</a>`
          );
        } else {
          editor.storage.copyToClipboard?.(href);
        }
        hide();
      }}
    />
  );
}

export type LinkDefinition = {
  href: string;
  title?: string;
};
type LinkToolProps = ToolProps & {
  isEditing?: boolean;
  onDone: (link: LinkDefinition) => void;
  onClick: () => LinkDefinition | undefined;
};
function LinkTool(props: LinkToolProps) {
  const { isEditing, onClick, onDone, editor, ...toolProps } = props;
  const buttonRef = useRef<HTMLButtonElement>(null);
  const [isOpen, setIsOpen] = useState(false);

  const [linkDefinition, setLinkDefinition] = useState<LinkDefinition>();
  const isImageActive = editor.isActive(ImageNode.name);

  return (
    <>
      <ToolButton
        {...toolProps}
        buttonRef={buttonRef}
        onClick={() => {
          const result = onClick();
          if (!result) return;
          setLinkDefinition(result);
          setIsOpen(true);
        }}
        toggled={isOpen}
      />
      <ResponsivePresenter
        mobile="sheet"
        desktop="popup"
        position={{
          target: buttonRef.current || undefined,
          isTargetAbsolute: true,
          location: "below",
          align: "center",
          yOffset: 5
        }}
        title={isEditing ? strings.editLink() : strings.insertLink()}
        isOpen={isOpen}
        items={[]}
        onClose={() => {
          setIsOpen(false);
          editor.commands.focus();
        }}
        focusOnRender={false}
      >
        <LinkPopup
          link={linkDefinition}
          isEditing={isEditing}
          isImageActive={isImageActive}
          onClose={() => setIsOpen(false)}
          onDone={(link) => {
            onDone(link);
            setIsOpen(false);
          }}
        />
      </ResponsivePresenter>
    </>
  );
}

const LINK_TYPE_ICONS: Record<LinkData["type"], string> = {
  note: mdiNoteOutline,
  notebook: mdiBookOutline,
  tag: mdiPound,
  color: Icons.circle
};

function LinkTypeIcon({
  type,
  metadata
}: {
  type: LinkData["type"];
  metadata?: LinkData["metadata"];
}) {
  const path = LINK_TYPE_ICONS[type];
  if (!path) return null;

  const color =
    type === "color" && metadata?.colorCode
      ? metadata.colorCode
      : "icon-secondary";

  return <Icon path={path} color={color} size={13} sx={{ mr: 1 }} />;
}

export function isInternalLink(href?: string | null) {
  return typeof href === "string" ? href.startsWith("nn://") : false;
}
