//! Generate provably-winnable Klondike seeds for `CHALLENGE_SEEDS`.
//!
//! Walks seeds incrementally from `--start`, calls the solver on each, and
//! collects only those proven winnable (`Ok(Some(_))`; inconclusive is
//! rejected — the curated list wants proof). Prints Rust source suitable for
//! pasting into `solitaire_data/src/challenge.rs`.
//!
//! # Usage
//!
//! ```bash
//! cargo run -p solitaire_assetgen --bin gen_seeds --release -- \
//!     --start 0xCAFE_BABE_0000_0000 --count 75
//! ```
//!
//! Flags:
//!   --start  Starting seed (decimal or 0x-prefixed hex, default 0xCAFEBABE00000000)
//!   --count  Number of Winnable seeds to emit (default 75)
//!   --help   Print this message

use solitaire_core::DrawStockConfig;
use solitaire_core::game_state::GameState;
use solitaire_core::{DEFAULT_SOLVE_MOVES_BUDGET, DEFAULT_SOLVE_STATES_BUDGET};

fn main() {
    let mut args = std::env::args().skip(1).peekable();
    let mut start: u64 = 0xCAFE_BABE_0000_0000;
    let mut count: usize = 75;

    while let Some(arg) = args.next() {
        match arg.as_str() {
            "--start" => {
                let val = args.next().unwrap_or_else(|| {
                    eprintln!("error: --start requires a value");
                    std::process::exit(1);
                });
                start = parse_u64(&val);
            }
            "--count" => {
                let val = args.next().unwrap_or_else(|| {
                    eprintln!("error: --count requires a value");
                    std::process::exit(1);
                });
                count = val.parse().unwrap_or_else(|_| {
                    eprintln!("error: --count must be a positive integer");
                    std::process::exit(1);
                });
            }
            "--help" | "-h" => {
                eprintln!(
                    "{}",
                    include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/bin/gen_seeds.rs"))
                        .lines()
                        .take(20)
                        .collect::<Vec<_>>()
                        .join("\n")
                );
                return;
            }
            other => {
                eprintln!("error: unknown argument: {other}");
                std::process::exit(1);
            }
        }
    }

    if count == 0 {
        eprintln!("error: --count must be > 0");
        std::process::exit(1);
    }

    let draw_mode = DrawStockConfig::DrawOne;
    let mut found: Vec<u64> = Vec::with_capacity(count);
    let mut tried: u64 = 0;
    let mut seed = start;

    eprintln!("gen_seeds: finding {count} Winnable seeds from 0x{start:016X} (DrawOne) …");

    while found.len() < count {
        tried += 1;
        if matches!(
            GameState::solve_fresh_deal(
                seed,
                draw_mode,
                DEFAULT_SOLVE_MOVES_BUDGET,
                DEFAULT_SOLVE_STATES_BUDGET
            ),
            Ok(Some(_))
        ) {
            found.push(seed);
            eprintln!(
                "  [{:>3}/{}]  0x{:016X}  ({} tried so far)",
                found.len(),
                count,
                seed,
                tried
            );
        }
        seed = seed.wrapping_add(1);
    }

    eprintln!(
        "\nDone. Paste the block below into CHALLENGE_SEEDS in solitaire_data/src/challenge.rs:\n"
    );

    println!(
        "    // Generated by solitaire_assetgen::gen_seeds \
         (start=0x{start:016X}, count={count}, date={date})",
        date = current_date()
    );
    for chunk in found.chunks(5) {
        for s in chunk {
            println!(
                "    0x{:04X}_{:04X}_{:04X}_{:04X},",
                (s >> 48) & 0xFFFF,
                (s >> 32) & 0xFFFF,
                (s >> 16) & 0xFFFF,
                s & 0xFFFF,
            );
        }
        println!();
    }
}

fn parse_u64(s: &str) -> u64 {
    let cleaned = s.replace('_', "");
    if let Some(hex) = cleaned
        .strip_prefix("0x")
        .or_else(|| cleaned.strip_prefix("0X"))
    {
        u64::from_str_radix(hex, 16).unwrap_or_else(|_| {
            eprintln!("error: could not parse '{s}' as a hex u64");
            std::process::exit(1);
        })
    } else {
        cleaned.parse().unwrap_or_else(|_| {
            eprintln!("error: could not parse '{s}' as a decimal u64");
            std::process::exit(1);
        })
    }
}

fn current_date() -> String {
    use std::time::{SystemTime, UNIX_EPOCH};
    let secs = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| d.as_secs())
        .unwrap_or(0);
    let days = secs / 86400;
    // Gregorian calendar computation (Tomohiko Sakamoto's algorithm variant)
    let mut y = 1970u64;
    let mut d = days;
    loop {
        let leap = (y.is_multiple_of(4) && !y.is_multiple_of(100)) || y.is_multiple_of(400);
        let days_in_year = if leap { 366 } else { 365 };
        if d < days_in_year {
            break;
        }
        d -= days_in_year;
        y += 1;
    }
    let leap = (y.is_multiple_of(4) && !y.is_multiple_of(100)) || y.is_multiple_of(400);
    let month_days: [u64; 12] = [
        31,
        if leap { 29 } else { 28 },
        31,
        30,
        31,
        30,
        31,
        31,
        30,
        31,
        30,
        31,
    ];
    let mut m = 0usize;
    for &md in &month_days {
        if d < md {
            break;
        }
        d -= md;
        m += 1;
    }
    format!("{y}-{:02}-{:02}", m + 1, d + 1)
}
