# Source: app/models/Lineup.model.js

    const { Model } = require('flitter-orm')

    /**
     * Model representing a starting lineup/bench configuration for a
     * given team. These will have copies frozen for each week that progresses.
     * @extends Model
     */
    class Lineup extends Model {
        static get services() {
            return [...super.services, 'models']
        }

        static get schema() {
            return {
                // Associated team ID
                team_id: String,

                // Array of player IDs on the team on the bench
                benched_player_ids: [String],

                // Array of player IDs on the team and their starting positions
                starting_players: [{
                    player_id: String,
                    position: String,
                }],

                // If true, this is the current "draft" of the lineup
                active: { type: Boolean, default: true },

                // If not active, then it was archived for the week with this number
                week_num: Number,
            }
        }

        /**
         * Calculate the fantasy points scored by the starting players on this lineup.
         * @return {Promise<number>}
         */
        async calculate_fantasy_points() {
            const starting_players = await this.players_in_starting()
            let points = 0

            for ( const player of starting_players ) {
                points += (await player.points_for_week(this.week_num))?.fantasy_points || 0
            }

            return points
        }

        /**
         * Given a team, fetch the latest draft lineup for that team.
         * This will also update the lineup record so that it includes all players
         * currently on the team (benching any players not already in the lineup).
         *
         * If no lineup exists, one will be created.
         *
         * @param {Team} team
         * @return {Promise<Lineup>}
         */
        static async get_and_update_for_team(team) {
            let lineup = await this.findOne({ team_id: team.id })

            if ( !lineup ) {
                lineup = new this({
                    team_id: team.id,
                    benched_player_ids: [],
                    starting_players: [],
                    active: true,
                })
            } else if ( !lineup.active ) {
                lineup = new this({
                    team_id: team.id,
                    benched_player_ids: lineup.benched_player_ids,
                    starting_players: lineup.starting_players,
                    active: true,
                })
            }

            // Make sure all players on the team are either on the bench or in the lineup
            const players = await team.players()
            for ( const player of players ) {
                if ( !lineup.has_player(player) ) {
                    lineup.bench_player(player)
                }
            }

            await lineup.save()
            return lineup
        }

        /**
         * Returns an array of Players that are on the bench for this lineup.
         * @return {Promise<Array<Model>>}
         */
        async players_on_bench() {
            const Player = this.models.get('Player')
            return Player.find({
                _id: {
                    $in: this.benched_player_ids.map(x => Player.to_object_id(x)),
                },
            })
        }

        /**
         * Returns an array of Players that are on the starting lineup.
         * @return {Promise<Array<Model>>}
         */
        async players_in_starting() {
            const Player = this.models.get('Player')
            return Player.find({
                _id: {
                    $in: this.starting_players.map(x => Player.to_object_id(x.player_id)),
                },
            })
        }

        /**
         * Returns true if the given player is on the bench in this lineup.
         * @param {Player} player
         * @return {boolean}
         */
        has_bench_player(player) {
            return this.benched_player_ids.includes(player.id)
        }

        /**
         * Returns true if the given player is on the starting lineup in this lineup.
         * @param {Player} player
         * @return {boolean}
         */
        has_starting_player(player) {
            return !!this.starting_players.find(x => x.player_id === player.id)
        }

        /**
         * Returns true if the given player is in this lineup anywhere.
         * @param {Player} player
         * @return {boolean}
         */
        has_player(player) {
            return this.has_bench_player(player) || this.has_starting_player(player)
        }

        /**
         * Removes the player from the starting lineup if they are there and
         * adds them to the bench if they aren't already there.
         * @param {Player} player
         */
        bench_player(player) {
            this.starting_players = this.starting_players.filter(x => x.player_id !== player.id)
            if ( !this.benched_player_ids.includes(player.id) ) {
                this.benched_player_ids.push(player.id)
            }
        }

        /**
         * Given the player_id/position record, add it to the starting lineup.
         * @param {object} player_position_record
         */
        start_player(player_position_record) {
            this.benched_player_ids = this.benched_player_ids.filter(x => x !== player_position_record.player_id)
            this.starting_players = this.starting_players.filter(x => x.player_id !== player_position_record.player_id)
            this.starting_players.push(player_position_record)
        }

        /**
         * Remove all players from the bench and the starting lineup.
         */
        clear_lineup() {
            this.starting_players = []
            this.benched_player_ids = []
        }

        /**
         * Cast the lineup to an object which can be returned via the API.
         * @return {Promise<object>}
         */
        async to_api() {
            // positions to guarantee are in the line-up
            let lineup_positions = ['QB', 'RB', 'RB', 'WR', 'WR', 'TE', 'FLX', 'DST']

            const data = {
                team_id: this.team_id,
                active: this.active,
                week_num: this.week_num,
            }

            // build the starting players data
            const starting_players = await this.players_in_starting()
            const build_starting_players = []
            for ( const player of this.starting_players ) {
                // Find the player instance and cast it to an API object
                const player_inst = starting_players.find(x => x.id === player.player_id)
                build_starting_players.push({
                    ...(await player_inst.to_api(true)),
                    position: player.position
                })

                // remove the position from the array of positions to back-fill
                let found_one = false
                lineup_positions = lineup_positions.filter(x => {
                    if ( !found_one && x === player.position ) {
                        found_one = true
                        return false
                    }

                    return true
                })
            }

            // Fill in any missing positions into the data
            for ( const position of lineup_positions ) {
                build_starting_players.push ({ position })
            }

            // build the benched players data
            const bench_players = await this.players_on_bench()
            const build_benched_players = []
            for ( const player of bench_players ) {
                // Cast the starting player to an API object
                const obj = await player.to_api(true)
                obj.position = 'B'
                build_benched_players.push(obj)
            }

            // If there are no players on the bench, add a placeholder slot.
            if ( build_benched_players.length < 1 ) {
                build_benched_players.push({ position: 'B' })
            }

            data.starting_players = build_starting_players
            data.benched_players = build_benched_players
            return data
        }
    }

    module.exports = exports = Lineup