use crate::scene::WireModel;
use crate::ui::overlay::GridPlane;
use acadrust::tables::Ucs;

// ── Coordinate parsing ─────────────────────────────────────────────────────

/// How a typed coordinate should be interpreted relative to the last
/// input point.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(super) enum CoordKind {
    /// `@x,y` prefix — offset from the last input point.
    Relative,
    /// `#x,y` prefix — world/UCS absolute, overriding DYN.
    Absolute,
    /// No prefix — the caller decides (DYN on → relative, off → absolute).
    Default,
}

/// Parse a typed coordinate string into a Vec3 plus its interpretation.
/// Accepts "x,y"   → Vec3(x, y, 0)
///         "x,y,z" → Vec3(x, y, z)
/// A leading `@` marks the value relative to the last point; a leading
/// `#` forces absolute. Separators: comma or semicolon.
pub(super) fn parse_coord(text: &str) -> Option<(glam::Vec3, CoordKind)> {
    let trimmed = text.trim();
    let (kind, rest) = if let Some(r) = trimmed.strip_prefix('@') {
        (CoordKind::Relative, r)
    } else if let Some(r) = trimmed.strip_prefix('#') {
        (CoordKind::Absolute, r)
    } else {
        (CoordKind::Default, trimmed)
    };
    let parts: Vec<f32> = rest
        .split(|c| c == ',' || c == ';')
        .map(|s| s.trim())
        .filter_map(|s| s.parse().ok())
        .collect();
    match parts.as_slice() {
        [x, y] => Some((glam::Vec3::new(*x, *y, 0.0), kind)),
        [x, y, z] => Some((glam::Vec3::new(*x, *y, *z), kind)),
        _ => None,
    }
}

/// Rotate a UCS-local offset into WCS without applying the origin
/// translation — used for relative coordinate entry, where only the
/// axis orientation matters, not the UCS origin.
pub(super) fn ucs_rotate_vec(offset: glam::Vec3, ucs: &Ucs) -> glam::Vec3 {
    let x = glam::Vec3::new(ucs.x_axis.x as f32, ucs.x_axis.y as f32, ucs.x_axis.z as f32);
    let y = glam::Vec3::new(ucs.y_axis.x as f32, ucs.y_axis.y as f32, ucs.y_axis.z as f32);
    let z = ucs_z_axis(ucs);
    x * offset.x + y * offset.y + z * offset.z
}

// ── UCS ↔ WCS transforms ───────────────────────────────────────────────────

/// Convert a point from UCS local coordinates to WCS.
///
/// WCS = origin + x_axis*u + y_axis*v + z_axis*w
pub(super) fn ucs_to_wcs(pt: glam::Vec3, ucs: &Ucs) -> glam::Vec3 {
    let o = glam::Vec3::new(
        ucs.origin.x as f32,
        ucs.origin.y as f32,
        ucs.origin.z as f32,
    );
    let x = glam::Vec3::new(
        ucs.x_axis.x as f32,
        ucs.x_axis.y as f32,
        ucs.x_axis.z as f32,
    );
    let y = glam::Vec3::new(
        ucs.y_axis.x as f32,
        ucs.y_axis.y as f32,
        ucs.y_axis.z as f32,
    );
    let z_ax = ucs_z_axis(ucs);
    o + x * pt.x + y * pt.y + z_ax * pt.z
}

/// Return the normalised Z axis of a UCS (cross product of X and Y axes).
pub(super) fn ucs_z_axis(ucs: &Ucs) -> glam::Vec3 {
    let x = glam::Vec3::new(
        ucs.x_axis.x as f32,
        ucs.x_axis.y as f32,
        ucs.x_axis.z as f32,
    );
    let y = glam::Vec3::new(
        ucs.y_axis.x as f32,
        ucs.y_axis.y as f32,
        ucs.y_axis.z as f32,
    );
    x.cross(y).normalize_or_zero()
}

/// Build a UCS with `origin` and axes rotated by `angle_z_rad` around the Z axis.
pub(super) fn ucs_rotated_z(origin: glam::Vec3, angle_z: f32) -> Ucs {
    let cos = angle_z.cos() as f64;
    let sin = angle_z.sin() as f64;
    let mut ucs = Ucs::new("*ACTIVE*");
    ucs.origin = acadrust::types::Vector3::new(origin.x as f64, origin.y as f64, origin.z as f64);
    ucs.x_axis = acadrust::types::Vector3::new(cos, sin, 0.0);
    ucs.y_axis = acadrust::types::Vector3::new(-sin, cos, 0.0);
    ucs
}

