use std::collections::HashMap;

use serde::{Deserialize, Serialize};
use uuid::Uuid;

use cruciverb_core::grid::Cell;
use cruciverb_core::puzzle::Puzzle;

// -- request types --

#[derive(Deserialize)]
pub struct GenerateRequest {
    pub difficulty: Option<String>,
    pub size: Option<usize>,
    pub density: Option<f64>,
    pub clue_difficulty: Option<u8>,
    pub seed: Option<u64>,
    pub theme: Option<String>,
    pub cryptic: Option<bool>,
    pub pack: Option<String>,
}

#[derive(Deserialize)]
pub struct ValidateRequest {
    pub cells: Vec<Vec<Option<String>>>,
    pub elapsed: Option<i64>,
    // kept for client compatibility; the server now uses its own clock for streaks
    #[allow(dead_code)]
    pub local_date: Option<String>,
    pub room_code: Option<String>,
    pub cell_results: Option<String>,
}

#[derive(Deserialize)]
pub struct HintRequest {
    pub kind: String,
    pub row: usize,
    pub col: usize,
    pub direction: Option<String>,
    pub letter: Option<String>,
}

#[derive(Deserialize)]
pub struct ProgressRequest {
    pub cells: Vec<Vec<Option<String>>>,
}

#[derive(Deserialize)]
pub struct UpdateMeRequest {
    pub display_name: Option<String>,
}

// -- response types --

#[derive(Serialize)]
pub struct GenerateResponse {
    pub id: String,
    pub short_id: String,
    pub puzzle: ClientPuzzle,
}

#[derive(Serialize)]
pub struct ClientPuzzleResponse {
    pub id: String,
    pub short_id: String,
    pub puzzle: ClientPuzzle,
}

#[derive(Serialize)]
pub struct ClientPuzzle {
    pub width: usize,
    pub height: usize,
    pub cells: Vec<Vec<ClientCell>>,
    pub clues_across: Vec<ClientClue>,
    pub clues_down: Vec<ClientClue>,
    pub difficulty_score: u32,
    pub cell_numbers: HashMap<String, u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub theme: Option<String>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub theme_entries: Vec<String>,
}

#[derive(Serialize)]
#[serde(rename_all = "snake_case")]
pub enum ClientCell {
    Black,
    Empty,
}

#[derive(Serialize)]
pub struct ClientClue {
    pub number: u32,
    pub direction: String,
    pub text: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub definition: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub wordplay_type: Option<String>,
}

#[derive(Serialize)]
#[serde(untagged)]
pub enum HintResponse {
    CheckCell { is_correct: bool },
    RevealLetter { row: usize, col: usize, letter: String },
    RevealWord { letters: Vec<LetterPosition> },
}

#[derive(Serialize)]
pub struct LetterPosition {
    pub row: usize,
    pub col: usize,
    pub letter: String,
}

// -- conversions --

pub fn to_client_puzzle(puzzle: &Puzzle, _id: Uuid) -> ClientPuzzle {
    let mut cells = Vec::with_capacity(puzzle.height);
    for r in 0..puzzle.height {
        let mut row = Vec::with_capacity(puzzle.width);
        for c in 0..puzzle.width {
            match puzzle.grid.get(r, c) {
                Cell::Black => row.push(ClientCell::Black),
                _ => row.push(ClientCell::Empty),
            }
        }
        cells.push(row);
    }

    let mut cell_numbers: HashMap<String, u32> = HashMap::new();
    for word in &puzzle.words {
        let key = format!("{},{}", word.row, word.col);
        cell_numbers.entry(key).or_insert(word.clue_number);
    }

    let clues_across = puzzle.clues_across.iter().map(|c| ClientClue {
        number: c.number,
        direction: "across".into(),
        text: c.text.clone(),
        definition: c.definition.clone(),
        wordplay_type: c.wordplay_type.clone(),
    }).collect();

    let clues_down = puzzle.clues_down.iter().map(|c| ClientClue {
        number: c.number,
        direction: "down".into(),
        text: c.text.clone(),
        definition: c.definition.clone(),
        wordplay_type: c.wordplay_type.clone(),
    }).collect();

    ClientPuzzle {
        width: puzzle.width,
        height: puzzle.height,
        cells,
        clues_across,
        clues_down,
        difficulty_score: puzzle.difficulty_score,
        cell_numbers,
        theme: puzzle.theme.clone(),
        theme_entries: puzzle.theme_entries.clone(),
    }
}
