#!/usr/bin/env python3
"""
Script to read and parse Powerball numbers from a CSV file with advanced pattern detection.
"""

import csv
from datetime import datetime, timedelta
from pathlib import Path
from typing import List, Dict, Tuple, Optional, Set
from collections import Counter, defaultdict
from itertools import combinations


def read_powerball_csv(filepath: str) -> List[Dict]:
    """
    Read a CSV file containing Powerball numbers.
    
    Expected CSV format:
    Draw Date,Winning Numbers,Multiplier
    09/26/2020,11 21 27 36 62 24,3
    
    Args:
        filepath: Path to the CSV file
        
    Returns:
        List of dictionaries containing parsed powerball data
    """
    powerball_data = []
    
    try:
        with open(filepath, 'r', encoding='utf-8') as csvfile:
            reader = csv.DictReader(csvfile)
            
            for row_num, row in enumerate(reader, start=2):  # start=2 because row 1 is header
                try:
                    # Skip empty rows
                    if not any(row.values()):
                        continue
                    
                    # Parse the row
                    draw_date = datetime.strptime(row['Draw Date'].strip(), '%m/%d/%Y')
                    
                    # Parse winning numbers - filter out empty strings
                    numbers_str = row['Winning Numbers'].strip()
                    winning_numbers = [int(n) for n in numbers_str.split() if n.strip()]
                    
                    multiplier = int(row['Multiplier'].strip())
                    
                    powerball_data.append({
                        'draw_date': draw_date,
                        'date_str': draw_date.strftime('%Y-%m-%d'),
                        'winning_numbers': winning_numbers,
                        'multiplier': multiplier
                    })
                except ValueError as e:
                    print(f"Warning: Skipping row {row_num} due to error: {e}")
                    print(f"  Row data: {row}")
                    continue
                
    except FileNotFoundError:
        print(f"Error: File '{filepath}' not found.")
        return []
    except Exception as e:
        print(f"Error: Failed to read CSV file - {e}")
        return []
    
    return powerball_data


def display_powerball_data(data: List[Dict]) -> None:
    """
    Display powerball data in a formatted table.
    
    Args:
        data: List of powerball data dictionaries
    """
    if not data:
        print("No data to display.")
        return
    
    print(f"{'Draw Date':<12} {'Winning Numbers':<30} {'Multiplier':<10}")
    print("-" * 52)
    
    for entry in data:
        numbers_str = ' '.join(map(str, entry['winning_numbers']))
        print(f"{entry['date_str']:<12} {numbers_str:<30} {entry['multiplier']:<10}")


def _get_unweighted_top_numbers(draws: List[Dict]) -> Tuple[List[int], Optional[int]]:
    """Return top 5 white balls and top Powerball from a draw list."""
    if not draws:
        return [], None

    white_counter = Counter()
    powerball_counter = Counter()

    for entry in draws:
        numbers = entry['winning_numbers']
        for number in numbers[:5]:
            white_counter[number] += 1

        if len(numbers) >= 6:
            powerball_counter[numbers[5]] += 1

    top_white = [number for number, _ in white_counter.most_common(5)]
    top_powerball = powerball_counter.most_common(1)[0][0] if powerball_counter else None

    return top_white, top_powerball


def _get_recency_weighted_top_numbers(draws: List[Dict]) -> Tuple[List[int], Optional[int]]:
    """Return top numbers weighted so newer draws contribute more."""
    if not draws:
        return [], None

    sorted_draws = sorted(draws, key=lambda x: x['draw_date'])
    total_draws = len(sorted_draws)

    white_scores: Dict[int, float] = defaultdict(float)
    powerball_scores: Dict[int, float] = defaultdict(float)

    for index, entry in enumerate(sorted_draws, start=1):
        weight = index / total_draws
        numbers = entry['winning_numbers']

        for number in numbers[:5]:
            white_scores[number] += weight

        if len(numbers) >= 6:
            powerball_scores[numbers[5]] += weight

    top_white = [number for number, _ in sorted(white_scores.items(), key=lambda x: x[1], reverse=True)[:5]]
    top_powerball = max(powerball_scores.items(), key=lambda x: x[1])[0] if powerball_scores else None

    return top_white, top_powerball


