use std::collections::HashMap;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;

use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SymmetryType {
    Rotational180,
    Rotational90,
    Diagonal,
    Mirror,
    None,
}

impl Default for SymmetryType {
    fn default() -> Self {
        Self::Rotational180
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DifficultyPreset {
    Glyph,
    Rune,
    Scroll,
    Codex,
    Grimoire,
}

impl DifficultyPreset {
    pub fn grid_size(&self) -> usize {
        match self {
            Self::Glyph => 5,
            Self::Rune => 5,
            Self::Scroll => 9,
            Self::Codex => 15,
            Self::Grimoire => 21,
        }
    }

    pub fn black_cell_density(&self) -> f64 {
        match self {
            Self::Glyph => 0.40,
            Self::Rune => 0.35,
            Self::Scroll => 0.28,
            Self::Codex => 0.20,
            Self::Grimoire => 0.18,
        }
    }

    pub fn frequency_threshold(&self) -> f64 {
        match self {
            Self::Glyph => 0.5,
            Self::Rune => 0.1,
            Self::Scroll => 0.0,
            Self::Codex => 0.0,
            Self::Grimoire => 0.0,
        }
    }

    pub fn difficulty_level(&self) -> u8 {
        match self {
            Self::Glyph => 0,
            Self::Rune => 1,
            Self::Scroll => 2,
            Self::Codex => 3,
            Self::Grimoire => 3,
        }
    }

    pub fn obscurity_budget(&self) -> f64 {
        match self {
            Self::Glyph => 0.0,
            Self::Rune => 0.0,
            Self::Scroll => 0.05,
            Self::Codex => 0.20,
            Self::Grimoire => 0.20,
        }
    }

    pub fn rand_top_base(&self) -> usize {
        match self {
            Self::Glyph => 20,
            Self::Rune => 20,
            Self::Scroll => 15,
            Self::Codex => 10,
            Self::Grimoire => 8,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenerationConfig {
    pub width: usize,
    pub height: usize,
    pub symmetry: SymmetryType,
    pub black_cell_density: f64,
    pub min_word_length: usize,
    pub frequency_threshold: f64,
    pub obscurity_budget: f64,
    pub max_backtrack: usize,
    pub max_restarts: usize,
    pub timeout_seconds: u64,
    pub seed: Option<u64>,
    pub cryptic_grid: bool,
    pub rand_top_base: usize,
    #[serde(skip)]
    pub cancelled: Option<Arc<AtomicBool>>,
    /// Per-word staleness penalties (0.0 = max penalty, 1.0 = no penalty).
    /// Applied during solver scoring to deprioritize recently used words.
    #[serde(skip)]
    pub staleness: Option<Arc<HashMap<String, f64>>>,
}

impl Default for GenerationConfig {
    fn default() -> Self {
        Self {
            width: 15,
            height: 15,
            symmetry: SymmetryType::default(),
            black_cell_density: 0.22,
            min_word_length: 3,
            frequency_threshold: 0.0,
            obscurity_budget: 0.20,
            max_backtrack: 5000,
            max_restarts: 10,
            timeout_seconds: 90,
            seed: None,
            cryptic_grid: false,
            rand_top_base: 10,
            cancelled: None,
            staleness: None,
        }
    }
}

impl GenerationConfig {
    pub fn rand_top_for_size(size: usize) -> usize {
        match size {
            0..=5 => 20,
            6..=9 => 15,
            10..=14 => 10,
            _ => 8,
        }
    }

    pub fn from_preset(preset: DifficultyPreset) -> Self {
        let size = preset.grid_size();
        let (bt, restarts) = match preset {
            DifficultyPreset::Codex => (200000, 40),
            DifficultyPreset::Grimoire => (500000, 60),
            _ => (5000, 10),
        };
        Self {
            width: size,
            height: size,
            black_cell_density: preset.black_cell_density(),
            frequency_threshold: preset.frequency_threshold(),
            obscurity_budget: preset.obscurity_budget(),
            max_backtrack: bt,
            max_restarts: restarts,
            rand_top_base: preset.rand_top_base(),
            ..Default::default()
        }
    }
}
