use serde_json::Value;

use crate::config::SymmetryType;
use crate::grid::{Cell, Grid};
use crate::grid::slot::Direction;
use crate::puzzle::{Clue, PlacedWord, Puzzle};

pub fn from_ipuz(json: &Value) -> Result<Puzzle, String> {
    let dims = json.get("dimensions").ok_or("missing dimensions")?;
    let width = dims.get("width").and_then(|v| v.as_u64()).ok_or("missing width")? as usize;
    let height = dims.get("height").and_then(|v| v.as_u64()).ok_or("missing height")? as usize;

    if width == 0 || height == 0 || width > 50 || height > 50 {
        return Err(format!("invalid grid size {}x{}", width, height));
    }

    let solution = json.get("solution").ok_or("missing solution grid")?;
    let rows = solution.as_array().ok_or("solution is not an array")?;
    if rows.len() != height {
        return Err(format!("solution has {} rows, expected {}", rows.len(), height));
    }

    let mut grid = Grid::new(width, height);
    for (r, row) in rows.iter().enumerate() {
        let cells = row.as_array().ok_or("solution row is not an array")?;
        if cells.len() != width {
            return Err(format!("row {} has {} cells, expected {}", r, cells.len(), width));
        }
        for (c, cell) in cells.iter().enumerate() {
            if cell.is_null() {
                grid.set(r, c, Cell::Empty);
            } else if let Some(s) = cell.as_str() {
                if s == "#" {
                    grid.set(r, c, Cell::Black);
                } else if s.len() == 1 {
                    let ch = s.chars().next().unwrap().to_ascii_uppercase();
                    if ch.is_ascii_alphabetic() {
                        grid.set(r, c, Cell::Letter(ch));
                    } else {
                        grid.set(r, c, Cell::Empty);
                    }
                } else {
                    grid.set(r, c, Cell::Empty);
                }
            } else if cell.as_u64().is_some() || cell.as_i64().is_some() {
                // some ipuz files use 0 for empty cells in solution
                grid.set(r, c, Cell::Empty);
            } else {
                grid.set(r, c, Cell::Empty);
            }
        }
    }

    // parse clues
    let clues_obj = json.get("clues").ok_or("missing clues")?;
    let across_arr = clues_obj.get("Across").and_then(|v| v.as_array()).ok_or("missing Across clues")?;
    let down_arr = clues_obj.get("Down").and_then(|v| v.as_array()).ok_or("missing Down clues")?;

    let parsed_across = parse_clue_array(across_arr)?;
    let parsed_down = parse_clue_array(down_arr)?;

    // build word list and full clues from grid + parsed clue data
    let mut clues_across = Vec::new();
    let mut clues_down = Vec::new();
    let mut words = Vec::new();

    for (num, text) in &parsed_across {
        if let Some((r, c)) = find_numbered_cell(&grid, &json, width, height, *num) {
            let answer = read_word(&grid, r, c, width, height, Direction::Across);
            words.push(PlacedWord {
                word: answer.clone(),
                row: r,
                col: c,
                direction: Direction::Across,
                clue_number: *num,
            });
            clues_across.push(Clue {
                number: *num,
                direction: Direction::Across,
                text: text.clone(),
                answer,
                definition: None,
                wordplay_type: None,
            });
        }
    }

    for (num, text) in &parsed_down {
        if let Some((r, c)) = find_numbered_cell(&grid, &json, width, height, *num) {
            let answer = read_word(&grid, r, c, width, height, Direction::Down);
            words.push(PlacedWord {
                word: answer.clone(),
                row: r,
                col: c,
                direction: Direction::Down,
                clue_number: *num,
            });
            clues_down.push(Clue {
                number: *num,
                direction: Direction::Down,
                text: text.clone(),
                answer,
                definition: None,
                wordplay_type: None,
            });
        }
    }

    Ok(Puzzle {
        grid,
        words,
        clues_across,
        clues_down,
        width,
        height,
        symmetry: SymmetryType::None,
        difficulty_score: 50,
        generation_time_ms: 0,
        theme: None,
        theme_entries: vec![],
        is_cryptic: false,
    })
}

fn parse_clue_array(arr: &[Value]) -> Result<Vec<(u32, String)>, String> {
    let mut result = Vec::new();
    for entry in arr {
        if let Some(pair) = entry.as_array() {
            if pair.len() >= 2 {
                let num = pair[0].as_u64().ok_or("clue number not a number")? as u32;
                let text = pair[1].as_str().unwrap_or("").to_string();
                result.push((num, text));
            }
        } else if let Some(obj) = entry.as_object() {
            // some ipuz files use object format: {"number": N, "clue": "text"}
            let num = obj.get("number").and_then(|v| v.as_u64()).ok_or("clue missing number")? as u32;
            let text = obj.get("clue").and_then(|v| v.as_str()).unwrap_or("").to_string();
            result.push((num, text));
        }
    }
    Ok(result)
}

fn find_numbered_cell(grid: &Grid, json: &Value, width: usize, height: usize, target: u32) -> Option<(usize, usize)> {
    // try the puzzle grid first (ipuz stores numbers there)
    if let Some(puzzle_grid) = json.get("puzzle").and_then(|v| v.as_array()) {
        for (r, row) in puzzle_grid.iter().enumerate() {
            if let Some(cells) = row.as_array() {
                for (c, cell) in cells.iter().enumerate() {
                    let num = if let Some(n) = cell.as_u64() {
                        n as u32
                    } else if let Some(obj) = cell.as_object() {
                        obj.get("cell").and_then(|v| v.as_u64()).unwrap_or(0) as u32
                    } else {
                        continue
                    };
                    if num == target {
                        return Some((r, c));
                    }
                }
            }
        }
    }

    // fallback: compute numbering from grid
    let mut number = 1u32;
    for r in 0..height {
        for c in 0..width {
            if grid.get(r, c).is_black() { continue; }
            let starts_across = (c == 0 || grid.get(r, c.wrapping_sub(1)).is_black() || c.wrapping_sub(1) >= width)
                && c + 1 < width && !grid.get(r, c + 1).is_black();
            let starts_down = (r == 0 || grid.get(r.wrapping_sub(1), c).is_black() || r.wrapping_sub(1) >= height)
                && r + 1 < height && !grid.get(r + 1, c).is_black();
            if starts_across || starts_down {
                if number == target {
                    return Some((r, c));
                }
                number += 1;
            }
        }
    }
    None
}

fn read_word(grid: &Grid, row: usize, col: usize, width: usize, height: usize, dir: Direction) -> String {
    let mut word = String::new();
    let (mut r, mut c) = (row, col);
    loop {
        if r >= height || c >= width { break; }
        match grid.get(r, c) {
            Cell::Letter(ch) => word.push(ch),
            Cell::Empty => word.push('_'),
            Cell::Black => break,
        }
        match dir {
            Direction::Across => c += 1,
            Direction::Down => r += 1,
        }
    }
    word
}
