import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; 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(); 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 || []).filter(n => typeof n === 'number') as number[]; const isBoxed = updated.isBoxed ?? false; updated.total = 0; if (numbers.length > 0 && value > 0 && label) { switch (label) { case 'WIN': case 'SHP': case 'THP': case 'PLC': updated.total = numbers.length * value * 10; break; case 'FOR': { const dashIndex = updated.numbers.indexOf('-'); const group1 = updated.numbers.slice(0, dashIndex).filter(n => typeof n === 'number') as number[]; const group2 = updated.numbers.slice(dashIndex + 1).filter(n => typeof n === 'number') as number[]; let combinations = 0; if (isBoxed) { const uniq = Array.from(new Set([...group1, ...group2])); combinations = this.calculatePermutations(uniq.length); // nP2 updated.numbers = [...uniq, '-', ...uniq]; } else { for (const a of group1) { for (const b of group2) { if (a !== b) combinations++; } } updated.numbers = [...group1, '-', ...group2]; } updated.total = combinations * value * 10; break; } case 'QUI': { const dashIndex = updated.numbers.indexOf('-'); const group1 = updated.numbers.slice(0, dashIndex).filter(n => typeof n === 'number') as number[]; const group2 = updated.numbers.slice(dashIndex + 1).filter(n => typeof n === 'number') as number[]; let combinations = 0; const set1 = new Set(group1); const set2 = new Set(group2); const uniqueUnion = new Set([...group1, ...group2]); function setsAreEqual(a: Set, b: Set): boolean { if (a.size !== b.size) return false; for (let v of a) if (!b.has(v)) return false; return true; } if (isBoxed || setsAreEqual(set1, set2)) { combinations = uniqueUnion.size >= 2 ? (uniqueUnion.size * (uniqueUnion.size - 1)) / 2 : 0; } else { const pairSet = new Set(); for (let a of set1) { for (let b of set2) { if (a === b) continue; let key = a < b ? `${a},${b}` : `${b},${a}`; pairSet.add(key); } } combinations = pairSet.size; } updated.numbers = [...group1, '-', ...group2]; updated.total = combinations * value * 10; break; } case 'TAN': { const dashIndices = updated.numbers .map((n, idx) => (n === '-' ? idx : -1)) .filter(idx => idx !== -1); if (dashIndices.length < 2) break; // not ready yet const group1 = updated.numbers.slice(0, dashIndices[0]).filter(n => typeof n === 'number') as number[]; const group2 = updated.numbers.slice(dashIndices[0] + 1, dashIndices[1]).filter(n => typeof n === 'number') as number[]; const group3 = updated.numbers.slice(dashIndices[1] + 1).filter(n => typeof n === 'number') as number[]; let combinations = 0; if (isBoxed) { // Boxed: all unique numbers, nP3 const allNums = Array.from(new Set([...group1, ...group2, ...group3])); combinations = allNums.length >= 3 ? this.calculatePermutationsN(allNums.length, 3) : 0; } else { // Unboxed: only combinations where all chosen numbers are different for (const a of group1) { for (const b of group2) { if (b === a) continue; for (const c of group3) { if (c === a || c === b) continue; combinations++; } } } } updated.total = combinations * value * 10; updated.numbers = [...group1, '-', ...group2, '-', ...group3]; break; } case 'TRE': case 'MJP': case 'JKP': { const legs = this.splitToLegs(updated.numbers, this.getLegCount(label)); const requiredLegs = this.getLegCount(label); const filledLegs = legs.filter(leg => leg.length > 0).length; if (filledLegs >= requiredLegs - 1) { const combinations = legs.reduce((acc, leg) => acc * (leg.length || 1), 1); updated.total = combinations * value * 10; } break; } // ⚪ BOX logic default: { const combCount = isBoxed ? this.calculatePermutations(numbers.length) : this.calculateCombinations(numbers.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; this.selections.next([...this.selections.value, { ...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[][] { const result: number[][] = []; let currentLeg: number[] = []; 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 if (typeof item === 'number') { 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; } } }