def _get_aggressive_recency_weighted_top_numbers(draws: List[Dict]) -> Tuple[List[int], Optional[int]]:
    """Return top numbers with an aggressive recency curve (cubic weighting)."""
    if not draws:
        return [], None

    sorted_draws = sorted(draws, key=lambda x: x['draw_date'])
    total_draws = len(sorted_draws)

    white_scores: Dict[int, float] = defaultdict(float)
    powerball_scores: Dict[int, float] = defaultdict(float)

    for index, entry in enumerate(sorted_draws, start=1):
        normalized_position = index / total_draws
        weight = normalized_position ** 3
        numbers = entry['winning_numbers']

        for number in numbers[:5]:
            white_scores[number] += weight

        if len(numbers) >= 6:
            powerball_scores[numbers[5]] += weight

    top_white = [number for number, _ in sorted(white_scores.items(), key=lambda x: x[1], reverse=True)[:5]]
    top_powerball = max(powerball_scores.items(), key=lambda x: x[1])[0] if powerball_scores else None

    return top_white, top_powerball


def analyze_most_frequent_numbers(data: List[Dict]) -> Dict:
    """
    Analyze and display the most frequent number in each slot position.
    
    Args:
        data: List of powerball data dictionaries
    """
    if not data:
        print("No data to analyze.")
        return {}
    
    # Initialize lists to hold numbers for each position
    num_positions = len(data[0]['winning_numbers'])
    position_numbers = [[] for _ in range(num_positions)]
    
    # Collect all numbers for each position
    for entry in data:
        for position, number in enumerate(entry['winning_numbers']):
            position_numbers[position].append(number)
    
    # Find most common number in each position
    print("\n" + "=" * 60)
    print("Most Frequent Numbers by Slot Position")
    print("=" * 60)
    
    most_frequent = []
    for position, numbers in enumerate(position_numbers, start=1):
        counter = Counter(numbers)
        most_common_num, frequency = counter.most_common(1)[0]
        most_frequent.append(most_common_num)
        percentage = (frequency / len(numbers)) * 100
        print(f"Position {position}: {most_common_num:<3} (appears {frequency} times, {percentage:.1f}%)")
    
    print("-" * 60)
    print(f"Most frequent numbers combined: {' '.join(map(str, most_frequent))}")
    
    # Analyze most frequent number across first 5 positions combined
    print("\n" + "=" * 60)
    print("Additional Analysis")
    print("=" * 60)
    
    # Combine all numbers from positions 1-5
    first_five_combined = []
    for i in range(min(5, num_positions)):
        first_five_combined.extend(position_numbers[i])
    
    top_five_numbers = []
    top_ten_numbers = []
    if first_five_combined:
        counter_first_five = Counter(first_five_combined)
        top_five_numbers = counter_first_five.most_common(5)
        top_ten_numbers = counter_first_five.most_common(10)
        print("Top 5 most drawn numbers in first 5 positions combined:")
        for rank, (number, frequency) in enumerate(top_five_numbers, start=1):
            percentage = (frequency / len(first_five_combined)) * 100
            print(f"  {rank}. {number:<3} (appears {frequency} times, {percentage:.1f}%)")
    
    # Analyze the sixth position (Powerball)
    powerball_ranked = []
    most_common_sixth = None
    if num_positions >= 6:
        sixth_position_numbers = position_numbers[5]
        counter_sixth = Counter(sixth_position_numbers)
        powerball_ranked = counter_sixth.most_common(5)
        most_common_sixth, freq_sixth = counter_sixth.most_common(1)[0]
        percentage_sixth = (freq_sixth / len(sixth_position_numbers)) * 100
        print(f"\nMost drawn number in position 6 (Powerball): {most_common_sixth} (appears {freq_sixth} times, {percentage_sixth:.1f}%)")

    # Third strategy: most frequent numbers from the most recent 104 draws
    sorted_by_date = sorted(data, key=lambda x: x['draw_date'])
    recent_104_draws = sorted_by_date[-104:]
    recent_104_top_five, recent_104_powerball = _get_unweighted_top_numbers(recent_104_draws)

    # Fourth strategy: weighted-by-recency across all draws
    weighted_top_five, weighted_powerball = _get_recency_weighted_top_numbers(data)

    # Fifth strategy: aggressive weighted-by-recency across all draws
    aggressive_weighted_top_five, aggressive_weighted_powerball = _get_aggressive_recency_weighted_top_numbers(data)
    
    # Return statistics for prediction
    return {
        'top_five_combined': [num for num, freq in top_five_numbers] if first_five_combined else [],
        'top_ten_combined': [num for num, freq in top_ten_numbers],
        'most_common_powerball': most_common_sixth,
        'powerball_ranked': [num for num, freq in powerball_ranked],
        'recent_104_top_five': recent_104_top_five,
        'recent_104_powerball': recent_104_powerball,
        'weighted_top_five': weighted_top_five,
        'weighted_powerball': weighted_powerball,
        'aggressive_weighted_top_five': aggressive_weighted_top_five,
        'aggressive_weighted_powerball': aggressive_weighted_powerball
    }


