"use client";

import { useCallback, useMemo, useState } from "react";
import { Button, Divider, Group, Stack, Text, Title, Tooltip } from "@mantine/core";
import { useLocalStorage } from "@mantine/hooks";
import {
  IconArchive,
  IconCheck,
  IconCircleDot,
  IconCircleFilled,
  IconExternalLink,
  IconGitFork,
  IconProgressCheck,
  IconStar,
  IconTriangleFilled,
} from "@tabler/icons-react";
import combineClasses from "clsx";
import { useFormatter, useNow } from "next-intl";
import ReactMarkdown from "react-markdown";

import { clientApi } from "@homarr/api/client";
import { useRequiredBoard } from "@homarr/boards/context";
import { isDateWithin, isNullOrWhitespace, splitToChunksWithNItems } from "@homarr/common";
import { useScopedI18n } from "@homarr/translation/client";
import { MaskedOrNormalImage } from "@homarr/ui";

import type { WidgetComponentProps } from "../definition";
import classes from "./component.module.scss";
import type { ReleasesRepository, ReleasesRepositoryResponse } from "./releases-repository";

const formatRelativeDate = (value: string): string => {
  const isMonths = /\d+m/g.test(value);
  const isOtherUnits = /\d+[HDWY]/g.test(value);
  return isMonths ? value.toUpperCase() : isOtherUnits ? value.toLowerCase() : value;
};

