import { DownloadIcon } from "@chakra-ui/icons";
import {
  Box,
  Collapse,
  Stack,
  StackProps,
  Text,
  Tooltip,
  useDisclosure,
} from "@chakra-ui/react";
import { Assembly } from "@jsii/spec";
import { Fragment, FunctionComponent, ReactNode } from "react";
import spdx from "spdx-license-list";
import { ToggleButton } from "./ToggleButton";
import { PackageLinkConfig } from "../../../api/config";
import { Metadata } from "../../../api/package/metadata";
import { PackageStats } from "../../../api/stats";
import { ExternalLink } from "../../../components/ExternalLink";
import { Highlight } from "../../../components/Highlight";
import { LicenseLink } from "../../../components/LicenseLink";
import { NavLink } from "../../../components/NavLink";
import { Time } from "../../../components/Time";
import { FORMATS } from "../../../constants/dates";
import { useConfigValue } from "../../../hooks/useConfigValue";
import { useStats } from "../../../hooks/useStats";
import { highlightsFrom } from "../../../util/package";
import { getRepoUrlAndHost, getSearchPath } from "../../../util/url";
import { usePackageState } from "../PackageState";

interface DetailsProps extends StackProps {}

const WithLabel: FunctionComponent<{ label: ReactNode; tooltip?: string }> = ({
  children,
  label,
  tooltip,
}) => (
  <Tooltip hasArrow isDisabled={!tooltip} label={tooltip} placement="left">
    <Text fontWeight="bold">
      {label}{" "}
      <Box as="span" fontWeight="normal">
        {children}
      </Box>
    </Text>
  </Tooltip>
);

const Downloads: FunctionComponent<{ downloads: number }> = ({ downloads }) => (
  <WithLabel
    label={
      <>
        <Box as="span" mr={1}>
          <DownloadIcon />
        </Box>
        {downloads.toLocaleString()}
      </>
    }
    tooltip="Download numbers are periodically sourced from the npm registry"
  >
    Weekly downloads
  </WithLabel>
);

const getDetailItemsFromPackage = ({
  assembly,
  metadata,
  name,
  packageLinks,
  stats,
}: {
  assembly?: Assembly;
  metadata?: Metadata;
  stats?: PackageStats;
  packageLinks?: PackageLinkConfig[];
  name: string;
}): ReactNode[] => {
  const items: ReactNode[] = [];

  if (assembly || metadata || stats || packageLinks) {
    const downloads: number | undefined =
      stats?.packages?.[name]?.downloads?.npm;

    if (downloads !== undefined) {
      items.push(<Downloads downloads={downloads} />);
    }

    const username = assembly?.author.name;
    const repository = assembly?.repository;
    const license = assembly?.license;

    if (username) {
      const author = (
        <NavLink color="link" to={getSearchPath({ query: username })}>
          {username}
        </NavLink>
      );
      items.push(<WithLabel label="Author">{author}</WithLabel>);
    }

    const date = metadata?.date;

    if (date) {
      const publishDate = (
        <Time
          date={new Date(date)}
          fontWeight="normal"
          format={FORMATS.PUBLISH_DATE}
        />
      );
      items.push(<WithLabel label="Published">{publishDate}</WithLabel>);
    }

    if (repository) {
      const repo = getRepoUrlAndHost(repository.url);

      if (repo) {
        const repoLink = (
          <ExternalLink href={repo.url}>{repo.hostname}</ExternalLink>
        );
        items.push(<WithLabel label="Repository">{repoLink}</WithLabel>);
      }
    }

    if (license && license in spdx) {
      const licenseLink = <LicenseLink license={license} />;
      items.push(<WithLabel label="License">{licenseLink}</WithLabel>);
    }

    if (packageLinks?.length) {
      packageLinks.forEach(({ linkLabel, configKey, linkText }) => {
        const target = (metadata?.packageLinks ?? {})[configKey];
        if (target) {
          const link = (
            <ExternalLink href={target}>{linkText ?? target}</ExternalLink>
          );
          items.push(
            <WithLabel label={linkLabel}>
              {linkLabel}: {link}
            </WithLabel>
          );
        }
      });
    }

    const registry =
      metadata?.links?.npm ??
      `https://www.npmjs.com/package/${assembly?.name}/v/${assembly?.version}`;

    if (registry) {
      const registryLink = (
        <ExternalLink href={registry}>
          {new URL(registry).hostname}
        </ExternalLink>
      );

      items.push(<WithLabel label="Registry">{registryLink}</WithLabel>);
    }
  }

  return items.map((item, i) => <Fragment key={i}>{item}</Fragment>);
};

export const Details: FunctionComponent<DetailsProps> = (props) => {
  const state = usePackageState();
  const stats = useStats().data;
  const collapse = useDisclosure({ defaultIsOpen: true });
  const packageLinks = useConfigValue("packageLinks");

  const assembly = state.assembly.data;
  const metadata = state.metadata.data;
  const name = state.scope ? `${state.scope}/${state.name}` : state.name;

  const [highlight] = highlightsFrom(metadata?.packageTags);

  const items = getDetailItemsFromPackage({
    assembly,
    metadata,
    name,
    packageLinks,
    stats,
  });

  if (!items.length) return null;

  const alwaysShow = items.slice(0, 2);
  const showWithCollapse = items.slice(2, items.length);

  return (
    <Stack
      align="start"
      color="textPrimary"
      fontSize=".75rem"
      mt={2}
      spacing={2}
      {...props}
    >
      {highlight && <Highlight {...highlight} />}
      {/* TODO: Highlight element */}
      {alwaysShow}
      {showWithCollapse.length > 0 && (
        <>
          <Collapse animateOpacity in={collapse.isOpen}>
            <Stack spacing={2}>{showWithCollapse}</Stack>
          </Collapse>
          <ToggleButton
            closeText={`See fewer details (${alwaysShow.length})`}
            fontSize="inherit"
            isOpen={collapse.isOpen}
            onClick={collapse.onToggle}
            openText={`See more details (${showWithCollapse.length})`}
          />
        </>
      )}
    </Stack>
  );
};