def generate_suggested_combination(stats: dict) -> None:
    """
    Generate a suggested combination based on statistical analysis.
    
    Args:
        stats: Dictionary containing statistical analysis results
    """
    print("\n" + "=" * 60)
    print("Suggested Combination Based on Statistical Analysis")
    print("=" * 60)
    
    print("\n*** DISCLAIMER: This is purely statistical analysis.")
    print("Past results do NOT predict future outcomes.")
    print("Each draw is random and independent.\n")
    
    top_five = stats['top_five_combined']
    top_ten = stats.get('top_ten_combined', [])
    powerball = stats['most_common_powerball']
    ranked_powerballs = stats.get('powerball_ranked', [])
    recent_104_top_five = stats.get('recent_104_top_five', [])
    recent_104_powerball = stats.get('recent_104_powerball')
    weighted_top_five = stats.get('weighted_top_five', [])
    weighted_powerball = stats.get('weighted_powerball')
    aggressive_weighted_top_five = stats.get('aggressive_weighted_top_five', [])
    aggressive_weighted_powerball = stats.get('aggressive_weighted_powerball')
    
    if len(top_five) >= 5 and powerball:
        # Use the top 5 most frequent numbers from positions 1-5
        primary_numbers = sorted(top_five[:5])
        primary_powerball = powerball

        # Build a secondary set from the next-most-frequent historical numbers.
        secondary_pool = top_ten[5:10]
        if len(secondary_pool) < 5:
            secondary_pool = top_ten[:5]

        secondary_numbers = sorted(secondary_pool[:5])
        secondary_powerball = ranked_powerballs[1] if len(ranked_powerballs) > 1 else primary_powerball

        third_numbers = sorted(recent_104_top_five[:5]) if len(recent_104_top_five) >= 5 else []
        third_powerball = recent_104_powerball

        fourth_numbers = sorted(weighted_top_five[:5]) if len(weighted_top_five) >= 5 else []
        fourth_powerball = weighted_powerball

        fifth_numbers = sorted(aggressive_weighted_top_five[:5]) if len(aggressive_weighted_top_five) >= 5 else []
        fifth_powerball = aggressive_weighted_powerball
        
        print("Primary set (most frequent):")
        print(f"  White balls: {' '.join(map(str, primary_numbers))}")
        print(f"  Powerball: {primary_powerball}")

        print("\nSecondary set (next-most frequent):")
        print(f"  White balls: {' '.join(map(str, secondary_numbers))}")
        print(f"  Powerball: {secondary_powerball}")

        if third_numbers and third_powerball:
            print("\nThird set (most frequent in recent 104 draws):")
            print(f"  White balls: {' '.join(map(str, third_numbers))}")
            print(f"  Powerball: {third_powerball}")

        if fourth_numbers and fourth_powerball:
            print("\nFourth set (weighted by recency):")
            print(f"  White balls: {' '.join(map(str, fourth_numbers))}")
            print(f"  Powerball: {fourth_powerball}")

        if fifth_numbers and fifth_powerball:
            print("\nFifth set (aggressive recency weighting):")
            print(f"  White balls: {' '.join(map(str, fifth_numbers))}")
            print(f"  Powerball: {fifth_powerball}")
        print()
        print(f"Primary combination: {' '.join(map(str, primary_numbers))} + {primary_powerball}")
        print(f"Secondary combination: {' '.join(map(str, secondary_numbers))} + {secondary_powerball}")

        if third_numbers and third_powerball:
            print(f"Third combination: {' '.join(map(str, third_numbers))} + {third_powerball}")

        if fourth_numbers and fourth_powerball:
            print(f"Fourth combination: {' '.join(map(str, fourth_numbers))} + {fourth_powerball}")

        if fifth_numbers and fifth_powerball:
            print(f"Fifth combination: {' '.join(map(str, fifth_numbers))} + {fifth_powerball}")
        print()
        print("These combinations are based on:")
        print("  • The 5 most frequently drawn numbers across all white ball positions")
        print(f"  • The most frequently drawn Powerball number ({primary_powerball})")
        print("  • A secondary set from the next-most-frequent white ball and Powerball values")
        print("  • Recent-window frequency (last 104 draws)")
        print("  • Recency-weighted scoring where newer draws count more")
        print("  • Aggressive recency weighting using a cubic curve")
    else:
        print("Insufficient data to generate a suggestion.")


