app/src/services/LineupService.tstypescript
import { debug } from "@/utils/logging";
import ax from "./RequestService";
import type { Lineup, Member } from "@/types";

export interface PositionProposal {
  MemberId: string;
  Member: Member;
  x: number;
  y: number;
}

/**
 * Service for managing lineups in a choreography.
 * @class LineupService
 */
class LineupService {
  /**
   * Create a new lineup.
   * @param {number} startCount - Start count for the lineup
   * @param {number} endCount - End count for the lineup
   * @param {string} choreoId - Choreography ID
   * @returns {Promise<Object>} Created lineup
   */
  create(
    startCount: number,
    endCount: number,
    choreoId: string
  ): Promise<Lineup> {
    debug("Creating a new lineup", { startCount, endCount, choreoId });
    return ax
      .post(`/lineup`, { startCount, endCount, choreoId })
      .then((res) => {
        debug("Successfully created new lineup");
        return res.data;
      });
  }

  /**
   * Update a lineup by its ID.
   * @param {string} id - Lineup ID
   * @param {Object} data - Data to update
   * @returns {Promise<Object>} Updated lineup
   */
  update(id: string, data: Partial<Lineup>): Promise<Lineup> {
    debug("Updating lineup", { id, data });
    return ax.put(`/lineup/${id}`, data).then((res) => {
      debug("Successfully updated lineup");
      return res.data;
    });
  }

  /**
   * Remove a lineup by its ID.
   * @param {string} id - Lineup ID
   * @returns {Promise<Object>} Response data
   */
  remove(id: string): Promise<void> {
    debug("Deleting lineup", id);
    return ax.delete(`/lineup/${id}`).then((res) => {
      debug("Successfully deleted lineup");
      return res.data;
    });
  }

  filterRejectedProposals(
    proposals: PositionProposal[],
    rejectedPositionProposals: PositionProposal[][]
  ): PositionProposal[] {
    function isProposalNotRejected(proposal: PositionProposal[]) {
      const alreadyRejected = rejectedPositionProposals.some(
        (rejectedProposal: PositionProposal[]) => {
          return proposal.every((p: PositionProposal) =>
            rejectedProposal.some(
              (rp: PositionProposal) =>
                Math.abs(rp.x - p.x) < 1 && Math.abs(rp.y - p.y) < 1
            )
          );
        }
      );

      return !alreadyRejected;
    }

    if (!proposals || proposals.length === 0) return [];
    if (Array.isArray(proposals[0]))
      return (proposals as unknown as PositionProposal[][]).filter(
        (proposal: PositionProposal[]) => isProposalNotRejected(proposal)
      ) as unknown as PositionProposal[];
    else return isProposalNotRejected(proposals) ? proposals : [];
  }

  getLinesForNumberOfMembers(n: number): number[] | undefined {
    switch (n) {
      case 8:
        return [3, 2, 3];
      case 9:
        return [5, 4];
      case 10:
        return [4, 3, 2, 1];
      case 11:
        return [6, 5];
      case 12:
        return [5, 4, 3];
      case 13:
        return [4, 5, 4];
      case 14:
        return [5, 4, 3, 2];
      case 15:
        return [5, 4, 3, 2, 1];
      case 16:
        return [5, 6, 5];
      case 17:
        return [6, 5, 6];
      case 18:
        return [6, 5, 4, 3];
      case 19:
        return [4, 5, 4, 3, 2, 1];
      case 20:
        return [6, 5, 4, 3, 2];
      case 21:
        return [6, 5, 4, 3, 2, 1];
      case 22:
        return [7, 6, 5, 4];
      case 23:
        return [5, 4, 5, 4, 5];
      case 24:
        return [4, 5, 6, 5, 4];
      case 25:
        return [7, 6, 5, 4, 3];
    }
  }

  proposeLineup(
    teamMembers: Member[],
    rejectedPositionProposals: PositionProposal[][]
  ): PositionProposal[] {
    function createLinePositions(members: Member[], lineNumber = 0) {
      const spacing = 100 / 7;
      const ySpacing = spacing / 2;
      let leftPadding = 50 + spacing / 2 - (spacing * members.length) / 2;
      return members.map((member, index) => ({
        MemberId: member.id,
        Member: member,
        x: leftPadding + index * spacing,
        y: 40 + lineNumber * ySpacing,
      }));
    }

    if (teamMembers.length === 0 || teamMembers.length === 1) {
      return [];
    } else if (teamMembers.length > 0 && teamMembers.length <= 7) {
      return this.filterRejectedProposals(
        createLinePositions(teamMembers),
        rejectedPositionProposals
      );
    } else if (teamMembers.length > 7 && teamMembers.length <= 25) {
      let lines = this.getLinesForNumberOfMembers(teamMembers.length);
      if (
        lines!.reduce((partialSum: number, a: number) => partialSum + a, 0) !=
        teamMembers.length
      ) {
        throw Error(
          "Sum of participants per line must equal the total number of participants"
        );
      }

      const linePositions = lines!.map(
        (lineLength: number, lineNumber: number) => {
          const startIndex = lines!
            .slice(0, lineNumber)
            .reduce((partialSum: number, a: number) => partialSum + a, 0);

          return createLinePositions(
            teamMembers.slice(startIndex, startIndex + lineLength),
            lineNumber
          );
        }
      );
      return this.filterRejectedProposals(
        linePositions.flat(),
        rejectedPositionProposals
      );
    }
    return [];
  }
}

export default new LineupService();