import React, {
  useRef,
  useState,
  useEffect,
  useMemo,
  HTMLAttributes,
} from "react";
import { useRecoilValue, useRecoilState } from "recoil";
import { useHotkeys } from "react-hotkeys-hook";
import { motion, AnimatePresence, Variants } from "framer-motion";
import { Svg2Png } from "svg2png-converter";
import { saveAs } from "file-saver";
import {
  Copy,
  CheckCircle,
  ArrowFatLinesDown,
  XCircle,
  CaretDoubleLeft,
  CaretDoubleRight,
} from "@phosphor-icons/react";
import { IconStyle } from "@phosphor-icons/core";
import ReactGA from "react-ga4";

import Tabs, { Tab } from "@/components/Tabs";
import { useMediaQuery, useTransientState, useSessionStorage } from "@/hooks";
import { SnippetType } from "@/lib";
import {
  iconWeightAtom,
  iconSizeAtom,
  iconColorAtom,
  selectionEntryAtom,
  isDarkThemeSelector,
} from "@/state";
import { getCodeSnippets, supportsWeight } from "@/utils";

import TagCloud from "./TagCloud";

const variants: Record<string, Variants> = {
  desktop: {
    initial: { y: 188 },
    animate: { y: 0 },
    exit: { y: 188 },
  },
  mobile: {
    initial: { y: "60vh" },
    animate: { y: 0 },
    exit: { y: "60vh" },
  },
};

const RENDERED_SNIPPETS = [
  SnippetType.REACT,
  SnippetType.HTML,
  SnippetType.VUE,
  SnippetType.FLUTTER,
  SnippetType.ELM,
  SnippetType.SWIFT,
];

enum CopyType {
  SVG,
  SVG_RAW,
  SVG_DATA,
  PNG,
  PNG_DATA,
  UNICODE,
}

function cloneWithSize(svg: SVGSVGElement, size: number): SVGSVGElement {
  const sized = svg.cloneNode(true) as SVGSVGElement;
  sized.setAttribute("width", `${size}`);
  sized.setAttribute("height", `${size}`);
  return sized;
}

const ActionButton = (
  props: {
    active?: boolean;
    label: string;
    download?: boolean;
    disabled?: boolean;
  } & HTMLAttributes<HTMLButtonElement>
) => {
  const { active, download, label, ...rest } = props;
  const Icon = download ? ArrowFatLinesDown : Copy;
  return (
    <button
      {...rest}
      className={`action-button text ${props.disabled ? "disabled" : ""}`}
      aria-disabled={props.disabled}
      tabIndex={0}
    >
      {active ? (
        <CheckCircle size={20} color="var(--olive)" weight="fill" />
      ) : (
        <Icon size={20} color="currentColor" weight="fill" />
      )}
      {label}
    </button>
  );
};

