// Array dropdown — ribbon definition + interactive commands.
//
// ARRAYRECT (AR):
//   Rectangular array: row/column counts and spacing collected via text input.
//   1. Row count → 2. Column count → 3. Row spacing → 4. Column spacing
//   → Returns BatchCopy with a grid of Translate transforms.
//
// ARRAYPATH:
//   Path array: copies placed at equal intervals along a curve/line.
//   (pending geometry engine support — stub)
//
// ARRAYPOLAR:
//   Polar array: copies rotated around a center point by a total angle.
//   1. Center point → 2. Item count (text) → 3. Total angle in degrees (text)

use acadrust::Handle;
use glam::Vec3;

use crate::command::{CadCommand, CmdResult, EntityTransform};
use crate::modules::home::defaults;
use crate::modules::IconKind;
use crate::scene::wire_model::WireModel;

// ── Dropdown constants ─────────────────────────────────────────────────────

pub const DROPDOWN_ID: &str = "array_type";
pub const ICON: IconKind = IconKind::Svg(include_bytes!("../../../../assets/icons/array_rect.svg"));

pub const DROPDOWN_ITEMS: &[(&str, &str, IconKind)] = &[
    (
        "ARRAYRECT",
        "Rectangular Array",
        IconKind::Svg(include_bytes!("../../../../assets/icons/array_rect.svg")),
    ),
    (
        "ARRAYPATH",
        "Path Array",
        IconKind::Svg(include_bytes!("../../../../assets/icons/array_path.svg")),
    ),
    (
        "ARRAYPOLAR",
        "Polar Array",
        IconKind::Svg(include_bytes!("../../../../assets/icons/array_polar.svg")),
    ),
];

// ── Rectangular Array ──────────────────────────────────────────────────────

#[derive(Debug, Clone, Copy)]
enum RectStep {
    Rows,
    Cols { rows: u32 },
    RowSp { rows: u32, cols: u32 },
    ColSp { rows: u32, cols: u32, row_sp: f32 },
}

pub struct ArrayRectCommand {
    handles: Vec<Handle>,
    wire_models: Vec<WireModel>,
    step: RectStep,
    default_rows: u32,
    default_cols: u32,
    default_row_sp: f32,
    default_col_sp: f32,
}

impl ArrayRectCommand {
    pub fn new(handles: Vec<Handle>, wire_models: Vec<WireModel>) -> Self {
        Self {
            handles,
            wire_models,
            step: RectStep::Rows,
            default_rows: defaults::get_array_rows() as u32,
            default_cols: defaults::get_array_cols() as u32,
            default_row_sp: defaults::get_array_row_sp(),
            default_col_sp: defaults::get_array_col_sp(),
        }
    }

    fn build_transforms(rows: u32, cols: u32, row_sp: f32, col_sp: f32) -> Vec<EntityTransform> {
        let mut t = Vec::new();
        for r in 0..rows {
            for c in 0..cols {
                if r == 0 && c == 0 {
                    continue;
                }
                t.push(EntityTransform::Translate(Vec3::new(
                    col_sp * c as f32,
                    row_sp * r as f32,
                    0.0,
                )));
            }
        }
        t
    }
}

