use std::fmt;

#[derive(Debug, Clone, PartialEq)]
pub enum GrblState {
    Idle,
    Run,
    Hold,
    Jog,
    Alarm,
    Door,
    Check,
    Home,
    Sleep,
    Unknown(String),
}

impl fmt::Display for GrblState {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            GrblState::Unknown(s) => write!(f, "{s}"),
            other => write!(f, "{other:?}"),
        }
    }
}

#[derive(Debug, Clone)]
pub struct GrblStatus {
    pub state: GrblState,
    pub machine_pos: (f64, f64, f64),
    pub work_pos: Option<(f64, f64, f64)>,
}

#[derive(Debug)]
#[allow(dead_code)]
pub enum GrblResponse {
    Ok,
    Error(u32),
    Status(GrblStatus),
    Message(String),
}

pub fn parse_response(line: &str) -> GrblResponse {
    let line = line.trim();
    if line == "ok" {
        GrblResponse::Ok
    } else if let Some(code) = line.strip_prefix("error:") {
        GrblResponse::Error(code.parse().unwrap_or(0))
    } else if line.starts_with('<') && line.ends_with('>') {
        match parse_status(line) {
            Some(status) => GrblResponse::Status(status),
            None => GrblResponse::Message(line.to_string()),
        }
    } else {
        GrblResponse::Message(line.to_string())
    }
}

fn parse_position(s: &str) -> Option<(f64, f64, f64)> {
    let parts: Vec<&str> = s.split(',').collect();
    if parts.len() == 3 {
        Some((
            parts[0].parse().ok()?,
            parts[1].parse().ok()?,
            parts[2].parse().ok()?,
        ))
    } else {
        None
    }
}

fn parse_state(s: &str) -> GrblState {
    match s {
        "Idle" => GrblState::Idle,
        "Run" => GrblState::Run,
        "Hold" | "Hold:0" | "Hold:1" => GrblState::Hold,
        "Jog" => GrblState::Jog,
        "Alarm" => GrblState::Alarm,
        "Door" | "Door:0" | "Door:1" | "Door:2" | "Door:3" => GrblState::Door,
        "Check" => GrblState::Check,
        "Home" => GrblState::Home,
        "Sleep" => GrblState::Sleep,
        other => GrblState::Unknown(other.to_string()),
    }
}

pub fn parse_status(report: &str) -> Option<GrblStatus> {
    // Format: <State|MPos:x,y,z|WPos:x,y,z|...>
    let inner = report.strip_prefix('<')?.strip_suffix('>')?;
    let fields: Vec<&str> = inner.split('|').collect();
    if fields.is_empty() {
        return None;
    }

    let state = parse_state(fields[0]);
    let mut machine_pos = None;
    let mut work_pos = None;

    for field in &fields[1..] {
        if let Some(pos) = field.strip_prefix("MPos:") {
            machine_pos = parse_position(pos);
        } else if let Some(pos) = field.strip_prefix("WPos:") {
            work_pos = parse_position(pos);
        }
    }

    Some(GrblStatus {
        state,
        machine_pos: machine_pos?,
        work_pos,
    })
}

pub fn build_move_command(
    x: Option<f64>,
    y: Option<f64>,
    z: Option<f64>,
    feed_rate: Option<f64>,
) -> String {
    let gcode = if feed_rate.is_some() { "G01" } else { "G00" };
    let mut cmd = gcode.to_string();
    if let Some(v) = x {
        cmd.push_str(&format!(" X{v:.3}"));
    }
    if let Some(v) = y {
        cmd.push_str(&format!(" Y{v:.3}"));
    }
    if let Some(v) = z {
        cmd.push_str(&format!(" Z{v:.3}"));
    }
    if let Some(f) = feed_rate {
        cmd.push_str(&format!(" F{f:.1}"));
    }
    cmd
}