const Panel = () => {
  const [entry, setSelectionEntry] = useRecoilState(selectionEntryAtom);

  const weight = useRecoilValue(iconWeightAtom);
  const size = useRecoilValue(iconSizeAtom);
  const color = useRecoilValue(iconColorAtom);
  const isDark = useRecoilValue(isDarkThemeSelector);
  const [copied, setCopied] = useTransientState<SnippetType | CopyType | false>(
    false,
    2000
  );
  const ref = useRef<SVGSVGElement>(null);

  const [showMoreActions, setShowMoreActions] = useState<boolean>(false);

  const [i, setInitialTab] = useSessionStorage("tab", 0);

  const isMobile = useMediaQuery("(max-width: 719px)");

  const [snippets, tabs] = useMemo<
    [Partial<Record<SnippetType, string>>, Tab[]]
  >(() => {
    if (!entry) return [{}, []];

    const snippets = getCodeSnippets({
      displayName: entry?.pascal_name!,
      name: entry.name,
      weight,
      size,
      color,
    });

    const tabs = [
      {
        header: "Tags",
        content: (
          <TagCloud
            name={entry.name}
            tags={Array.from(
              new Set<string>([
                ...entry.tags,
                ...entry.categories,
                ...entry.name.split("-"),
              ])
            )}
          />
        ),
      },
    ].concat(
      RENDERED_SNIPPETS.map((type) => {
        const isWeightSupported = supportsWeight({ type, weight });

        return {
          header: type,
          content: (
            <div className="snippet" key={type}>
              <pre className={!isWeightSupported ? "disabled" : undefined}>
                <span className={!isWeightSupported ? "disabled" : undefined}>
                  {isWeightSupported
                    ? snippets[type]
                    : "This weight is not yet supported"}
                </span>
                {isWeightSupported && (
                  <button
                    title="Copy snippet"
                    className="action-button"
                    onClick={(e) => handleCopySnippet(e, type)}
                  >
                    {copied === type ? (
                      <CheckCircle
                        size={20}
                        color="var(--olive)"
                        weight="fill"
                      />
                    ) : (
                      <Copy size={20} color="var(--foreground)" weight="fill" />
                    )}
                  </button>
                )}
              </pre>
            </div>
          ),
        };
      })
    );

    return [snippets, tabs];
  }, [entry, weight, size, color, copied, isDark]);

  useHotkeys("esc", () => setSelectionEntry(null));

  useEffect(() => {
    if (!entry) return;
    ReactGA.event({
      category: "Grid",
      action: "Details",
      label: entry.name,
    });
  }, [entry]);

  const handleCopySnippet = (
    event: React.MouseEvent<HTMLButtonElement, MouseEvent>,
    type: SnippetType
  ) => {
    event.currentTarget.blur();
    if (!entry) return;

    setCopied(type);
    const data = snippets[type];
    data && void navigator.clipboard?.writeText(data);
  };

  const handleCopySVG = (
    event: React.MouseEvent<HTMLButtonElement, MouseEvent>
  ) => {
    event.currentTarget.blur();
    if (!entry) return;
    if (!ref.current) return;

    navigator.clipboard?.writeText(cloneWithSize(ref.current, size).outerHTML);
    setCopied(CopyType.SVG);
  };

  const handleCopyDataSVG = (
    event: React.MouseEvent<HTMLButtonElement, MouseEvent>
  ) => {
    event.currentTarget.blur();
    if (!entry) return;
    if (!ref.current) return;

    navigator.clipboard?.writeText(
      "data:image/svg+xml;base64," +
        btoa(
          unescape(
            encodeURIComponent(cloneWithSize(ref.current, size).outerHTML)
          )
        )
    );
    setCopied(CopyType.SVG_DATA);
  };

  const handleCopyRawSVG = async () => {
    if (!entry) return;

    const { name } = entry;
    const data = await fetch(
      `https://raw.githubusercontent.com/phosphor-icons/core/main/raw/${weight}/${name}${
        weight === "regular" ? "" : `-${weight}`
      }.svg`
    );
    const content = await data.text();
    navigator.clipboard?.writeText(content);
    setCopied(CopyType.SVG_RAW);
  };

  const handleCopyUnicode = async () => {
    if (!entry) return;

    const content = String.fromCharCode(entry.codepoint);
    navigator.clipboard?.writeText(content);
    setCopied(CopyType.UNICODE);
  };

  const handleDownloadSVG = (
    event: React.MouseEvent<HTMLButtonElement, MouseEvent>
  ) => {
    event.currentTarget.blur();
    if (!entry) return;
    if (!ref.current) return;

    const blob = new Blob([cloneWithSize(ref.current, size).outerHTML]);
    saveAs(
      blob,
      `${entry?.name}${weight === "regular" ? "" : `-${weight}`}.svg`
    );
  };

  const handleDownloadRawSVG = async () => {
    if (!entry) return;

    const { name } = entry;
    saveAs(
      `https://raw.githubusercontent.com/phosphor-icons/core/main/raw/${weight}/${name}${
        weight === "regular" ? "" : `-${weight}`
      }.svg`,
      `${entry?.name}${weight === "regular" ? "" : `-${weight}`}.svg`
    );
  };

  const handleDownloadPNG = async (
    event: React.MouseEvent<HTMLButtonElement, MouseEvent>
  ) => {
    event.currentTarget.blur();
    if (!entry) return;
    if (!ref.current) return;

    Svg2Png.save(
      cloneWithSize(ref.current, size),
      `${entry?.name}${weight === "regular" ? "" : `-${weight}`}.png`
    );
  };

  const handleCopyPNG = async (
    event: React.MouseEvent<HTMLButtonElement, MouseEvent>
  ) => {
    event.currentTarget.blur();
    if (!entry) return;
    if (!ref.current) return;

    Svg2Png.toDataURL(cloneWithSize(ref.current, size))
      .then((data) => fetch(data))
      .then((res) => res.blob())
      .then((blob) =>
        navigator.clipboard.write([
          new ClipboardItem({
            [blob.type]: blob,
          }),
        ])
      )
      .then(() => {
        setCopied(CopyType.PNG);
      });
  };

  // const handleCopyDataPNG = async (
  //   event: React.MouseEvent<HTMLButtonElement, MouseEvent>
  // ) => {
  //   event.currentTarget.blur();
  //   if (!entry) return;
  //   if (!ref.current) return;

  //   const data = await Svg2Png.toDataURL(cloneWithSize(ref.current, size));
  //   navigator.clipboard?.writeText(data);
  //   setCopied(CopyType.PNG_DATA);
  // };

  return (
    <AnimatePresence initial={true}>
      {!!entry && (
        <motion.aside
          initial="initial"
          animate="animate"
          exit="exit"
          variants={isMobile ? variants.mobile : variants.desktop}
          className="secondary detail-footer card"
          transition={isMobile ? { duration: 0.25 } : { duration: 0.1 }}
        >
          <div className="detail-preview">
            <figure>
              <entry.Icon ref={ref} size={64}></entry.Icon>
              <figcaption>
                <p>{entry.name}</p>
                <small className="versioning">
                  U+{entry.codepoint.toString(16).toUpperCase()}
                </small>
                <small className="versioning">
                  available in v{entry.published_in.toFixed(1)}+
                </small>
              </figcaption>
            </figure>
            <hr />
            <div className="detail-meta">
              <div className="detail-actions">
                {!showMoreActions ? (
                  <>
                    <ActionButton
                      label="SVG"
                      title="Download SVG"
                      download
                      onClick={handleDownloadSVG}
                    />

                    <ActionButton
                      label="SVG Raw"
                      title="Download raw SVG including original strokes"
                      download
                      onClick={handleDownloadRawSVG}
                    />

                    <ActionButton
                      label="SVG"
                      title="Copy SVG"
                      active={copied === CopyType.SVG}
                      onClick={handleCopySVG}
                    />

                    <ActionButton
                      label="SVG Raw"
                      title="Copy raw SVG including original strokes"
                      active={copied === CopyType.SVG_RAW}
                      onClick={handleCopyRawSVG}
                    />
                  </>
                ) : (
                  <>
                    <ActionButton
                      label="PNG"
                      title="Download PNG"
                      download
                      onClick={handleDownloadPNG}
                    />

                    <ActionButton
                      label="PNG"
                      title="Copy PNG"
                      active={copied === CopyType.PNG}
                      onClick={handleCopyPNG}
                    />

                    <ActionButton
                      label="Data SVG"
                      title="Copy SVG as DataURL"
                      active={copied === CopyType.SVG_DATA}
                      onClick={handleCopyDataSVG}
                    />

                    <ActionButton
                      label="Unicode"
                      title="Copy Unicode character (v2.1.0 or newer)"
                      active={copied === CopyType.UNICODE}
                      disabled={weight === IconStyle.DUOTONE}
                      onClick={handleCopyUnicode}
                    />
                  </>
                )}
              </div>
              <button
                className="action-button"
                title="More actions"
                tabIndex={0}
                onClick={() => setShowMoreActions((s) => !s)}
              >
                {!showMoreActions ? (
                  <CaretDoubleRight
                    size={16}
                    weight="bold"
                    color="var(--foreground)"
                  />
                ) : (
                  <CaretDoubleLeft
                    size={16}
                    weight="bold"
                    color="var(--foreground)"
                  />
                )}
              </button>
            </div>
          </div>

          <Tabs tabs={tabs} initialIndex={i} onTabChange={setInitialTab} />

          <button
            tabIndex={0}
            className="close-button"
            onClick={() => setSelectionEntry(null)}
            onKeyDown={(e) => {
              e.key === "Enter" && setSelectionEntry(null);
            }}
          >
            <XCircle color="currentColor" size={28} weight="fill" />
          </button>
        </motion.aside>
      )}
    </AnimatePresence>
  );
};

export default Panel;