def detect_consecutive_patterns(data: List[Dict]) -> Dict:
    """
    Analyze frequency of consecutive numbers in draws.
    
    Args:
        data: List of powerball data dictionaries
        
    Returns:
        Dictionary with consecutive number statistics
    """
    consecutive_counts = Counter()
    total_draws = len(data)
    draws_with_consecutives = 0
    consecutive_pairs = []
    
    for entry in data:
        white_balls = sorted(entry['winning_numbers'][:5])
        has_consecutive = False
        
        for i in range(len(white_balls) - 1):
            if white_balls[i+1] - white_balls[i] == 1:
                consecutive_counts[f"{white_balls[i]}-{white_balls[i+1]}"] += 1
                consecutive_pairs.append((white_balls[i], white_balls[i+1]))
                has_consecutive = True
        
        if has_consecutive:
            draws_with_consecutives += 1
    
    print("\n" + "=" * 60)
    print("PATTERN: Consecutive Numbers Analysis")
    print("=" * 60)
    percentage = (draws_with_consecutives / total_draws) * 100 if total_draws > 0 else 0
    print(f"Draws with consecutive numbers: {draws_with_consecutives}/{total_draws} ({percentage:.1f}%)")
    
    if consecutive_counts:
        print("\nMost common consecutive pairs:")
        for pair, count in consecutive_counts.most_common(10):
            pair_pct = (count / total_draws) * 100
            print(f"  {pair}: {count} times ({pair_pct:.1f}%)")
    
    return {
        'draws_with_consecutives': draws_with_consecutives,
        'percentage': percentage,
        'common_pairs': dict(consecutive_counts.most_common(10))
    }


def detect_odd_even_patterns(data: List[Dict]) -> Dict:
    """
    Analyze odd/even distribution patterns.
    
    Args:
        data: List of powerball data dictionaries
        
    Returns:
        Dictionary with odd/even distribution statistics
    """
    distribution = Counter()
    
    for entry in data:
        white_balls = entry['winning_numbers'][:5]
        odd_count = sum(1 for num in white_balls if num % 2 == 1)
        even_count = 5 - odd_count
        distribution[f"{odd_count}odd-{even_count}even"] += 1
    
    print("\n" + "=" * 60)
    print("PATTERN: Odd/Even Distribution")
    print("=" * 60)
    print("Distribution of odd vs even numbers:")
    
    total = sum(distribution.values())
    for pattern, count in sorted(distribution.items(), key=lambda x: x[1], reverse=True):
        percentage = (count / total) * 100
        print(f"  {pattern}: {count} times ({percentage:.1f}%)")
    
    return {'distribution': dict(distribution)}


def detect_high_low_patterns(data: List[Dict]) -> Dict:
    """
    Analyze high/low number distribution (1-35 = low, 36-69 = high).
    
    Args:
        data: List of powerball data dictionaries
        
    Returns:
        Dictionary with high/low distribution statistics
    """
    distribution = Counter()
    SPLIT_POINT = 35
    
    for entry in data:
        white_balls = entry['winning_numbers'][:5]
        low_count = sum(1 for num in white_balls if num <= SPLIT_POINT)
        high_count = 5 - low_count
        distribution[f"{low_count}low-{high_count}high"] += 1
    
    print("\n" + "=" * 60)
    print(f"PATTERN: High/Low Distribution (Low: 1-{SPLIT_POINT}, High: {SPLIT_POINT+1}-69)")
    print("=" * 60)
    print("Distribution of low vs high numbers:")
    
    total = sum(distribution.values())
    for pattern, count in sorted(distribution.items(), key=lambda x: x[1], reverse=True):
        percentage = (count / total) * 100
        print(f"  {pattern}: {count} times ({percentage:.1f}%)")
    
    return {'distribution': dict(distribution)}