// ── Grid plane detection ───────────────────────────────────────────────────

/// Choose the grid plane whose normal is most aligned with the camera view direction.
pub(super) fn grid_plane_from_camera(pitch: f32, yaw: f32) -> GridPlane {
    let fz = pitch.sin().abs();
    let fy = (pitch.cos() * yaw.cos()).abs();
    let fx = (pitch.cos() * yaw.sin()).abs();
    if fz >= fy && fz >= fx {
        GridPlane::Xy
    } else if fy >= fx {
        GridPlane::Xz
    } else {
        GridPlane::Yz
    }
}

// ── Drawing constraint helpers ─────────────────────────────────────────────

/// Constrain `pt` to the nearest 90° direction from `base` (XY plane, Z-up).
pub(super) fn ortho_constrain(pt: glam::Vec3, base: glam::Vec3) -> glam::Vec3 {
    let dx = (pt.x - base.x).abs();
    let dy = (pt.y - base.y).abs();
    if dx >= dy {
        glam::Vec3::new(pt.x, base.y, pt.z)
    } else {
        glam::Vec3::new(base.x, pt.y, pt.z)
    }
}

/// Constrain `pt` to the nearest polar angle multiple from `base` (XY plane, Z-up).
pub(super) fn polar_constrain(pt: glam::Vec3, base: glam::Vec3, step_deg: f32) -> glam::Vec3 {
    let dx = pt.x - base.x;
    let dy = pt.y - base.y;
    let dist = (dx * dx + dy * dy).sqrt();
    if dist < 1e-6 {
        return pt;
    }
    let step = step_deg.to_radians();
    let angle = dy.atan2(dx);
    let snapped = (angle / step).round() * step;
    glam::Vec3::new(
        base.x + dist * snapped.cos(),
        base.y + dist * snapped.sin(),
        pt.z,
    )
}

/// Polar constraint that only engages when the cursor is within `tol_px`
/// screen pixels of the nearest polar ray; otherwise the cursor is left free
/// so POLAR behaves as if off when pointing away from every angle (issue #70).
pub(super) fn polar_constrain_near(
    pt: glam::Vec3,
    base: glam::Vec3,
    step_deg: f32,
    view_proj: glam::Mat4,
    bounds: iced::Rectangle,
    tol_px: f32,
) -> glam::Vec3 {
    let snapped = polar_constrain(pt, base, step_deg);
    let to_screen = |w: glam::Vec3| {
        let ndc = view_proj.project_point3(w);
        (
            (ndc.x + 1.0) * 0.5 * bounds.width,
            (1.0 - ndc.y) * 0.5 * bounds.height,
        )
    };
    let (cx, cy) = to_screen(pt);
    let (sx, sy) = to_screen(snapped);
    if ((cx - sx).powi(2) + (cy - sy).powi(2)).sqrt() <= tol_px {
        snapped
    } else {
        pt
    }
}

// ── Clipboard / selection helpers ──────────────────────────────────────────

/// Compute the centroid of a set of wire models (average of all points).
pub(super) fn entities_centroid(wires: &[WireModel]) -> glam::Vec3 {
    let mut sum = glam::Vec3::ZERO;
    let mut count = 0usize;
    for w in wires {
        for p in &w.points {
            sum += glam::Vec3::from(*p);
            count += 1;
        }
    }
    if count > 0 {
        sum / count as f32
    } else {
        glam::Vec3::ZERO
    }
}

/// Generate the next available auto group name ("*A1", "*A2", …).
pub(super) fn next_group_auto_name(scene: &crate::scene::Scene) -> String {
    let existing: rustc_hash::FxHashSet<String> =
        scene.groups().map(|g| g.name.clone()).collect();
    for n in 1..=9999 {
        let name = format!("*A{n}");
        if !existing.contains(&name) {
            return name;
        }
    }
    "*A".to_string()
}

// ── Entity type labels ─────────────────────────────────────────────────────

pub(super) fn entity_type_label(entity: &acadrust::EntityType) -> String {
    crate::entities::names::ui_name(entity).to_string()
}

