import { msg } from "@lingui/core/macro";
import type { I18n } from "@lingui/core";
import { GroupConfig } from "../types";

function isPowerOfTwo(n: number): boolean {
  return n > 0 && (n & (n - 1)) === 0;
}

function getNextPowerOfTwo(n: number): number {
  if (n <= 0) return 1;
  if (isPowerOfTwo(n)) return n;
  return Math.pow(2, Math.ceil(Math.log2(n)));
}

export interface GroupConfigOption extends GroupConfig {
  groups_with_extra: number;
  knockout_size: number;
  wildcards_needed: number;
  total_group_matches: number;
  description: string;
}

export function calculateGroupConfigurations(i18n: I18n, teamCount: number): GroupConfigOption[] {
  if (teamCount < 4) {
    throw new Error("Need at least 4 teams for group stage");
  }

  const configs: GroupConfigOption[] = [];

  for (let teamsPerGroup = 3; teamsPerGroup <= Math.min(6, teamCount); teamsPerGroup++) {
    const numGroupsFloor = Math.floor(teamCount / teamsPerGroup);
    const numGroupsCeil = Math.ceil(teamCount / teamsPerGroup);

    const groupOptions = new Set([numGroupsFloor, numGroupsCeil]);

    for (const numGroups of groupOptions) {
      if (numGroups < 2) continue;

      const baseTeamsPerGroup = Math.floor(teamCount / numGroups);
      const groupsWithExtra = teamCount % numGroups;

      const minGroupSize = baseTeamsPerGroup;
      const maxGroupSize = baseTeamsPerGroup + (groupsWithExtra > 0 ? 1 : 0);

      if (minGroupSize < 3 || maxGroupSize > 6) continue;

      const matchesGuaranteed = minGroupSize - 1;

      for (let advancePerGroup = 1; advancePerGroup <= Math.min(3, minGroupSize - 1); advancePerGroup++) {
        const teamsAdvancing = numGroups * advancePerGroup;

        if (teamsAdvancing < 4 || teamsAdvancing > 32) continue;

        const knockoutSize = getNextPowerOfTwo(teamsAdvancing);
        const wildcardsNeeded = knockoutSize - teamsAdvancing;

        if (wildcardsNeeded > teamsAdvancing / 2) continue;

        let totalGroupMatches = 0;
        for (let i = 0; i < numGroups; i++) {
          const groupSize = baseTeamsPerGroup + (i < groupsWithExtra ? 1 : 0);
          totalGroupMatches += (groupSize * (groupSize - 1)) / 2;
        }

        const description = generateDescription(i18n, {
          num_groups: numGroups,
          teams_per_group: baseTeamsPerGroup,
          groups_with_extra: groupsWithExtra,
          advance_per_group: advancePerGroup,
          matches_guaranteed: matchesGuaranteed,
          knockout_size: knockoutSize,
          wildcards_needed: wildcardsNeeded,
        });

        configs.push({
          num_groups: numGroups,
          teams_per_group: baseTeamsPerGroup,
          advance_per_group: advancePerGroup,
          matches_guaranteed: matchesGuaranteed,
          seeding_method: "random",
          groups_with_extra: groupsWithExtra,
          knockout_size: knockoutSize,
          wildcards_needed: wildcardsNeeded,
          total_group_matches: totalGroupMatches,
          description,
        });
      }
    }
  }

  const uniqueConfigs = new Map<string, GroupConfigOption>();

  for (const config of configs) {
    const groupSizes: number[] = [];
    for (let i = 0; i < config.num_groups; i++) {
      const size = config.teams_per_group + (i < config.groups_with_extra ? 1 : 0);
      groupSizes.push(size);
    }
    groupSizes.sort((a, b) => b - a);

    const key = `${groupSizes.join(',')}_advance${config.advance_per_group}`;

    if (!uniqueConfigs.has(key)) {
      uniqueConfigs.set(key, config);
    }
  }

  return Array.from(uniqueConfigs.values()).sort((a, b) => {
    if (a.matches_guaranteed !== b.matches_guaranteed) {
      return b.matches_guaranteed - a.matches_guaranteed;
    }
    if (a.wildcards_needed !== b.wildcards_needed) {
      return a.wildcards_needed - b.wildcards_needed;
    }
    if (a.knockout_size !== b.knockout_size) {
      return b.knockout_size - a.knockout_size;
    }
    return b.num_groups - a.num_groups;
  });
}

function generateDescription(i18n: I18n, config: Partial<GroupConfigOption>): string {
  const { num_groups, teams_per_group, groups_with_extra, matches_guaranteed, advance_per_group, knockout_size, wildcards_needed } = config;

  const groupsOf = (n: number, size: number) =>
    i18n._({ ...msg`{n, plural, one {# group} other {# groups}} of {size}`, values: { n, size } });

  let desc = '';

  if (groups_with_extra && groups_with_extra > 0 && teams_per_group) {
    const largerGroupSize = teams_per_group + 1;
    const smallerGroupCount = num_groups! - groups_with_extra;

    if (smallerGroupCount > 0) {
      desc += `${groupsOf(groups_with_extra, largerGroupSize)}, ${groupsOf(smallerGroupCount, teams_per_group)}`;
    } else {
      desc += groupsOf(num_groups!, largerGroupSize);
    }
  } else {
    desc += groupsOf(num_groups!, teams_per_group!);
  }

  desc += ` • ${i18n._({ ...msg`{n, plural, one {# match guaranteed} other {# matches guaranteed}}`, values: { n: matches_guaranteed } })}`;
  desc += ` • ${i18n._({ ...msg`Top {n} advance`, values: { n: advance_per_group } })}`;

  if (wildcards_needed && wildcards_needed > 0) {
    desc += ` + ${i18n._({ ...msg`{n, plural, one {# wildcard} other {# wildcards}}`, values: { n: wildcards_needed } })}`;
  }

  desc += ` → ${i18n._({ ...msg`{size}-team knockout`, values: { size: knockout_size } })}`;

  return desc;
}

export function assignTeamsToGroups(
  teamIds: string[],
  config: GroupConfigOption,
  seed?: number
): string[][] {
  const shuffled = shuffleArray([...teamIds], seed);

  const groups: string[][] = [];
  let teamIndex = 0;

  for (let groupIndex = 0; groupIndex < config.num_groups; groupIndex++) {
    const groupSize = config.teams_per_group + (groupIndex < config.groups_with_extra ? 1 : 0);
    const groupTeams = shuffled.slice(teamIndex, teamIndex + groupSize);
    groups.push(groupTeams);
    teamIndex += groupSize;
  }

  return groups;
}

function shuffleArray<T>(array: T[], seed?: number): T[] {
  const arr = [...array];
  const random = seed !== undefined ? seededRandom(seed) : Math.random;

  for (let i = arr.length - 1; i > 0; i--) {
    const j = Math.floor(random() * (i + 1));
    [arr[i], arr[j]] = [arr[j], arr[i]];
  }

  return arr;
}

function seededRandom(seed: number): () => number {
  let value = seed;
  return () => {
    value = (value * 9301 + 49297) % 233280;
    return value / 233280;
  };
}

export function getGroupName(index: number): string {
  return String.fromCharCode(65 + index);
}
