/*!
 * Copyright (c) 2024 Eclipse Foundation, Inc.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 */

import { WorkingGroup, InterestGroup, Project, ProjectProposal } from '../../types/models';

/**
  * Converts a string to a boolean.
  *
  * @param value - The string to be converted.
  * @returns A boolean.
  * @throws TypeError
  *
  * @example
  * stringToBoolean('true') // true
  * stringToBoolean('false') // false
  * stringToBoolean('apple') // TypeError
  */ 
export const stringToBoolean = (value: string): boolean => {
  switch (value.toLowerCase()) {
    case 'true':
      return true;
    case 'false':
      return false;
    default:
      throw new TypeError(`${value} could not be converted to a boolean.`);
  }
}

/**
  * Get the long name of a month from a given date.
  * 
  * @param date - The date.
  * @returns The long name of the month. 
  */
export const getMonthName = (date: Date) => {
  const locale = 'en-uk';
  return date.toLocaleString(locale, { month: 'long' });
}

/**
  * Repeatedly calls a function until it returns a specific amount of unique values.
  *
  * This is useful for functions which select a random item. You can repeat the
  * random selection N amount of times and ensure the results aren't duplicated.
  * 
  * @param callbackFn - The function to repeatedly call.
  * @param count - The number of results expected.
  * @returns An array of unique results without duplicates.
  *
  */
export const repeatSelectUnique = <Fn extends (...args: any[]) => any>(callbackFn: Fn, count: number): Array<ReturnType<Fn>> => {
  const selections = new Set<ReturnType<Fn>>();

  while (selections.size < count) {
    selections.add(callbackFn());
  }

  return Array.from(selections);
}


/**
  * Returns a boolean depending on whether the argument is a working group or
  * not. This is used for type-narrowing.
  * 
  * @param value - The object to check whether it is a working group or not.
  * @returns A boolean
  *
  * @example
  * let workingGroupCount = 0;
  * if (isWorkingGroup(collaboration)) {
  *   workingGroupCount++;
  * }
  */
export const isWorkingGroup = (value: object): value is WorkingGroup => {
  return (
    'alias' in value 
    && 'title' in value 
    && 'status' in value 
    && 'description' in value)
};

/**
  * Returns a boolean depending on whether the argument is an interest group or
  * not. This is used for type-narrowing.
  * 
  * @param value - The object to check whether it is an interest group or not.
  * @returns A boolean
  *
  * @example
  * let interestGroupCount = 0;
  * if (isInterestGroup(collaboration)) {
  *   interestGroupCount++;
  * }
  */
export const isInterestGroup = (value: object): value is InterestGroup => {
  return (
    'id' in value
    && 'shortProjectId' in value
    && 'projectId' in value
    && 'state' in value
  );
};

/**
  * Returns the type of collaboration that was given. This is used for type-narrowing.
  *
  * @param collaboration - The collaboration object to type-narrow.
  * @returns A string which represents the narrowed collaboration type.
  *
  * @example
  * const collaborationType = getCollaborationType(collaboration);
  * switch (collaborationType) {
  *   case 'interest-group':
  *     interestGroupCount++;    
  *     break;
  *   case 'working-group':
  *     workingGroupCount++;
  *     break;
  *   default:
  *     throw new Error('Object was neither an interest group nor a working group.');
  * }
  */
export const getCollaborationType = (collaboration: WorkingGroup | InterestGroup): 'interest-group' | 'working-group' | 'invalid' => {
  if (isWorkingGroup(collaboration)) {
    return 'working-group';
  }

  if (isInterestGroup(collaboration)) {
    return 'interest-group'
  }

  return 'invalid';
};


/** 
  * Converts a string to title case.
  * 
  * @param str - The string to convert.
  * 
  * @returns A title-cased string.
  */
export const toTitleCase = (str: string): string => {
  return str.split(' ')
    .map((word) => word[0].toUpperCase() + word.substring(1).toLowerCase())
    .join(' ');
}

/**
  * Returns a boolean depending on whether the argument is a project or not.
  * This is used for type-narrowing.
  * 
  * @param value - The object to check whether it is a project or not.
  * @returns A boolean
  *
  * @example
  * let projectCount = 0;
  * if (isProject(obj)) {
  *   projectCount++;
  * }
  */
export const isProject = (value: object): value is Project => {
  return (
    'logo' in value
    && 'slsa' in value
    && 'releases' in value
    && 'tags' in value
  );
};

/**
  * Returns a boolean depending on whether the argument is a project proposal
  * or not. This is used for type-narrowing.
  * 
  * @param value - The object to check whether it is a project proposal or not.
  * @returns A boolean
  *
  * @example
  * let proposalCount = 0;
  * if (isProjectProposal(obj)) {
  *   proposalCount++;
  * }
  */
export const isProjectProposal = (value: object): value is ProjectProposal => {
  if (!value) return false;

  return (
    'mentors' in value
    && 'licenses' in value
    && 'repos' in value
  );
};

