import sortBy from "../../sorting/sortBy.js";
import getCandidateCallStatus from '../getCandidateCallStatus';

/**
 * Sort the candidates in a contest in the order that they should show up in
 * a leaderboard-like display, returning a sorted array of candidate IDs. This
 * function sorts candidates using the following criteria, in order:
 *
 * - Call status, with winners first, runoffs second, and no-calls third. Note
 *   that in most typical elections there shouldn't be cases where there are
 *   _both_ winner calls _and_ runoff calls in the same contest, but this sort
 *   handles them both just to be safe.
 *
 * - Vote count in descending order.
 *
 * - Editorial order in ascending order, meaning editorial order is like
 *   a candidate's position in line. If they're first, they're in the front (at
 *   the top). This mirrors the semantics of ballot order.
 *
 * - Ballot order in ascending order. Theoretically all candidates should have
 *   a unique ballot order, so this would usually be the last step in a typical
 *   sort.
 *
 * - Last name alphabetically as a tie-breaker.
 *
 * - Candidate ID alphabetically as a final tie-breaker in case all else fails.
 *
 * This function works for results at either the contest summary level or the
 * county level. Note that calls are only included at the summary level.
 *
 * @param {Object} options
 * @param {ContestSummaryResults|ContestCountyResults} options.contest
 *   The results for the contest whose candidates you want to sort, either at
 *   the summary level or the county level. Note that calls are only included
 *   at the summary level, so that step of the sort has no impact at the county
 *   level.
 * @param {CandidateMetadata[]} options.candidatesMetadata
 *   An array of candidate metadata for the contest. Metadata fields like
 *   ballot order are used to resolve ties and when results tabulation has not
 *   started.
 * @returns {CandidateId[]}
 */
const getCandidatesSortedByResults = function getCandidatesSortedByResults({
  results: resultsObject = {},
  candidatesMetadata = []
}) {
  const candidateMetadataById = Object.fromEntries(candidatesMetadata.map(candidate => [candidate.id, candidate]));
  const {
    results = []
  } = resultsObject;
  const candidateData = results.map(candidate => ({
    id: candidate.id,
    results: candidate,
    metadata: candidateMetadataById[candidate.id]
  }));
  const sortAttributes = [{
    comparison: candidate => {
      const status = getCandidateCallStatus(resultsObject, candidate.id);
      const callStatusValues = {
        winner: 0,
        runoff: 1,
        'no-call': 2
      };
      return callStatusValues[status];
    },
    direction: 'asc'
  }, {
    comparison: 'results.votes',
    direction: 'desc',
    nullFirst: false
  }, {
    comparison: 'metadata.editorialOrder',
    direction: 'asc',
    nullFirst: false
  }, {
    comparison: 'metadata.ballotOrder',
    direction: 'asc',
    nullFirst: false
  }, {
    comparison: 'metadata.shortName',
    direction: 'asc',
    nullFirst: false
  }, {
    comparison: 'id',
    direction: 'asc'
  }];
  return sortBy(candidateData, sortAttributes).map(({
    id
  }) => id);
};
export default getCandidatesSortedByResults;