impl CadCommand for ArrayRectCommand {
    fn name(&self) -> &'static str {
        "ARRAYRECT"
    }

    fn prompt(&self) -> String {
        match self.step {
            RectStep::Rows => format!("ARRAYRECT  Enter row count <{}>:", self.default_rows),
            RectStep::Cols { rows } => format!(
                "ARRAYRECT  Enter column count <{}>  [{rows} rows]:",
                self.default_cols
            ),
            RectStep::RowSp { rows, cols } => format!(
                "ARRAYRECT  Row spacing <{:.0}>  [{rows}×{cols}]:",
                self.default_row_sp
            ),
            RectStep::ColSp { rows, cols, row_sp } => format!(
                "ARRAYRECT  Column spacing <{:.0}>  [{rows}×{cols}, row={row_sp:.0}]:",
                self.default_col_sp
            ),
        }
    }

    fn wants_text_input(&self) -> bool {
        true
    }

    fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
        let t = text.trim().replace(',', ".");
        let t = t.as_str();
        match self.step {
            RectStep::Rows => {
                let rows = if t.is_empty() {
                    self.default_rows
                } else {
                    let v = t.parse::<u32>().unwrap_or(self.default_rows).max(1);
                    defaults::set_array_rows(v as f32);
                    self.default_rows = v;
                    v
                };
                self.step = RectStep::Cols { rows };
                None
            }
            RectStep::Cols { rows } => {
                let cols = if t.is_empty() {
                    self.default_cols
                } else {
                    let v = t.parse::<u32>().unwrap_or(self.default_cols).max(1);
                    defaults::set_array_cols(v as f32);
                    self.default_cols = v;
                    v
                };
                self.step = RectStep::RowSp { rows, cols };
                None
            }
            RectStep::RowSp { rows, cols } => {
                let row_sp = if t.is_empty() {
                    self.default_row_sp
                } else {
                    let v = t.parse::<f32>().unwrap_or(self.default_row_sp);
                    defaults::set_array_row_sp(v);
                    self.default_row_sp = v;
                    v
                };
                self.step = RectStep::ColSp { rows, cols, row_sp };
                None
            }
            RectStep::ColSp { rows, cols, row_sp } => {
                let col_sp = if t.is_empty() {
                    self.default_col_sp
                } else {
                    let v = t.parse::<f32>().unwrap_or(self.default_col_sp);
                    defaults::set_array_col_sp(v);
                    v
                };
                Some(CmdResult::BatchCopy(
                    self.handles.clone(),
                    Self::build_transforms(rows, cols, row_sp, col_sp),
                ))
            }
        }
    }

    fn on_preview_wires(&mut self, _pt: Vec3) -> Vec<WireModel> {
        let (rows, cols, row_sp, col_sp) = match self.step {
            RectStep::Rows => (
                self.default_rows,
                self.default_cols,
                self.default_row_sp,
                self.default_col_sp,
            ),
            RectStep::Cols { rows } => (
                rows,
                self.default_cols,
                self.default_row_sp,
                self.default_col_sp,
            ),
            RectStep::RowSp { rows, cols } => {
                (rows, cols, self.default_row_sp, self.default_col_sp)
            }
            RectStep::ColSp { rows, cols, row_sp } => (rows, cols, row_sp, self.default_col_sp),
        };
        Self::build_transforms(rows, cols, row_sp, col_sp)
            .iter()
            .flat_map(|t| {
                if let EntityTransform::Translate(delta) = t {
                    self.wire_models
                        .iter()
                        .map(|w| w.translated(*delta))
                        .collect::<Vec<_>>()
                } else {
                    vec![]
                }
            })
            .collect()
    }

    fn on_point(&mut self, _pt: Vec3) -> CmdResult {
        CmdResult::NeedPoint
    }

    fn on_enter(&mut self) -> CmdResult {
        // Enter with empty input = use default for current step
        self.on_text_input("").map_or(CmdResult::NeedPoint, |r| r)
    }

    fn on_escape(&mut self) -> CmdResult {
        CmdResult::Cancel
    }
}

// ── Polar Array ────────────────────────────────────────────────────────────

enum PolarStep {
    Center,
    Count { center: Vec3 },
    Angle { center: Vec3, count: u32 },
}

pub struct ArrayPolarCommand {
    handles: Vec<Handle>,
    wire_models: Vec<WireModel>,
    step: PolarStep,
    default_count: u32,
    default_angle: f32,
}

impl ArrayPolarCommand {
    pub fn new(handles: Vec<Handle>, wire_models: Vec<WireModel>) -> Self {
        Self {
            handles,
            wire_models,
            step: PolarStep::Center,
            default_count: defaults::get_array_p_count() as u32,
            default_angle: defaults::get_array_p_angle(),
        }
    }
}

