use pyo3::prelude::*;
use pyo3_stub_gen::derive::{
    gen_stub_pyclass, gen_stub_pyfunction, gen_stub_pymethods,
};

use crate::ops::cut;
use crate::ops::types::ToolPose;
use crate::python::ops::part::cleared_area::PyClearedArea;
use crate::python::ops::part::stock_region::PyStockRegion;
use crate::types::Point3D;

#[gen_stub_pyclass(module = "raygeo.ops.cut.search")]
#[pyclass(name = "ToolPose", skip_from_py_object)]
#[derive(Clone, Debug)]
pub struct PyToolPose {
    #[pyo3(get)]
    pub pos: (f64, f64, f64),
    #[pyo3(get)]
    pub heading: f64,
}

#[gen_stub_pymethods]
#[pymethods]
impl PyToolPose {
    #[new]
    pub fn new(pos: (f64, f64, f64), heading: f64) -> Self {
        PyToolPose { pos, heading }
    }

    pub fn __repr__(&self) -> String {
        format!(
            "ToolPose(pos=({:.3},{:.3},{:.3}), heading={:.3})",
            self.pos.0, self.pos.1, self.pos.2, self.heading,
        )
    }
}

/// Walk the frontier forward from ``start_pos``, skipping the closest
/// vertex.  Returns the first vertex whose outward cut-area probe
/// falls in ``[min, max]``.
///
/// The returned position is offset inward (into the cleared area)
/// by ``radius - advance`` so the tool starts at the correct
/// engagement depth.
///
/// :param cleared: ``ClearedArea`` instance.
/// :param region: ``StockRegion`` defining the boundary and islands.
/// :param start: ``ToolPose`` seed.
/// :param radius: Disk radius (mm).
/// :param step_length: Forward step distance (mm) for the probe.
/// :param advance: Stepover distance (mm) for inward offset.
/// :param min_cut_area: Minimum cut area (mm2).
/// :param max_cut_area: Maximum cut area (mm2), e.g. ``float("inf")``.
/// :returns: ``ToolPose`` or ``None``.
#[allow(clippy::too_many_arguments)]
#[gen_stub_pyfunction(module = "raygeo.ops.cut.search")]
#[pyfunction(name = "search_frontier_engagement")]
fn search_frontier_engagement_py(
    cleared: &PyClearedArea,
    region: &PyStockRegion,
    start: &PyToolPose,
    radius: f64,
    step_length: f64,
    advance: f64,
    min_cut_area: f64,
    max_cut_area: f64,
) -> Option<PyToolPose> {
    let r = cut::search_frontier_engagement(
        &cleared.inner,
        &region.inner,
        ToolPose {
            pos: Point3D::new(start.pos.0, start.pos.1, start.pos.2),
            heading: start.heading,
        },
        radius,
        step_length,
        advance,
        min_cut_area,
        max_cut_area,
    )?;
    Some(PyToolPose {
        pos: (r.pos.x, r.pos.y, r.pos.z),
        heading: r.heading,
    })
}

pub fn register(cut_mod: &Bound<'_, PyModule>) -> PyResult<()> {
    let py = cut_mod.py();
    let m = PyModule::new(py, "search")?;

    m.add_class::<PyToolPose>()?;
    m.add_function(wrap_pyfunction!(search_frontier_engagement_py, &m)?)?;

    cut_mod.add_submodule(&m)?;

    let sys_modules = py.import("sys")?.getattr("modules")?;
    sys_modules.set_item("raygeo.ops.cut.search", &m)?;

    Ok(())
}
