export const getProjectTypeForUrl = (type, categories) => {
  return getProjectTypeForUrlShorthand(type, categories);
};

export const getProjectTypeForUrlShorthand = (type, categories, overrideTags) => {
  const tags = overrideTags ?? useTags().value;

  if (type === "mod") {
    const isMod = categories.some((category) => {
      return tags.loaderData.modLoaders.includes(category);
    });

    const isPlugin = categories.some((category) => {
      return tags.loaderData.allPluginLoaders.includes(category);
    });

    const isDataPack = categories.some((category) => {
      return tags.loaderData.dataPackLoaders.includes(category);
    });

    if (isDataPack) {
      return "datapack";
    } else if (isPlugin) {
      return "plugin";
    } else if (isMod) {
      return "mod";
    } else {
      return "mod";
    }
  } else {
    return type;
  }
};

export const getProjectLink = (project) => {
  return `/${getProjectTypeForUrl(project.project_type, project.loaders)}/${
    project.slug ? project.slug : project.id
  }`;
};

export const getVersionLink = (project, version) => {
  if (version) {
    return getProjectLink(project) + "/version/" + version.id;
  } else {
    return getProjectLink(project);
  }
};

export const isApproved = (project) => {
  return project && APPROVED_PROJECT_STATUSES.includes(project.status);
};

export const isListed = (project) => {
  return project && LISTED_PROJECT_STATUSES.includes(project.status);
};

export const isUnlisted = (project) => {
  return project && UNLISTED_PROJECT_STATUSES.includes(project.status);
};

export const isPrivate = (project) => {
  return project && PRIVATE_PROJECT_STATUSES.includes(project.status);
};

export const isRejected = (project) => {
  return project && REJECTED_PROJECT_STATUSES.includes(project.status);
};

export const isUnderReview = (project) => {
  return project && UNDER_REVIEW_PROJECT_STATUSES.includes(project.status);
};

export const isDraft = (project) => {
  return project && DRAFT_PROJECT_STATUSES.includes(project.status);
};

export const APPROVED_PROJECT_STATUSES = ["approved", "archived", "unlisted", "private"];
export const LISTED_PROJECT_STATUSES = ["approved", "archived"];
export const UNLISTED_PROJECT_STATUSES = ["unlisted", "withheld"];
export const PRIVATE_PROJECT_STATUSES = ["private", "rejected", "processing"];
export const REJECTED_PROJECT_STATUSES = ["rejected", "withheld"];
export const UNDER_REVIEW_PROJECT_STATUSES = ["processing"];
export const DRAFT_PROJECT_STATUSES = ["draft"];

export function getVersionsToDisplay(project, overrideTags) {
  const tags = overrideTags ?? useTags().value;

  const projectVersions = project.game_versions.slice();
  const allVersions = tags.gameVersions.slice();

  const allSnapshots = allVersions.filter((version) => version.version_type === "snapshot");
  const allReleases = allVersions.filter((version) => version.version_type === "release");
  const allLegacy = allVersions.filter(
    (version) => version.version_type !== "snapshot" && version.version_type !== "release",
  );

  {
    const indices = allVersions.reduce((map, gameVersion, index) => {
      map[gameVersion.version] = index;
      return map;
    }, {});
    projectVersions.sort((a, b) => indices[a] - indices[b]);
  }

  const releaseVersions = projectVersions.filter((projVer) =>
    allReleases.some((gameVer) => gameVer.version === projVer),
  );

  const latestReleaseVersionDate = Date.parse(
    allReleases.find((version) => version.version === releaseVersions[0])?.date,
  );
  const latestSnapshot = projectVersions.find((projVer) =>
    allSnapshots.some(
      (gameVer) =>
        gameVer.version === projVer &&
        (!latestReleaseVersionDate || latestReleaseVersionDate < Date.parse(gameVer.date)),
    ),
  );

  const allReleasesGrouped = groupVersions(
    allReleases.map((release) => release.version),
    false,
  );
  const projectVersionsGrouped = groupVersions(releaseVersions, true);

  const releaseVersionsAsRanges = projectVersionsGrouped.map(({ major, minor }) => {
    if (minor.length === 1) {
      return formatVersion(major, minor[0]);
    }

    if (
      allReleasesGrouped
        .find((x) => x.major === major)
        .minor.every((value, index) => value === minor[index])
    ) {
      return `${major}.x`;
    }

    return `${formatVersion(major, minor[0])}–${formatVersion(major, minor[minor.length - 1])}`;
  });

  const legacyVersionsAsRanges = groupConsecutiveIndices(
    projectVersions.filter((projVer) => allLegacy.some((gameVer) => gameVer.version === projVer)),
    allLegacy,
  );

  let output = [...legacyVersionsAsRanges];

  // show all snapshots if there's no release versions
  if (releaseVersionsAsRanges.length === 0) {
    const snapshotVersionsAsRanges = groupConsecutiveIndices(
      projectVersions.filter((projVer) =>
        allSnapshots.some((gameVer) => gameVer.version === projVer),
      ),
      allSnapshots,
    );
    output = [...snapshotVersionsAsRanges, ...output];
  } else {
    output = [...releaseVersionsAsRanges, ...output];
  }

  if (latestSnapshot) {
    output = [latestSnapshot, ...output];
  }
  return output;
}

const mcVersionRegex = /^([0-9]+.[0-9]+)(.[0-9]+)?$/;

function groupVersions(versions, consecutive = false) {
  return versions
    .slice()
    .reverse()
    .reduce((ranges, version) => {
      const matchesVersion = version.match(mcVersionRegex);

      if (matchesVersion) {
        const majorVersion = matchesVersion[1];
        const minorVersion = matchesVersion[2];
        const minorNumeric = minorVersion ? parseInt(minorVersion.replace(".", "")) : 0;

        let prevInRange;
        if (
          (prevInRange = ranges.find(
            (x) =>
              x.major === majorVersion && (!consecutive || x.minor.at(-1) === minorNumeric - 1),
          ))
        ) {
          prevInRange.minor.push(minorNumeric);
          return ranges;
        }

        return [...ranges, { major: majorVersion, minor: [minorNumeric] }];
      }

      return ranges;
    }, [])
    .reverse();
}

function groupConsecutiveIndices(versions, referenceList) {
  if (!versions || versions.length === 0) {
    return [];
  }

  const referenceMap = new Map();
  referenceList.forEach((item, index) => {
    referenceMap.set(item.version, index);
  });

  const sortedList = versions.slice().sort((a, b) => referenceMap.get(a) - referenceMap.get(b));

  const ranges = [];
  let start = sortedList[0];
  let previous = sortedList[0];

  for (let i = 1; i < sortedList.length; i++) {
    const current = sortedList[i];
    if (referenceMap.get(current) !== referenceMap.get(previous) + 1) {
      ranges.push(validateRange(`${previous}–${start}`));
      start = current;
    }
    previous = current;
  }

  ranges.push(validateRange(`${previous}–${start}`));

  return ranges;
}

function validateRange(range) {
  switch (range) {
    case "rd-132211–b1.8.1":
      return "All legacy versions";
    case "a1.0.4–b1.8.1":
      return "All alpha and beta versions";
    case "a1.0.4–a1.2.6":
      return "All alpha versions";
    case "b1.0–b1.8.1":
      return "All beta versions";
    case "rd-132211–inf20100618":
      return "All pre-alpha versions";
  }
  const splitRange = range.split("–");
  if (splitRange && splitRange[0] === splitRange[1]) {
    return splitRange[0];
  }
  return range;
}

function formatVersion(major, minor) {
  return minor === 0 ? major : `${major}.${minor}`;
}