impl CadCommand for ArrayPolarCommand {
    fn name(&self) -> &'static str {
        "ARRAYPOLAR"
    }

    fn prompt(&self) -> String {
        match &self.step {
            PolarStep::Center => format!(
                "ARRAYPOLAR  Specify center point  [{} objects]:",
                self.handles.len()
            ),
            PolarStep::Count { .. } => {
                format!("ARRAYPOLAR  Enter item count <{}>:", self.default_count)
            }
            PolarStep::Angle { count, .. } => format!(
                "ARRAYPOLAR  Enter total angle in degrees <{:.0}>  [{count} items]:",
                self.default_angle
            ),
        }
    }

    fn wants_text_input(&self) -> bool {
        matches!(self.step, PolarStep::Count { .. } | PolarStep::Angle { .. })
    }

    fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
        let t = text.trim().replace(',', ".");
        let t = t.as_str();
        match &self.step {
            PolarStep::Count { center } => {
                let center = *center;
                let count = if t.is_empty() {
                    self.default_count
                } else {
                    let v = t.parse::<u32>().unwrap_or(self.default_count).max(2);
                    defaults::set_array_p_count(v as f32);
                    self.default_count = v;
                    v
                };
                self.step = PolarStep::Angle { center, count };
                None
            }
            PolarStep::Angle { center, count } => {
                let center = *center;
                let count = *count;
                let total_deg = if t.is_empty() {
                    self.default_angle
                } else {
                    let v = t.parse::<f32>().unwrap_or(self.default_angle);
                    defaults::set_array_p_angle(v);
                    v
                };
                let step_rad = total_deg.to_radians() / count as f32;
                let transforms = (1..count)
                    .map(|n| EntityTransform::Rotate {
                        center,
                        angle_rad: step_rad * n as f32,
                    })
                    .collect();
                Some(CmdResult::BatchCopy(self.handles.clone(), transforms))
            }
            _ => None,
        }
    }

    fn on_preview_wires(&mut self, pt: Vec3) -> Vec<WireModel> {
        let (center, count, total_deg) = match &self.step {
            PolarStep::Center => (pt, self.default_count, self.default_angle),
            PolarStep::Count { center } => (*center, self.default_count, self.default_angle),
            PolarStep::Angle { center, count } => (*center, *count, self.default_angle),
        };
        let step_rad = total_deg.to_radians() / count as f32;
        let mut out: Vec<WireModel> = (1..count)
            .flat_map(|n| {
                let angle_rad = step_rad * n as f32;
                self.wire_models
                    .iter()
                    .map(move |w| w.rotated(center, angle_rad))
            })
            .collect();
        // Rubber-band from center to cursor while picking the center point.
        if matches!(self.step, PolarStep::Center) {
            out.push(WireModel::solid(
                "rubber_band".into(),
                vec![[center.x, center.y, center.z], [pt.x, pt.y, pt.z]],
                WireModel::CYAN,
                false,
            ));
        }
        out
    }

    fn on_point(&mut self, pt: Vec3) -> CmdResult {
        if let PolarStep::Center = self.step {
            self.step = PolarStep::Count { center: pt };
        }
        CmdResult::NeedPoint
    }

    fn on_enter(&mut self) -> CmdResult {
        self.on_text_input("").map_or(CmdResult::NeedPoint, |r| r)
    }

    fn on_escape(&mut self) -> CmdResult {
        CmdResult::Cancel
    }
}

// ── Path Array ─────────────────────────────────────────────────────────────
//
// ARRAYPATH:
//   Copies selected objects at equal arc-length intervals along a path entity.
//   1. Select path entity (Line, Arc, Circle, LwPolyline)
//   2. Enter item count (total, including the original at the path start)
//   → Returns BatchCopy with Translate transforms derived from path samples.

use acadrust::EntityType;
use std::f32::consts::TAU as FTAU;

// ── Path geometry helpers ──────────────────────────────────────────────────

/// Tessellate an LwPolyline.
fn lw_dense_pts(p: &acadrust::entities::LwPolyline) -> Vec<Vec3> {
    let z = p.elevation as f32;
    let verts = &p.vertices;
    if verts.is_empty() {
        return vec![];
    }
    let n = verts.len();
    let segs = if p.is_closed { n } else { n.saturating_sub(1) };
    let mut out: Vec<Vec3> = vec![];

    for i in 0..segs {
        let v0 = &verts[i];
        let v1 = &verts[(i + 1) % n];
        let x0 = v0.location.x as f32;
        let y0 = v0.location.y as f32;
        let x1 = v1.location.x as f32;
        let y1 = v1.location.y as f32;

        if out.is_empty() {
            out.push(Vec3::new(x0, y0, z));
        }

        let bulge = v0.bulge as f32;
        if bulge.abs() < 1e-9 {
            out.push(Vec3::new(x1, y1, z));
        } else {
            let dx = x1 - x0;
            let dy = y1 - y0;
            let d = (dx * dx + dy * dy).sqrt();
            if d < 1e-9 {
                out.push(Vec3::new(x1, y1, z));
                continue;
            }
            let angle = 4.0 * bulge.atan();
            let r = (d * 0.5) / (angle * 0.5).sin().abs();
            let mx = (x0 + x1) * 0.5;
            let my = (y0 + y1) * 0.5;
            let inv = 1.0 / d;
            let (px, py) = (-dy * inv, dx * inv);
            let sign = if bulge > 0.0 { 1.0f32 } else { -1.0 };
            let h_val = r - (r * r - d * d * 0.25).max(0.0).sqrt();
            let (cx, cy) = (mx - sign * px * (r - h_val), my - sign * py * (r - h_val));
            let a0 = (y0 - cy).atan2(x0 - cx);
            let a1 = (y1 - cy).atan2(x1 - cx);
            // Signed span: CCW for bulge > 0, CW (negative) for bulge < 0.
            let span = if bulge > 0.0 {
                let s = a1 - a0;
                if s <= 0.0 {
                    s + FTAU
                } else {
                    s
                }
            } else {
                let s = a1 - a0;
                if s >= 0.0 {
                    s - FTAU
                } else {
                    s
                }
            };
            let steps = ((r * span.abs() / 2.0).ceil() as usize).clamp(4, 64);
            for j in 1..=steps {
                let ang = a0 + span * (j as f32 / steps as f32);
                out.push(Vec3::new(cx + r * ang.cos(), cy + r * ang.sin(), z));
            }
        }
    }
    out
}