pub(super) fn entity_type_key(entity: &acadrust::EntityType) -> String {
    use acadrust::EntityType::*;
    match entity {
        Point(_) => "point",
        Line(_) => "line",
        Circle(_) => "circle",
        Arc(_) => "arc",
        Ellipse(_) => "ellipse",
        Spline(_) => "spline",
        LwPolyline(_) | Polyline(_) => "pline",
        Polyline2D(_) => "pline2d",
        Polyline3D(_) => "pline3d",
        PolyfaceMesh(_) => "polyface",
        PolygonMesh(_) => "polymesh",
        Text(_) => "text",
        MText(_) => "mtext",
        Dimension(_) => "dimension",
        Leader(_) => "leader",
        MultiLeader(_) => "multileader",
        Tolerance(_) => "tolerance",
        Insert(_) => "insert",
        Block(_) => "block",
        BlockEnd(_) => "blockend",
        Hatch(_) => "hatch",
        Solid(_) => "solid",
        Face3D(_) => "face3d",
        Solid3D(_) => "solid3d",
        Region(_) => "region",
        Body(_) => "body",
        Surface(_) => "surface",
        Mesh(_) => "mesh",
        Ray(_) => "ray",
        XLine(_) => "xline",
        MLine(_) => "mline",
        Viewport(_) => "viewport",
        RasterImage(_) => "rasterimage",
        Wipeout(_) => "wipeout",
        Underlay(_) => "underlay",
        Shape(_) => "shape",
        Table(_) => "table",
        AttributeDefinition(_) => "attdef",
        AttributeEntity(_) => "attrib",
        Ole2Frame(_) => "ole2frame",
        Seqend(_) => "seqend",
        Unknown(_) => "unknown",
    }
    .to_string()
}

pub(super) fn title_case_word(value: &str) -> String {
    let mut chars = value.chars();
    match chars.next() {
        Some(first) => {
            let mut out = first.to_uppercase().collect::<String>();
            out.push_str(chars.as_str());
            out
        }
        None => String::new(),
    }
}

// ── Window icon ────────────────────────────────────────────────────────────

/// Builds a 32×32 RGBA icon: red background with OCS drawn in white pixels.
pub(super) fn build_window_icon() -> Vec<u8> {
    const W: usize = 32;
    const SZ: usize = W * W * 4;

    let bg = [176u8, 48, 32, 255];
    let fg = [255u8, 255, 255, 255];

    let mut px = vec![0u8; SZ];
    for i in 0..W * W {
        px[i * 4..i * 4 + 4].copy_from_slice(&bg);
    }

    fn stroke(px: &mut Vec<u8>, ax: i32, ay: i32, bx: i32, by: i32, fg: [u8; 4]) {
        let steps = ((bx - ax).abs().max((by - ay).abs()) * 3).max(1);
        for s in 0..=steps {
            let t = s as f32 / steps as f32;
            let cx = ax as f32 + (bx - ax) as f32 * t;
            let cy = ay as f32 + (by - ay) as f32 * t;
            for dy in -1i32..=1 {
                for dx in -1i32..=1 {
                    let ix = cx.round() as i32 + dx;
                    let iy = cy.round() as i32 + dy;
                    if ix >= 0 && ix < W as i32 && iy >= 0 && iy < W as i32 {
                        let idx = (iy as usize * W + ix as usize) * 4;
                        px[idx..idx + 4].copy_from_slice(&fg);
                    }
                }
            }
        }
    }

    // O
    stroke(&mut px, 3, 6, 9, 6, fg);
    stroke(&mut px, 3, 25, 9, 25, fg);
    stroke(&mut px, 3, 6, 3, 25, fg);
    stroke(&mut px, 9, 6, 9, 25, fg);
    // C
    stroke(&mut px, 12, 6, 18, 6, fg);
    stroke(&mut px, 12, 25, 18, 25, fg);
    stroke(&mut px, 12, 6, 12, 25, fg);
    // S
    stroke(&mut px, 21, 6, 27, 6, fg);
    stroke(&mut px, 21, 6, 21, 15, fg);
    stroke(&mut px, 21, 15, 27, 15, fg);
    stroke(&mut px, 27, 15, 27, 25, fg);
    stroke(&mut px, 21, 25, 27, 25, fg);

    px
}
