import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; import { SharedStateService } from '../../service/shared-state.service'; export interface SelectionData { label: string; numbers: (number | string)[]; value: number; total: number; isBoxed?: boolean; } @Injectable({ providedIn: 'root' }) export class SelectionService { private selections = new BehaviorSubject([]); selections$ = this.selections.asObservable(); private currentRow = new BehaviorSubject({ label: '', numbers: [], value: 0, total: 0, isBoxed: false }); currentRow$ = this.currentRow.asObservable(); private runnerCount: number = 12; // Fallback constructor(private sharedStateService: SharedStateService) { this.sharedStateService.runnerCount$.subscribe(count => { this.runnerCount = count || 12; // Update runner count dynamically }); } updatePartial(update: Partial) { const current = this.currentRow.value; const updated: SelectionData = { ...current, ...update }; const label = updated.label; const value = updated.value; const numbers = updated.numbers || []; const isBoxed = updated.isBoxed ?? false; // Enforce ticket number limit (1-100) if (value > 100) { updated.value = 100; } updated.total = 0; if ((numbers.length > 0 || numbers.includes('F')) && value > 0 && label) { switch (label) { case 'WIN': case 'SHP': case 'THP': case 'PLC': case 'SHW': case 'WSP': { if (numbers.includes('F')) { updated.total = this.runnerCount * value * 10; } else { updated.total = numbers.filter(n => typeof n === 'number').length * value * 10; } break; } case 'FOR': case 'EXA': { const dashIndex = numbers.indexOf('-'); const group1 = dashIndex === -1 ? numbers : numbers.slice(0, dashIndex); const group2 = dashIndex === -1 ? [] : numbers.slice(dashIndex + 1); const group1Count = group1.includes('F') ? this.runnerCount : group1.filter(n => typeof n === 'number').length; const group2Count = group2.includes('F') ? this.runnerCount : group2.filter(n => typeof n === 'number').length; let combinations = 0; if (isBoxed) { const allNums = [...group1, ...group2].filter(n => typeof n === 'number') as number[]; const uniqueCount = group1.includes('F') || group2.includes('F') ? this.runnerCount : new Set(allNums).size; combinations = uniqueCount >= 2 ? this.calculatePermutations(uniqueCount) : 0; updated.numbers = [...group1, ...(group2.length ? ['-', ...group2] : [])]; } else { combinations = group1Count * group2Count; updated.numbers = [...group1, ...(group2.length ? ['-', ...group2] : [])]; } updated.total = combinations * value * 10; break; } case 'QUI': { const dashIndex = numbers.indexOf('-'); const group1 = dashIndex === -1 ? numbers : numbers.slice(0, dashIndex); const group2 = dashIndex === -1 ? [] : numbers.slice(dashIndex + 1); const group1Count = group1.includes('F') ? this.runnerCount : group1.filter(n => typeof n === 'number').length; const group2Count = group2.includes('F') ? this.runnerCount : group2.filter(n => typeof n === 'number').length; let combinations = 0; if (isBoxed) { const allNums = [...group1, ...group2].filter(n => typeof n === 'number') as number[]; const uniqueCount = group1.includes('F') || group2.includes('F') ? this.runnerCount : new Set(allNums).size; combinations = uniqueCount >= 2 ? this.calculateCombinations(uniqueCount) : 0; updated.numbers = [...group1, ...(group2.length ? ['-', ...group2] : [])]; } else { combinations = (group1Count * group2Count) ; updated.numbers = [...group1, ...(group2.length ? ['-', ...group2] : [])]; } updated.total = combinations * value * 10; break; } case 'TAN': { const dashIndices = numbers .map((n, idx) => (n === '-' ? idx : -1)) .filter(idx => idx !== -1); if (dashIndices.length < 2 && !isBoxed) break; const group1 = dashIndices.length > 0 ? numbers.slice(0, dashIndices[0]) : numbers; const group2 = dashIndices.length > 0 ? numbers.slice(dashIndices[0] + 1, dashIndices[1] || numbers.length) : []; const group3 = dashIndices.length > 1 ? numbers.slice(dashIndices[1] + 1) : []; let combinations = 0; if (isBoxed) { if (numbers.includes('F')) { combinations = this.calculatePermutationsN(this.runnerCount, 3); } else { const allNums = [...group1, ...group2, ...group3].filter(n => typeof n === 'number') as number[]; combinations = allNums.length >= 3 ? this.calculatePermutationsN(allNums.length, 3) : 0; } } else { const group1Count = group1.includes('F') ? this.runnerCount : group1.filter(n => typeof n === 'number').length; const group2Count = group2.includes('F') ? this.runnerCount : group2.filter(n => typeof n === 'number').length; const group3Count = group3.includes('F') ? this.runnerCount : group3.filter(n => typeof n === 'number').length; combinations = group1Count * group2Count * group3Count; } updated.numbers = [...group1, ...(group2.length ? ['-', ...group2] : []), ...(group3.length ? ['-', ...group3] : [])]; updated.total = combinations * value * 10; break; } case 'TRE': case 'MJP': case 'JKP': { const legs = this.splitToLegs(numbers, this.getLegCount(label)); const requiredLegs = this.getLegCount(label); const legCounts = legs.map(leg => leg.includes('F') ? this.runnerCount : leg.filter(n => typeof n === 'number').length); const filledLegs = legs.filter(leg => leg.length > 0).length; if (filledLegs >= requiredLegs - 1) { const combinations = legCounts.reduce((acc, count) => acc * (count || 1), 1); updated.total = combinations * value * 10; } break; } // ⚪ BOX logic default: { const combCount = isBoxed ? this.calculatePermutations(numbers.filter(n => typeof n === 'number').length) : this.calculateCombinations(numbers.filter(n => typeof n === 'number').length); updated.total = combCount * value * 10; break; } } } this.currentRow.next(updated); } finalizeCurrentRow() { const completed = this.currentRow.value; if (!completed.label || completed.numbers.length === 0 || completed.value <= 0) return; const currentSelections = this.selections.value; const totalAmount = currentSelections.reduce((sum, sel) => sum + sel.total, 0); if (totalAmount + completed.total > 5000) return; this.selections.next([...currentSelections, { ...completed }]); this.currentRow.next({ label: '', numbers: [], value: 0, total: 0, isBoxed: false }); } clearSelections() { this.selections.next([]); this.currentRow.next({ label: '', numbers: [], value: 0, total: 0, isBoxed: false }); } private calculateCombinations(n: number): number { return n >= 2 ? (n * (n - 1)) / 2 : 0; } private calculatePermutations(n: number): number { return n >= 2 ? n * (n - 1) : 0; } private calculatePermutationsN(n: number, r: number): number { if (n < r) return 0; let result = 1; for (let i = 0; i < r; i++) { result *= (n - i); } return result; } private splitToLegs(numbers: (number | string)[], legCount: number): (number | string)[][] { const result: (number | string)[][] = []; let currentLeg: (number | string)[] = []; let separatorCount = 0; for (const item of numbers) { if (item === '/') { if (currentLeg.length > 0) { result.push([...currentLeg]); currentLeg = []; separatorCount++; } if (separatorCount >= legCount - 1) break; } else { currentLeg.push(item); } } // Push the last leg if it has numbers if (currentLeg.length > 0) { result.push([...currentLeg]); } // Fill remaining legs with empty arrays if needed while (result.length < legCount) { result.push([]); } return result.slice(0, legCount); } private getLegCount(label: string): number { switch (label) { case 'TRE': return 3; case 'MJP': return 4; case 'JKP': return 5; default: return 3; } } }