import { database } from "~/database/context"; import * as schema from "~/database/schema"; import { eq, and, inArray, asc } from "drizzle-orm"; import { getScoringRules, calculateFantasyPoints, calculateBracketPoints } from "./scoring-rules"; export async function createDraftPick(data: { seasonId: string; teamId: string; participantId: string; pickNumber: number; round: number; pickInRound: number; pickedByUserId: string; pickedByType: "owner" | "commissioner" | "auto"; timeUsed: number; }) { const db = database(); const [pick] = await db.insert(schema.draftPicks).values(data).returning(); return pick; } export async function getDraftPicks(seasonId: string) { const db = database(); return await db .select() .from(schema.draftPicks) .where(eq(schema.draftPicks.seasonId, seasonId)) .orderBy(schema.draftPicks.pickNumber); } export async function getDraftPickByNumber(seasonId: string, pickNumber: number) { const db = database(); const [pick] = await db .select() .from(schema.draftPicks) .where( and( eq(schema.draftPicks.seasonId, seasonId), eq(schema.draftPicks.pickNumber, pickNumber) ) ); return pick; } export async function getTeamDraftPicks(teamId: string) { const db = database(); return await db .select() .from(schema.draftPicks) .where(eq(schema.draftPicks.teamId, teamId)) .orderBy(schema.draftPicks.pickNumber); } export async function isParticipantDrafted(seasonId: string, participantId: string, providedDb?: ReturnType) { const db = providedDb || database(); const [pick] = await db .select() .from(schema.draftPicks) .where( and( eq(schema.draftPicks.seasonId, seasonId), eq(schema.draftPicks.participantId, participantId) ) ); return !!pick; } /** * Get a team's drafted participants grouped by sports season. * Returns a map of sportsSeasonId → [{id, name}]. */ export async function getDraftedParticipantsBySportsSeason( teamId: string, seasonId: string, providedDb?: ReturnType ): Promise>> { const db = providedDb || database(); const results = await db .select({ sportsSeasonId: schema.participants.sportsSeasonId, participantId: schema.participants.id, participantName: schema.participants.name, }) .from(schema.draftPicks) .innerJoin( schema.participants, eq(schema.draftPicks.participantId, schema.participants.id) ) .where( and( eq(schema.draftPicks.teamId, teamId), eq(schema.draftPicks.seasonId, seasonId) ) ); const map = new Map>(); for (const row of results) { if (!map.has(row.sportsSeasonId)) { map.set(row.sportsSeasonId, []); } map.get(row.sportsSeasonId)?.push({ id: row.participantId, name: row.participantName }); } return map; } export interface DraftedParticipantWithPoints { id: string; name: string; /** Fantasy league points earned (position → scoring rules). null = no result yet. */ earnedPoints: number | null; /** Accumulated qualifying points for qualifying_points sports. null for other sports. */ currentQP: number | null; } /** * Get a team's drafted participants with their current earned points, grouped by * sports season. Used for the league home summary card. * * - playoff_bracket / season_standings: earnedPoints = finalPosition → scoring rules * - qualifying_points (active): currentQP = accumulated QP from participantQualifyingTotals * - qualifying_points (finalized): earnedPoints = finalPosition → scoring rules (participantResults written) * - No EV / projected points — actual earned only. */ export async function getDraftedParticipantsWithPoints( teamId: string, seasonId: string, providedDb?: ReturnType ): Promise> { const db = providedDb || database(); const scoringRules = await getScoringRules(seasonId, db); if (!scoringRules) return new Map(); // One query: picks → participant → sportsSeason (scoringPattern) + results (position) const picks = await db.query.draftPicks.findMany({ where: and( eq(schema.draftPicks.teamId, teamId), eq(schema.draftPicks.seasonId, seasonId) ), columns: {}, with: { participant: { columns: { id: true, name: true, sportsSeasonId: true }, with: { sportsSeason: { columns: { id: true, scoringPattern: true } }, results: { columns: { finalPosition: true }, limit: 1 }, }, }, }, }); // Collect unique sportsSeasonIds per scoring pattern const bracketSeasonIds = new Set(); const qpSeasonIds = new Set(); const qpParticipantIds = new Set(); for (const pick of picks) { const pattern = pick.participant.sportsSeason.scoringPattern; const ssId = pick.participant.sportsSeasonId; if (pattern === "playoff_bracket") bracketSeasonIds.add(ssId); if (pattern === "qualifying_points") { qpSeasonIds.add(ssId); qpParticipantIds.add(pick.participant.id); } } // Batch-fetch bracket template IDs (one per sports season) const bracketTemplateMap = new Map(); if (bracketSeasonIds.size > 0) { const events = await db.query.scoringEvents.findMany({ where: inArray(schema.scoringEvents.sportsSeasonId, [...bracketSeasonIds]), columns: { sportsSeasonId: true, bracketTemplateId: true }, }); for (const ev of events) { if (!bracketTemplateMap.has(ev.sportsSeasonId)) { bracketTemplateMap.set(ev.sportsSeasonId, ev.bracketTemplateId ?? null); } } } // Batch-fetch QP totals for qualifying_points participants const qpMap = new Map(); // participantId → totalQP if (qpParticipantIds.size > 0 && qpSeasonIds.size > 0) { const totals = await db.query.participantQualifyingTotals.findMany({ where: and( inArray(schema.participantQualifyingTotals.participantId, [...qpParticipantIds]), inArray(schema.participantQualifyingTotals.sportsSeasonId, [...qpSeasonIds]) ), columns: { participantId: true, totalQualifyingPoints: true }, }); for (const row of totals) { qpMap.set(row.participantId, parseFloat(row.totalQualifyingPoints)); } } // Assemble result grouped by sportsSeasonId const result = new Map(); for (const pick of picks) { const { id, name, sportsSeasonId } = pick.participant; const pattern = pick.participant.sportsSeason.scoringPattern; const resultRow = pick.participant.results[0] ?? null; let earnedPoints: number | null = null; let currentQP: number | null = null; if (pattern === "qualifying_points" && (resultRow?.finalPosition === null || resultRow?.finalPosition === undefined)) { // Active QP season: show accumulated qualifying points currentQP = qpMap.get(id) ?? null; } else if (resultRow?.finalPosition !== null && resultRow?.finalPosition !== undefined) { // Finalized result for any pattern (including finalized QP seasons) earnedPoints = pattern === "playoff_bracket" ? calculateBracketPoints( resultRow.finalPosition, scoringRules, bracketTemplateMap.get(sportsSeasonId) ?? null ) : calculateFantasyPoints(resultRow.finalPosition, scoringRules); } const arr = result.get(sportsSeasonId) ?? []; result.set(sportsSeasonId, arr); arr.push({ id, name, earnedPoints, currentQP }); } return result; } export async function deleteAllDraftPicks(seasonId: string) { const db = database(); await db .delete(schema.draftPicks) .where(eq(schema.draftPicks.seasonId, seasonId)); } /** * Get all draft picks for a season with participant and sport information * Used for draft eligibility calculations */ export async function getDraftPicksWithSports(seasonId: string, providedDb?: ReturnType) { const db = providedDb || database(); const results = await db .select({ id: schema.draftPicks.id, teamId: schema.draftPicks.teamId, pickNumber: schema.draftPicks.pickNumber, participantId: schema.participants.id, participantName: schema.participants.name, sportId: schema.sports.id, sportName: schema.sports.name, }) .from(schema.draftPicks) .innerJoin( schema.participants, eq(schema.draftPicks.participantId, schema.participants.id) ) .innerJoin( schema.sportsSeasons, eq(schema.participants.sportsSeasonId, schema.sportsSeasons.id) ) .innerJoin( schema.sports, eq(schema.sportsSeasons.sportId, schema.sports.id) ) .where(eq(schema.draftPicks.seasonId, seasonId)) .orderBy(schema.draftPicks.pickNumber); // Transform to expected format return results.map((r) => ({ teamId: r.teamId, participant: { id: r.participantId, sport: { id: r.sportId, name: r.sportName, }, }, })); } /** * Get team's draft picks with participant and sport information */ export async function getTeamDraftPicksWithSports(teamId: string, seasonId: string, providedDb?: ReturnType) { const db = providedDb || database(); const results = await db .select({ id: schema.draftPicks.id, teamId: schema.draftPicks.teamId, pickNumber: schema.draftPicks.pickNumber, participantId: schema.participants.id, participantName: schema.participants.name, sportId: schema.sports.id, sportName: schema.sports.name, }) .from(schema.draftPicks) .innerJoin( schema.participants, eq(schema.draftPicks.participantId, schema.participants.id) ) .innerJoin( schema.sportsSeasons, eq(schema.participants.sportsSeasonId, schema.sportsSeasons.id) ) .innerJoin( schema.sports, eq(schema.sportsSeasons.sportId, schema.sports.id) ) .where( and( eq(schema.draftPicks.teamId, teamId), eq(schema.draftPicks.seasonId, seasonId) ) ) .orderBy(schema.draftPicks.pickNumber); // Transform to expected format return results.map((r) => ({ teamId: r.teamId, participant: { id: r.participantId, sport: { id: r.sportId, name: r.sportName, }, }, })); } export async function getDraftPicksForSeason(seasonId: string) { const db = database(); return await db .select({ id: schema.draftPicks.id, pickNumber: schema.draftPicks.pickNumber, round: schema.draftPicks.round, pickInRound: schema.draftPicks.pickInRound, timeUsed: schema.draftPicks.timeUsed, team: schema.teams, participant: schema.participants, sport: schema.sports, }) .from(schema.draftPicks) .innerJoin(schema.teams, eq(schema.draftPicks.teamId, schema.teams.id)) .innerJoin(schema.participants, eq(schema.draftPicks.participantId, schema.participants.id)) .innerJoin(schema.sportsSeasons, eq(schema.participants.sportsSeasonId, schema.sportsSeasons.id)) .innerJoin(schema.sports, eq(schema.sportsSeasons.sportId, schema.sports.id)) .where(eq(schema.draftPicks.seasonId, seasonId)) .orderBy(asc(schema.draftPicks.pickNumber)); }