export default function ReleasesWidget({ options }: WidgetComponentProps<"releases">) {
  const t = useScopedI18n("widget.releases");
  const now = useNow();
  const formatter = useFormatter();
  const board = useRequiredBoard();
  const [expandedRepositoryId, setExpandedRepositoryId] = useState<string | null>(null);
  const hasIconColor = useMemo(() => board.iconColor !== null, [board.iconColor]);
  const [releasesViewedList, setReleasesViewedList] = useLocalStorage<Record<string, string>>({
    key: "releases-viewed-versions",
    defaultValue: {},
  });

  const relativeDateOptions = useMemo(
    () => ({
      newReleaseWithin: formatRelativeDate(options.newReleaseWithin),
      staleReleaseWithin: formatRelativeDate(options.staleReleaseWithin),
    }),
    [options.newReleaseWithin, options.staleReleaseWithin],
  );

  // Group repositories by integration
  const groupedRepositories = useMemo(() => {
    return options.repositories.reduce(
      (acc, repo) => {
        const key = repo.providerIntegrationId;
        if (!key) return acc;

        acc[key] ??= [];
        acc[key].push(repo);

        return acc;
      },
      {} as Record<string, ReleasesRepository[]>,
    );
  }, [options.repositories]);

  // For each group, split into chunks of 5
  const batchedRepositories = useMemo(() => {
    return Object.entries(groupedRepositories).flatMap(([integrationId, group]) =>
      splitToChunksWithNItems(group, 5).map((chunk) => ({
        integrationId,
        repositories: chunk,
      })),
    );
  }, [groupedRepositories]);

  const [results] = clientApi.useSuspenseQueries((t) =>
    batchedRepositories.flatMap(({ integrationId, repositories }) =>
      t.widget.releases.getLatest({
        integrationId,
        repositories: repositories.map((repository) => ({
          id: repository.id,
          identifier: repository.identifier,
          versionFilter: repository.versionFilter,
        })),
      }),
    ),
  );

  const repositories = useMemo(() => {
    const formattedResults = options.repositories
      .map((repository) => {
        if (repository.providerIntegrationId === undefined) {
          return {
            ...repository,
            isNewRelease: false,
            isStaleRelease: false,
            latestReleaseAt: undefined,
            error: {
              code: "noProviderSeleceted",
            },
          };
        }

        const response = results.flat().find(({ data }) => data.id === repository.id)?.data;

        if (response === undefined)
          return {
            ...repository,
            isNewRelease: false,
            isStaleRelease: false,
            latestReleaseAt: undefined,
            error: {
              code: "noProviderResponse",
            },
          };

        return {
          ...repository,
          ...response,
          isNewRelease:
            relativeDateOptions.newReleaseWithin !== "" && response.latestReleaseAt
              ? isDateWithin(response.latestReleaseAt, relativeDateOptions.newReleaseWithin)
              : false,
          isStaleRelease:
            relativeDateOptions.staleReleaseWithin !== "" && response.latestReleaseAt
              ? !isDateWithin(response.latestReleaseAt, relativeDateOptions.staleReleaseWithin)
              : false,
          viewed: releasesViewedList[repository.id] === response.latestRelease,
        };
      })
      .filter(
        (repository) =>
          repository.error !== undefined ||
          !options.showOnlyHighlighted ||
          repository.isNewRelease ||
          repository.isStaleRelease,
      )
      .sort((repoA, repoB) => {
        if (repoA.latestReleaseAt === undefined) return -1;
        if (repoB.latestReleaseAt === undefined) return 1;
        return repoA.latestReleaseAt > repoB.latestReleaseAt ? -1 : 1;
      }) as ReleasesRepositoryResponse[];

    if (typeof options.topReleases !== "string" && options.topReleases > 0) {
      return formattedResults.slice(0, options.topReleases);
    }

    return formattedResults;
  }, [
    results,
    options.repositories,
    options.showOnlyHighlighted,
    options.topReleases,
    relativeDateOptions.newReleaseWithin,
    relativeDateOptions.staleReleaseWithin,
    releasesViewedList,
  ]);

  const toggleExpandedDisplay = useCallback(
    (repository: ReleasesRepositoryResponse) =>
      setExpandedRepositoryId(expandedRepositoryId === repository.id ? null : repository.id),
    [expandedRepositoryId],
  );

  const markReleaseViewed = useCallback(
    (repository: ReleasesRepositoryResponse) => {
      repository.viewed = true;
      setReleasesViewedList((prev) => ({ ...prev, [repository.id]: repository.latestRelease ?? "" }));
    },
    [setReleasesViewedList],
  );

  return (
    <Stack gap={0} className="releases">
      {repositories.map((repository: ReleasesRepositoryResponse) => {
        const isActive = expandedRepositoryId === repository.id;
        const hasError = repository.error !== undefined;

        return (
          <Stack
            key={repository.id}
            className={combineClasses(
              "releases-repository",
              // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
              `releases-repository-${repository.integration?.name ?? "error"}-${repository.name || repository.identifier.replace(/[^a-zA-Z0-9]/g, "_")}`,
              classes.releasesRepository,
            )}
            gap={0}
          >
            <Group
              className={combineClasses("releases-repository-header", classes.releasesRepositoryHeader, {
                [classes.active ?? ""]: isActive,
              })}
              p="xs"
              onClick={() => toggleExpandedDisplay(repository)}
            >
              <MaskedOrNormalImage
                className="releases-repository-header-icon"
                imageUrl={repository.iconUrl ?? repository.integration?.iconUrl}
                hasColor={hasIconColor}
                style={{
                  width: "1em",
                  aspectRatio: "1/1",
                }}
              />

              <Group
                className="releases-repository-header-nameVersion-wrapper"
                gap={5}
                justify="space-between"
                miw={0}
                style={{ flex: 1 }}
              >
                {!options.showOnlyIcon && (
                  <Text className="releases-repository-header-name" size="xs">
                    {/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing */}
                    {repository.name || repository.identifier}
                  </Text>
                )}

                <Tooltip
                  className="releases-repository-header-version-tooltip"
                  withArrow
                  arrowSize={5}
                  label={repository.latestRelease}
                  events={{ hover: repository.latestRelease !== undefined, focus: false, touch: false }}
                >
                  <Text
                    className="releases-repository-header-version"
                    size="xs"
                    fw={700}
                    truncate="end"
                    c={hasError ? "red" : "text"}
                    style={{ flexShrink: 1 }}
                  >
                    {hasError ? t("error.label") : (repository.latestRelease ?? t("not-found"))}
                  </Text>
                </Tooltip>
              </Group>

              <Group className="releases-repository-header-releaseDate-wrapper" gap={5} style={{ flex: "0 0 auto" }}>
                <Text
                  className="releases-repository-header-releaseDate"
                  size="xs"
                  c={
                    repository.viewed
                      ? "green"
                      : repository.isNewRelease
                        ? "primaryColor"
                        : repository.isStaleRelease
                          ? "secondaryColor"
                          : "dimmed"
                  }
                >
                  {repository.latestReleaseAt &&
                    !hasError &&
                    formatter.relativeTime(repository.latestReleaseAt, {
                      now,
                      style: "narrow",
                    })}
                </Text>
                {hasError ? (
                  <IconTriangleFilled
                    className="releases-repository-header-releaseDate-icon releases-repository-header-releaseDate-error"
                    size={10}
                    color="var(--mantine-color-red-filled)"
                  />
                ) : repository.viewed ? (
                  <IconCheck
                    className="releases-repository-header-releaseDate-icon releases-repository-header-releaseDate-confirmed"
                    size={10}
                    color="green"
                  />
                ) : (
                  (repository.isNewRelease || repository.isStaleRelease) && (
                    <IconCircleFilled
                      className="releases-repository-header-releaseDate-icon releases-repository-header-releaseDate-marker"
                      size={10}
                      color={
                        repository.isNewRelease
                          ? "var(--mantine-color-primaryColor-filled)"
                          : "var(--mantine-color-secondaryColor-filled)"
                      }
                    />
                  )
                )}
              </Group>
            </Group>
            {options.showDetails && (
              <DetailsDisplay repository={repository} toggleExpandedDisplay={toggleExpandedDisplay} />
            )}
            {isActive && (
              <ExpandedDisplay
                repository={repository}
                hasIconColor={hasIconColor}
                markReleaseViewed={markReleaseViewed}
                toggleExpandedDisplay={toggleExpandedDisplay}
              />
            )}
            <Divider className="releases-repository-divider" />
          </Stack>
        );
      })}
    </Stack>
  );
}