def detect_sum_patterns(data: List[Dict]) -> Dict:
    """
    Analyze the sum of winning numbers.
    
    Args:
        data: List of powerball data dictionaries
        
    Returns:
        Dictionary with sum statistics
    """
    sums = []
    
    for entry in data:
        white_balls = entry['winning_numbers'][:5]
        total = sum(white_balls)
        sums.append(total)
    
    print("\n" + "=" * 60)
    print("PATTERN: Sum of Winning Numbers")
    print("=" * 60)
    
    avg_sum = sum(sums) / len(sums) if sums else 0
    min_sum = min(sums) if sums else 0
    max_sum = max(sums) if sums else 0
    
    print(f"Average sum: {avg_sum:.1f}")
    print(f"Range: {min_sum} - {max_sum}")
    
    # Create buckets for sum ranges
    buckets = Counter()
    for s in sums:
        bucket = (s // 25) * 25  # Group into 25-number ranges
        buckets[f"{bucket}-{bucket+24}"] += 1
    
    print("\nSum distribution by range:")
    for range_str, count in sorted(buckets.items(), key=lambda x: int(x[0].split('-')[0])):
        percentage = (count / len(sums)) * 100
        print(f"  {range_str}: {count} times ({percentage:.1f}%)")
    
    return {
        'average': avg_sum,
        'min': min_sum,
        'max': max_sum,
        'buckets': dict(buckets)
    }


def detect_gap_patterns(data: List[Dict]) -> Dict:
    """
    Analyze gaps (spacing) between consecutive numbers when sorted.
    
    Args:
        data: List of powerball data dictionaries
        
    Returns:
        Dictionary with gap statistics
    """
    all_gaps = []
    
    for entry in data:
        white_balls = sorted(entry['winning_numbers'][:5])
        gaps = [white_balls[i+1] - white_balls[i] for i in range(len(white_balls) - 1)]
        all_gaps.extend(gaps)
    
    print("\n" + "=" * 60)
    print("PATTERN: Gap Analysis (spacing between sorted numbers)")
    print("=" * 60)
    
    avg_gap = sum(all_gaps) / len(all_gaps) if all_gaps else 0
    gap_counter = Counter(all_gaps)
    
    print(f"Average gap: {avg_gap:.1f}")
    print("\nMost common gap sizes:")
    for gap, count in gap_counter.most_common(10):
        percentage = (count / len(all_gaps)) * 100
        print(f"  Gap of {gap}: {count} times ({percentage:.1f}%)")
    
    return {
        'average_gap': avg_gap,
        'gap_distribution': dict(gap_counter.most_common(15))
    }


def detect_hot_cold_numbers(data: List[Dict], recent_draws: int = 20) -> Dict:
    """
    Identify hot (frequently appearing) and cold (rarely appearing) numbers in recent draws.
    
    Args:
        data: List of powerball data dictionaries
        recent_draws: Number of recent draws to analyze
        
    Returns:
        Dictionary with hot/cold number analysis
    """
    sorted_data = sorted(data, key=lambda x: x['draw_date'])
    recent = sorted_data[-recent_draws:] if len(sorted_data) >= recent_draws else sorted_data
    
    white_counter = Counter()
    powerball_counter = Counter()
    
    for entry in recent:
        for num in entry['winning_numbers'][:5]:
            white_counter[num] += 1
        if len(entry['winning_numbers']) >= 6:
            powerball_counter[entry['winning_numbers'][5]] += 1
    
    print("\n" + "=" * 60)
    print(f"PATTERN: Hot & Cold Numbers (Last {len(recent)} draws)")
    print("=" * 60)
    
    print(f"\nHottest white balls (appeared most in last {len(recent)} draws):")
    for num, count in white_counter.most_common(10):
        print(f"  {num}: {count} times")
    
    print(f"\nHottest Powerballs:")
    for num, count in powerball_counter.most_common(5):
        print(f"  {num}: {count} times")
    
    # Cold numbers - all possible white ball numbers (1-69) that appeared least
    all_white_numbers = set(range(1, 70))
    appeared = set(white_counter.keys())
    not_appeared = all_white_numbers - appeared
    
    print(f"\nColdest white balls (appeared least or not at all):")
    cold_numbers = sorted(
        [(num, white_counter.get(num, 0)) for num in all_white_numbers],
        key=lambda x: (x[1], x[0])
    )[:10]
    
    for num, count in cold_numbers:
        if count == 0:
            print(f"  {num}: 0 times (not appeared)")
        else:
            print(f"  {num}: {count} times")
    
    return {
        'hot_white': dict(white_counter.most_common(10)),
        'hot_powerball': dict(powerball_counter.most_common(5)),
        'cold_white': dict(cold_numbers[:10])
    }


def detect_overdue_numbers(data: List[Dict]) -> Dict:
    """
    Identify numbers that haven't appeared in the longest time.
    
    Args:
        data: List of powerball data dictionaries
        
    Returns:
        Dictionary with overdue number analysis
    """
    sorted_data = sorted(data, key=lambda x: x['draw_date'])
    
    # Track last appearance of each number
    last_seen_white = {}
    last_seen_powerball = {}
    
    for entry in sorted_data:
        draw_date = entry['draw_date']
        for num in entry['winning_numbers'][:5]:
            last_seen_white[num] = draw_date
        if len(entry['winning_numbers']) >= 6:
            last_seen_powerball[entry['winning_numbers'][5]] = draw_date
    
    most_recent_draw = sorted_data[-1]['draw_date']
    
    # Calculate days since last appearance
    overdue_white = {}
    for num in range(1, 70):  # White balls are 1-69
        if num in last_seen_white:
            days_overdue = (most_recent_draw - last_seen_white[num]).days
            overdue_white[num] = days_overdue
        else:
            overdue_white[num] = float('inf')  # Never appeared
    
    overdue_powerball = {}
    for num in range(1, 27):  # Powerballs are 1-26
        if num in last_seen_powerball:
            days_overdue = (most_recent_draw - last_seen_powerball[num]).days
            overdue_powerball[num] = days_overdue
        else:
            overdue_powerball[num] = float('inf')
    
    print("\n" + "=" * 60)
    print("PATTERN: Overdue Numbers (longest time since last appearance)")
    print("=" * 60)
    
    print("\nMost overdue white balls:")
    sorted_overdue_white = sorted(overdue_white.items(), key=lambda x: x[1], reverse=True)[:10]
    for num, days in sorted_overdue_white:
        if days == float('inf'):
            print(f"  {num}: Never appeared")
        else:
            print(f"  {num}: {days} days ago")
    
    print("\nMost overdue Powerballs:")
    sorted_overdue_powerball = sorted(overdue_powerball.items(), key=lambda x: x[1], reverse=True)[:5]
    for num, days in sorted_overdue_powerball:
        if days == float('inf'):
            print(f"  {num}: Never appeared")
        else:
            print(f"  {num}: {days} days ago")
    
    return {
        'overdue_white': dict(sorted_overdue_white),
        'overdue_powerball': dict(sorted_overdue_powerball)
    }


def detect_number_pairs(data: List[Dict], top_n: int = 10) -> Dict:
    """
    Identify which number pairs appear together most frequently.
    
    Args:
        data: List of powerball data dictionaries
        top_n: Number of top pairs to display
        
    Returns:
        Dictionary with pair frequency analysis
    """
    pair_counter = Counter()
    
    for entry in data:
        white_balls = entry['winning_numbers'][:5]
        # Get all pairs from this draw
        for pair in combinations(sorted(white_balls), 2):
            pair_counter[pair] += 1
    
    print("\n" + "=" * 60)
    print("PATTERN: Number Pair Analysis")
    print("=" * 60)
    print(f"\nTop {top_n} most common number pairs:")
    
    for (num1, num2), count in pair_counter.most_common(top_n):
        percentage = (count / len(data)) * 100
        print(f"  ({num1}, {num2}): appeared together {count} times ({percentage:.1f}%)")
    
    return {
        'top_pairs': dict(pair_counter.most_common(top_n))
    }


def detect_decade_distribution(data: List[Dict]) -> Dict:
    """
    Analyze how numbers are distributed across decades (1-9, 10-19, 20-29, etc.).
    
    Args:
        data: List of powerball data dictionaries
        
    Returns:
        Dictionary with decade distribution statistics
    """
    decade_counts = Counter()
    
    for entry in data:
        white_balls = entry['winning_numbers'][:5]
        for num in white_balls:
            decade = (num // 10) * 10
            decade_label = f"{decade:02d}-{decade+9:02d}" if decade < 60 else "60-69"
            decade_counts[decade_label] += 1
    
    print("\n" + "=" * 60)
    print("PATTERN: Decade Distribution")
    print("=" * 60)
    print("How often numbers from each range appear:")
    
    total = sum(decade_counts.values())
    for decade in ["00-09", "10-19", "20-29", "30-39", "40-49", "50-59", "60-69"]:
        count = decade_counts.get(decade, 0)
        percentage = (count / total) * 100 if total > 0 else 0
        print(f"  {decade}: {count} times ({percentage:.1f}%)")
    
    return {'decade_distribution': dict(decade_counts)}


def run_all_pattern_analyses(data: List[Dict]) -> Dict:
    """
    Run all pattern detection analyses.
    
    Args:
        data: List of powerball data dictionaries
        
    Returns:
        Dictionary containing all pattern analysis results
    """
    print("\n" + "#" * 60)
    print("# ADVANCED PATTERN DETECTION ANALYSIS")
    print("#" * 60)
    
    results = {}
    
    results['consecutive'] = detect_consecutive_patterns(data)
    results['odd_even'] = detect_odd_even_patterns(data)
    results['high_low'] = detect_high_low_patterns(data)
    results['sum'] = detect_sum_patterns(data)
    results['gaps'] = detect_gap_patterns(data)
    results['hot_cold'] = detect_hot_cold_numbers(data, recent_draws=20)
    results['overdue'] = detect_overdue_numbers(data)
    results['pairs'] = detect_number_pairs(data, top_n=15)
    results['decades'] = detect_decade_distribution(data)
    
    return results


def generate_pattern_based_combinations(data: List[Dict], pattern_results: Dict, num_combinations: int = 5) -> List[Dict]:
    """
    Generate number combinations based on pattern analysis.
    
    Args:
        data: List of powerball data dictionaries
        pattern_results: Results from run_all_pattern_analyses
        num_combinations: Number of combinations to generate
        
    Returns:
        List of dictionaries containing combination details
    """
    import random
    
    print("\n" + "=" * 60)
    print("PATTERN-BASED NUMBER COMBINATIONS")
    print("=" * 60)
    print("\n*** DISCLAIMER: These combinations are based on pattern analysis.")
    print("Lottery draws are random. Past patterns do NOT predict future results.")
    print("Play responsibly.\n")
    
    combinations = []
    
    # Get pattern insights
    hot_white = list(pattern_results['hot_cold']['hot_white'].keys())[:15]
    cold_white = [num for num, _ in pattern_results['hot_cold']['cold_white'].items() if _ < 3][:10]
    overdue_white = [num for num, days in pattern_results['overdue']['overdue_white'].items() if days != float('inf')][:15]
    hot_powerball = list(pattern_results['hot_cold']['hot_powerball'].keys())[:3]
    overdue_powerball = [num for num, days in pattern_results['overdue']['overdue_powerball'].items() if days != float('inf')][:5]
    
    # Get most common distributions
    odd_even_dist = pattern_results['odd_even']['distribution']
    most_common_oe = max(odd_even_dist.items(), key=lambda x: x[1])[0]
    target_odds = int(most_common_oe.split('odd')[0])
    
    high_low_dist = pattern_results['high_low']['distribution']
    most_common_hl = max(high_low_dist.items(), key=lambda x: x[1])[0]
    target_lows = int(most_common_hl.split('low')[0])
    
    # Get average sum
    avg_sum = pattern_results['sum']['average']
    sum_tolerance = 30
    
    # Get top pairs
    top_pairs = list(pattern_results['pairs']['top_pairs'].keys())[:20]
    
    # Strategy descriptions
    strategies = [
        ("Hot Numbers Focus", hot_white, "Uses frequently drawn recent numbers"),
        ("Balanced Hot + Overdue", hot_white[:8] + overdue_white[:12], "Mixes hot numbers with overdue picks"),
        ("Overdue Numbers Focus", overdue_white, "Focuses on numbers that haven't appeared recently"),
        ("Cold Numbers Gamble", cold_white + hot_white[:10], "Includes rarely drawn numbers"),
        ("Pair-Based Selection", [n for pair in top_pairs for n in pair], "Uses numbers from common pairs")
    ]
    
    for strategy_idx in range(min(num_combinations, len(strategies))):
        strategy_name, number_pool, strategy_desc = strategies[strategy_idx]
        
        # Ensure we have enough numbers in pool
        if len(number_pool) < 20:
            number_pool = list(set(number_pool + hot_white + list(range(1, 70))))
        
        # Try to generate a combination that fits the patterns
        attempts = 0
        max_attempts = 1000
        selected = []  # Initialize to avoid unbound variable
        
        while attempts < max_attempts:
            # Select 5 random numbers from pool
            selected = random.sample(number_pool[:30], min(5, len(number_pool[:30])))
            selected = sorted(selected)
            
            # Check odd/even distribution
            odds_count = sum(1 for n in selected if n % 2 == 1)
            if abs(odds_count - target_odds) > 1:
                attempts += 1
                continue
            
            # Check high/low distribution
            lows_count = sum(1 for n in selected if n <= 35)
            if abs(lows_count - target_lows) > 1:
                attempts += 1
                continue
            
            # Check sum is reasonable
            total = sum(selected)
            if abs(total - avg_sum) > sum_tolerance:
                attempts += 1
                continue
            
            # Check gaps are reasonable (not too clustered or too spread)
            gaps = [selected[i+1] - selected[i] for i in range(4)]
            if min(gaps) < 2 or max(gaps) > 25:
                attempts += 1
                continue
            
            # Good combination found
            break
        
        # If we didn't find a perfect match, selected still has the last attempt
        if not selected:
            selected = sorted(random.sample(range(1, 70), 5))
        
        # Select powerball
        if strategy_idx % 2 == 0 and hot_powerball:
            powerball = random.choice(hot_powerball)
        elif overdue_powerball:
            powerball = random.choice(overdue_powerball[:5])
        else:
            powerball = random.randint(1, 26)
        
        # Calculate combination stats
        odds = sum(1 for n in selected if n % 2 == 1)
        evens = 5 - odds
        lows = sum(1 for n in selected if n <= 35)
        highs = 5 - lows
        total = sum(selected)
        
        combinations.append({
            'strategy': strategy_name,
            'description': strategy_desc,
            'numbers': selected,
            'powerball': powerball,
            'odds': odds,
            'evens': evens,
            'lows': lows,
            'highs': highs,
            'sum': total,
            'attempts': attempts
        })
    
    # Display combinations
    for i, combo in enumerate(combinations, 1):
        print(f"\nCombination #{i}: {combo['strategy']}")
        print(f"  Strategy: {combo['description']}")
        print(f"  Numbers: {' '.join(map(str, combo['numbers']))} + Powerball: {combo['powerball']}")
        print(f"  Stats: {combo['odds']}odd-{combo['evens']}even, {combo['lows']}low-{combo['highs']}high, Sum: {combo['sum']}")
    
    print("\n" + "=" * 60)
    print("Quick Pick Lines:")
    print("=" * 60)
    for i, combo in enumerate(combinations, 1):
        numbers_str = ' - '.join(f"{n:2d}" for n in combo['numbers'])
        print(f"  Line {i}: {numbers_str} | PB: {combo['powerball']:2d}")
    
    return combinations


def main():
    """Main function."""
    # Default to 'powerball_numbers.csv' in the same directory as the script
    script_dir = Path(__file__).parent
    csv_file = script_dir / 'powerball_numbers.csv'
    
    # You can also specify a different file path here
    # csv_file = Path('path/to/your/file.csv')
    
    print(f"Reading Powerball numbers from: {csv_file}\n")
    
    powerball_data = read_powerball_csv(str(csv_file))
    
    if powerball_data:
        print(f"Successfully loaded {len(powerball_data)} records.\n")
        display_powerball_data(powerball_data)
        stats = analyze_most_frequent_numbers(powerball_data)
        generate_suggested_combination(stats)
        
        # Run advanced pattern detection
        pattern_results = run_all_pattern_analyses(powerball_data)
        
        # Generate pattern-based combinations
        pattern_combos = generate_pattern_based_combinations(powerball_data, pattern_results, num_combinations=5)
    else:
        print("Failed to load powerball data.")


if __name__ == '__main__':
    main()