/// Walk `pts` (ordered) and return `count` points at equal arc-length spacing.
fn subsample_equidistant(pts: &[Vec3], count: usize) -> Vec<Vec3> {
    if count == 0 {
        return vec![];
    }
    if pts.len() < 2 {
        return vec![pts.first().copied().unwrap_or(Vec3::ZERO); count];
    }

    let mut cum = vec![0.0f32; pts.len()];
    for i in 1..pts.len() {
        cum[i] = cum[i - 1] + pts[i].distance(pts[i - 1]);
    }
    let total = *cum.last().unwrap();
    if total < 1e-9 {
        return vec![pts[0]; count];
    }

    let mut out = Vec::with_capacity(count);
    let mut seg = 0usize;
    for i in 0..count {
        let target = if count > 1 {
            total * i as f32 / (count - 1) as f32
        } else {
            0.0
        };
        while seg + 2 < pts.len() && cum[seg + 1] < target - 1e-9 {
            seg += 1;
        }
        let seg_len = cum[seg + 1] - cum[seg];
        let t = if seg_len > 1e-9 {
            (target - cum[seg]) / seg_len
        } else {
            0.0
        };
        out.push(pts[seg].lerp(pts[seg + 1], t.clamp(0.0, 1.0)));
    }
    out
}

// ── State machine ──────────────────────────────────────────────────────────

enum PathStep {
    SelectPath,
    Count { path_entity: EntityType },
}

pub struct ArrayPathCommand {
    handles: Vec<Handle>,
    wire_models: Vec<WireModel>,
    all_entities: Vec<EntityType>,
    step: PathStep,
    default_count: u32,
}

impl ArrayPathCommand {
    pub fn new(
        handles: Vec<Handle>,
        wire_models: Vec<WireModel>,
        all_entities: Vec<EntityType>,
    ) -> Self {
        Self {
            handles,
            wire_models,
            all_entities,
            step: PathStep::SelectPath,
            default_count: defaults::get_array_path_count() as u32,
        }
    }

    /// Sample `count` evenly-spaced points along `entity`.
    fn sample_path(entity: &EntityType, count: usize) -> Vec<Vec3> {
        if count == 0 {
            return vec![];
        }
        let fn_norm = |x: f32| -> f32 { ((x % FTAU) + FTAU) % FTAU };
        match entity {
            EntityType::Line(l) => {
                let p0 = Vec3::new(l.start.x as f32, l.start.y as f32, 0.0);
                let p1 = Vec3::new(l.end.x as f32, l.end.y as f32, 0.0);
                let d = (count - 1).max(1) as f32;
                (0..count).map(|i| p0.lerp(p1, i as f32 / d)).collect()
            }
            EntityType::Arc(a) => {
                let (cx, cy, r) = (a.center.x as f32, a.center.y as f32, a.radius as f32);
                let a0 = a.start_angle.to_radians() as f32;
                let a1 = a.end_angle.to_radians() as f32;
                let span = {
                    let s = fn_norm(a1) - fn_norm(a0);
                    if s <= 0.0 {
                        s + FTAU
                    } else {
                        s
                    }
                };
                let d = (count - 1).max(1) as f32;
                (0..count)
                    .map(|i| {
                        let ang = fn_norm(a0) + span * (i as f32 / d);
                        Vec3::new(cx + r * ang.cos(), cy + r * ang.sin(), 0.0)
                    })
                    .collect()
            }
            EntityType::Circle(c) => {
                let (cx, cy, r) = (c.center.x as f32, c.center.y as f32, c.radius as f32);
                (0..count)
                    .map(|i| {
                        let ang = i as f32 / count as f32 * FTAU;
                        Vec3::new(cx + r * ang.cos(), cy + r * ang.sin(), 0.0)
                    })
                    .collect()
            }
            EntityType::LwPolyline(p) => subsample_equidistant(&lw_dense_pts(p), count),
            _ => vec![Vec3::ZERO; count],
        }
    }