interface DetailsDisplayProps {
  repository: ReleasesRepositoryResponse;
  toggleExpandedDisplay: (repository: ReleasesRepositoryResponse) => void;
}

const DetailsDisplay = ({ repository, toggleExpandedDisplay }: DetailsDisplayProps) => {
  const t = useScopedI18n("widget.releases");
  const formatter = useFormatter();

  return (
    <>
      <Divider className="releases-repository-details-divider" onClick={() => toggleExpandedDisplay(repository)} />
      <Group
        className={combineClasses("releases-repository-details", classes.releasesRepositoryDetails)}
        justify="space-between"
        p={5}
        onClick={() => toggleExpandedDisplay(repository)}
      >
        <Group className="releases-repository-details-icon-wrapper">
          <Tooltip
            className={combineClasses(
              "releases-repository-details-icon-tooltip",
              "releases-repository-details-icon-preRelease-tooltip",
            )}
            label={t("pre-release")}
            withArrow
            arrowSize={5}
          >
            <IconProgressCheck
              className={combineClasses(
                "releases-repository-details-icon",
                "releases-repository-details-icon-preRelease",
              )}
              size={13}
              color={
                repository.isPreRelease ? "var(--mantine-color-secondaryColor-text)" : "var(--mantine-color-dimmed)"
              }
            />
          </Tooltip>

          <Tooltip
            className={combineClasses(
              "releases-repository-details-icon-tooltip",
              "releases-repository-details-icon-archived-tooltip",
            )}
            label={t("archived")}
            withArrow
            arrowSize={5}
          >
            <IconArchive
              className={combineClasses(
                "releases-repository-details-icon",
                "releases-repository-details-icon-archived",
              )}
              size={13}
              color={repository.isArchived ? "var(--mantine-color-secondaryColor-text)" : "var(--mantine-color-dimmed)"}
            />
          </Tooltip>

          <Tooltip
            className={combineClasses(
              "releases-repository-details-icon-tooltip",
              "releases-repository-details-icon-forked-tooltip",
            )}
            label={t("forked")}
            withArrow
            arrowSize={5}
          >
            <IconGitFork
              className={combineClasses("releases-repository-details-icon", "releases-repository-details-icon-forked")}
              size={13}
              color={repository.isFork ? "var(--mantine-color-secondaryColor-text)" : "var(--mantine-color-dimmed)"}
            />
          </Tooltip>
        </Group>
        <Group className="releases-repository-details-stats">
          <Tooltip
            className={combineClasses(
              "releases-repository-details-stats-tooltip",
              "releases-repository-details-stats-stars-tooltip",
            )}
            label={t("starsCount")}
            withArrow
            arrowSize={5}
          >
            <Group
              className={combineClasses(
                "releases-repository-details-stats-wrapper",
                "releases-repository-details-stats-stars-wrapper",
              )}
              gap={5}
            >
              <IconStar
                className={combineClasses(
                  "releases-repository-details-stats-icon",
                  "releases-repository-details-stats-stars-icon",
                )}
                size={12}
                color={!repository.starsCount ? "var(--mantine-color-dimmed)" : "var(--mantine-color-text)"}
              />
              <Text
                className={combineClasses(
                  "releases-repository-details-stats-text",
                  "releases-repository-details-stats-stars-text",
                )}
                size="xs"
                c={!repository.starsCount ? "dimmed" : ""}
              >
                {!repository.starsCount
                  ? "-"
                  : formatter.number(repository.starsCount, {
                      notation: "compact",
                      maximumFractionDigits: 1,
                    })}
              </Text>
            </Group>
          </Tooltip>

          <Tooltip
            className={combineClasses(
              "releases-repository-details-stats-tooltip",
              "releases-repository-details-stats-forks-tooltip",
            )}
            label={t("forksCount")}
            withArrow
            arrowSize={5}
          >
            <Group
              className={combineClasses(
                "releases-repository-details-stats-wrapper",
                "releases-repository-details-stats-forks-wrapper",
              )}
              gap={5}
            >
              <IconGitFork
                className={combineClasses(
                  "releases-repository-details-stats-icon",
                  "releases-repository-details-stats-forks-icon",
                )}
                size={12}
                color={!repository.forksCount ? "var(--mantine-color-dimmed)" : "var(--mantine-color-text)"}
              />
              <Text
                className={combineClasses(
                  "releases-repository-details-stats-text",
                  "releases-repository-details-stats-forks-text",
                )}
                size="xs"
                c={!repository.forksCount ? "dimmed" : ""}
              >
                {!repository.forksCount
                  ? "-"
                  : formatter.number(repository.forksCount, {
                      notation: "compact",
                      maximumFractionDigits: 1,
                    })}
              </Text>
            </Group>
          </Tooltip>

          <Tooltip
            className={combineClasses(
              "releases-repository-details-stats-tooltip",
              "releases-repository-details-stats-issues-tooltip",
            )}
            label={t("issuesCount")}
            withArrow
            arrowSize={5}
          >
            <Group
              className={combineClasses(
                "releases-repository-details-stats-wrapper",
                "releases-repository-details-stats-issues-wrapper",
              )}
              gap={5}
            >
              <IconCircleDot
                className={combineClasses(
                  "releases-repository-details-stats-icon",
                  "releases-repository-details-stats-issues-icon",
                )}
                size={12}
                color={!repository.openIssues ? "var(--mantine-color-dimmed)" : "var(--mantine-color-text)"}
              />
              <Text
                className={combineClasses(
                  "releases-repository-details-stats-text",
                  "releases-repository-details-stats-issues-text",
                )}
                size="xs"
                c={!repository.openIssues ? "dimmed" : ""}
              >
                {!repository.openIssues
                  ? "-"
                  : formatter.number(repository.openIssues, {
                      notation: "compact",
                      maximumFractionDigits: 1,
                    })}
              </Text>
            </Group>
          </Tooltip>
        </Group>
      </Group>
    </>
  );
};