    /// Build Translate transforms: delta from pts[0] to each subsequent point.
    fn build_transforms(pts: &[Vec3]) -> Vec<EntityTransform> {
        if pts.len() < 2 {
            return vec![];
        }
        let origin = pts[0];
        pts[1..]
            .iter()
            .map(|&p| EntityTransform::Translate(p - origin))
            .collect()
    }
}

impl CadCommand for ArrayPathCommand {
    fn name(&self) -> &'static str {
        "ARRAYPATH"
    }

    fn prompt(&self) -> String {
        match &self.step {
            PathStep::SelectPath => format!(
                "ARRAYPATH  Select path entity  [{} objects]:",
                self.handles.len()
            ),
            PathStep::Count { .. } => {
                format!("ARRAYPATH  Enter item count <{}>:", self.default_count)
            }
        }
    }

    fn needs_entity_pick(&self) -> bool {
        matches!(self.step, PathStep::SelectPath)
    }

    fn wants_text_input(&self) -> bool {
        matches!(self.step, PathStep::Count { .. })
    }

    fn on_entity_pick(&mut self, handle: Handle, _pt: Vec3) -> CmdResult {
        if handle.is_null() || self.handles.contains(&handle) {
            return CmdResult::NeedPoint;
        }
        if let Some(entity) = self
            .all_entities
            .iter()
            .find(|e| e.common().handle == handle)
            .cloned()
        {
            self.step = PathStep::Count {
                path_entity: entity,
            };
        }
        CmdResult::NeedPoint
    }

    fn on_hover_entity(&mut self, handle: Handle, _pt: Vec3) -> Vec<WireModel> {
        if handle.is_null() || self.handles.contains(&handle) {
            return vec![];
        }
        if !matches!(self.step, PathStep::SelectPath) {
            return vec![];
        }
        if let Some(entity) = self
            .all_entities
            .iter()
            .find(|e| e.common().handle == handle)
        {
            let pts = Self::sample_path(entity, 64);
            if pts.len() >= 2 {
                return vec![WireModel::solid(
                    "arraypath_hover".into(),
                    pts.iter().map(|p| [p.x, p.y, p.z]).collect(),
                    WireModel::CYAN,
                    false,
                )];
            }
        }
        vec![]
    }

    fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
        let PathStep::Count { path_entity } = &self.step else {
            return None;
        };
        let t = text.trim().replace(',', ".");
        let count = if t.is_empty() {
            self.default_count
        } else {
            let v = t.parse::<u32>().unwrap_or(self.default_count).max(2);
            defaults::set_array_path_count(v as f32);
            self.default_count = v;
            v
        };
        let pts = Self::sample_path(path_entity, count as usize);
        let transforms = Self::build_transforms(&pts);
        if transforms.is_empty() {
            return Some(CmdResult::Cancel);
        }
        Some(CmdResult::BatchCopy(self.handles.clone(), transforms))
    }

    fn on_preview_wires(&mut self, _pt: Vec3) -> Vec<WireModel> {
        let PathStep::Count { path_entity } = &self.step else {
            return vec![];
        };
        let pts = Self::sample_path(path_entity, self.default_count as usize);
        let transforms = Self::build_transforms(&pts);
        transforms
            .iter()
            .flat_map(|t| {
                if let EntityTransform::Translate(delta) = t {
                    self.wire_models
                        .iter()
                        .map(|w| w.translated(*delta))
                        .collect::<Vec<_>>()
                } else {
                    vec![]
                }
            })
            .collect()
    }

    fn on_point(&mut self, _pt: Vec3) -> CmdResult {
        CmdResult::NeedPoint
    }

    fn on_enter(&mut self) -> CmdResult {
        self.on_text_input("").map_or(CmdResult::NeedPoint, |r| r)
    }

    fn on_escape(&mut self) -> CmdResult {
        CmdResult::Cancel
    }
}