interface ExtendedDisplayProps {
  repository: ReleasesRepositoryResponse;
  hasIconColor: boolean;
  markReleaseViewed: (repository: ReleasesRepositoryResponse) => void;
  toggleExpandedDisplay: (repository: ReleasesRepositoryResponse) => void;
}

const ExpandedDisplay = ({
  repository,
  hasIconColor,
  markReleaseViewed,
  toggleExpandedDisplay,
}: ExtendedDisplayProps) => {
  const t = useScopedI18n("widget.releases");
  const now = useNow();
  const formatter = useFormatter();

  return (
    <>
      <Divider className="releases-repository-expanded-divider" mx={5} />
      <Stack
        className={combineClasses("releases-repository-expanded", classes.releasesRepositoryExpanded)}
        gap="xs"
        p={10}
      >
        <Group className="releases-repository-expanded-header" justify="space-between" align="center" gap="xs">
          <Text className="releases-repository-expanded-header-identifier" size="xs" c="iconColor" ff="monospace">
            {repository.identifier}
          </Text>

          {repository.integration && (
            <Group className="releases-repository-expanded-header-provider-wrapper" gap={5} align="center">
              <MaskedOrNormalImage
                className="releases-repository-expanded-header-provider-icon"
                imageUrl={repository.integration.iconUrl}
                hasColor={hasIconColor}
                style={{
                  width: "1em",
                  aspectRatio: "1/1",
                }}
              />
              <Text
                className="releases-repository-expanded-header-provider-name"
                size="xs"
                c="iconColor"
                ff="monospace"
              >
                {repository.integration.name}
              </Text>
            </Group>
          )}
        </Group>

        {repository.createdAt && (
          <Text className="releases-repository-expanded-createdAt" size="xs" c="dimmed" ff="monospace">
            <Text className="releases-repository-expanded-createdAt-label" span>
              {`${t("created")} | `}
            </Text>
            <Text className="releases-repository-expanded-createdAt-date" span fw={700}>
              {formatter.relativeTime(repository.createdAt, {
                now,
                style: "narrow",
              })}
            </Text>
          </Text>
        )}

        <Divider className="releases-repository-expanded-actions-divider" mx="30%" />

        <Button
          className="releases-repository-expanded-markViewedButton"
          disabled={repository.viewed}
          color="green"
          variant="light"
          onClick={() => {
            markReleaseViewed(repository);
            toggleExpandedDisplay(repository);
          }}
        >
          <Group
            className="releases-repository-expanded-markViewedButton-wrapper"
            gap={5}
            justify="center"
            align="center"
          >
            <IconCheck className="releases-repository-expanded-markViewedButton-icon" size="1.5em" />
            <Text className="releases-repository-expanded-markViewedButton-text">{t("markViewed")}</Text>
          </Group>
        </Button>

        {(repository.releaseUrl ?? repository.projectUrl) && (
          <Button
            className="releases-repository-expanded-openButton"
            variant="light"
            component="a"
            href={repository.releaseUrl ?? repository.projectUrl}
            target="_blank"
            rel="noreferrer"
          >
            <Group className="releases-repository-expanded-openButton-wrapper" gap={5} justify="center" align="center">
              <IconExternalLink className="releases-repository-expanded-openButton-icon" size="1.5em" />
              <Text className="releases-repository-expanded-openButton-text">
                {repository.releaseUrl ? t("openReleasePage") : t("openProjectPage")}
              </Text>
            </Group>
          </Button>
        )}

        {repository.error && (
          <>
            <Divider className="releases-repository-expanded-error-divider" mx="30%" />
            <Title className="releases-repository-expanded-error-title" order={4} ta="center">
              {t("error.label")}
            </Title>
            <Text
              className="releases-repository-expanded-error-text"
              size="xs"
              ff="monospace"
              c="red"
              style={{ whiteSpace: "pre-wrap" }}
            >
              {repository.error.code ? t(`error.messages.${repository.error.code}` as never) : repository.error.message}
            </Text>
          </>
        )}

        {repository.releaseDescription ? (
          <Description title={t("releaseDescription")} description={repository.releaseDescription} />
        ) : (
          <Description title={t("projectDescription")} description={repository.projectDescription ?? null} />
        )}
      </Stack>
    </>
  );
};

interface DescriptionProps {
  title: string;
  description: string | null;
}

const Description = ({ title, description }: DescriptionProps) => {
  if (isNullOrWhitespace(description)) return null;

  return (
    <>
      <Divider className="releases-repository-expanded-description-divider" my={10} mx="30%" />
      <Title className="releases-repository-expanded-description-title" order={4} ta="center">
        {title}
      </Title>
      <Text
        className={combineClasses("releases-repository-expanded-description-text", classes.releasesDescription)}
        component="div"
        size="xs"
        ff="monospace"
      >
        <ReactMarkdown skipHtml>{description}</ReactMarkdown>
      </Text>
    </>
  );
};
