// TORIENT tool — interactive command.
//
// Command:  TORIENT
//   Requires at least one entity selected before starting.
//   Step 1: Wait for an angle (numerical input), Enter (Most Readable), or pick first point.
//   Step 2: If first point picked, pick second point to define angle vector.

use acadrust::Handle;
use glam::DVec3;

use crate::command::{CadCommand, CmdResult, DynField};
use crate::scene::model::wire_model::WireModel;
use acadrust::EntityType;

// ── Command implementation ─────────────────────────────────────────────────

enum Step {
    AngleOrFirstPoint,
    SecondPoint { first_point: DVec3 },
}

pub struct TorientCommand {
    entities: Vec<(Handle, EntityType)>,
    view_twist: f64,
    step: Step,
}

impl TorientCommand {
    pub fn new(entities: Vec<(Handle, EntityType)>, view_twist: f64) -> Self {
        Self {
            entities,
            view_twist,
            step: Step::AngleOrFirstPoint,
        }
    }

    fn commit_angle(&self, new_angle_rad: Option<f64>) -> CmdResult {
        let mut replacements = Vec::new();

        for (handle, entity) in &self.entities {
            let mut new_entity = entity.clone();
            let mut changed = false;

            match &mut new_entity {
                EntityType::Text(text) => {
                    let angle = new_angle_rad.unwrap_or_else(|| most_readable_angle(text.rotation, self.view_twist));
                    text.rotation = angle;
                    changed = true;
                }
                EntityType::MText(mtext) => {
                    let angle = new_angle_rad.unwrap_or_else(|| most_readable_angle(mtext.rotation as f64, self.view_twist));
                    mtext.rotation = angle as f64;
                    changed = true;
                }
                EntityType::AttributeDefinition(attdef) => {
                    let angle = new_angle_rad.unwrap_or_else(|| most_readable_angle(attdef.rotation, self.view_twist));
                    attdef.rotation = angle;
                    changed = true;
                }
                EntityType::Insert(insert) => {
                    let mut block_changed = false;
                    for attr in &mut insert.attributes {
                        let angle = new_angle_rad.unwrap_or_else(|| most_readable_angle(attr.rotation, self.view_twist));
                        attr.rotation = angle;
                        block_changed = true;
                    }
                    if block_changed {
                        changed = true;
                    }
                }
                _ => {}
            }

            if changed {
                replacements.push((*handle, vec![new_entity]));
            }
        }

        if replacements.is_empty() {
            CmdResult::Cancel
        } else {
            CmdResult::ReplaceMany(replacements, vec![])
        }
    }
}

fn most_readable_angle(wcs_angle_rad: f64, view_twist: f64) -> f64 {
    let two_pi = 2.0 * std::f64::consts::PI;
    
    // Calculate the angle as it appears on screen
    let mut screen_angle = (wcs_angle_rad - view_twist) % two_pi;
    if screen_angle < 0.0 {
        screen_angle += two_pi;
    }

    // "Most Readable" logic: if upside down on screen (angle > 90 and <= 270 deg)
    let half_pi = std::f64::consts::PI / 2.0;
    let three_half_pi = 3.0 * std::f64::consts::PI / 2.0;

    let mut final_wcs_angle = wcs_angle_rad;
    if screen_angle > half_pi + 1e-6 && screen_angle <= three_half_pi + 1e-6 {
        final_wcs_angle += std::f64::consts::PI;
    }
    
    // Normalize final WCS angle
    final_wcs_angle %= two_pi;
    if final_wcs_angle < 0.0 {
        final_wcs_angle += two_pi;
    }
    final_wcs_angle
}

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

    fn prompt(&self) -> String {
        match &self.step {
            Step::AngleOrFirstPoint => "TORIENT  New absolute rotation <Most Readable>:".into(),
            Step::SecondPoint { .. } => "TORIENT  Specify second point:".into(),
        }
    }

    fn on_point(&mut self, pt: DVec3) -> CmdResult {
        match self.step {
            Step::AngleOrFirstPoint => {
                self.step = Step::SecondPoint { first_point: pt };
                CmdResult::NeedPoint
            }
            Step::SecondPoint { first_point } => {
                let dx = pt.x - first_point.x;
                let dy = pt.y - first_point.y;
                let angle = dy.atan2(dx);
                self.commit_angle(Some(angle))
            }
        }
    }

    fn on_enter(&mut self) -> CmdResult {
        match self.step {
            Step::AngleOrFirstPoint => self.commit_angle(None),
            Step::SecondPoint { .. } => CmdResult::Cancel,
        }
    }

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

    fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
        if let Step::AngleOrFirstPoint = self.step {
            let deg: f64 = text.trim().replace(',', ".").parse().ok()?;
            Some(self.commit_angle(Some(deg.to_radians())))
        } else {
            None
        }
    }

    fn on_preview_wires(&mut self, pt: DVec3) -> Vec<WireModel> {
        let pt = pt.as_vec3();
        if let Step::SecondPoint { first_point } = self.step {
            let first_point = first_point.as_vec3();
            vec![WireModel::solid(
                "rubber_band".into(),
                vec![[first_point.x, first_point.y, first_point.z], [pt.x, pt.y, pt.z]],
                WireModel::CYAN,
                false,
            )]
        } else {
            vec![]
        }
    }

    fn dyn_field(&self) -> DynField {
        match self.step {
            Step::AngleOrFirstPoint => DynField::Angle,
            Step::SecondPoint { .. } => DynField::Point,
        }
    }

    fn dyn_spec(&self) -> Option<crate::command::DynSpec> {
        None
    }

    fn dyn_commit_as_text(&self) -> bool {
        matches!(self.step, Step::AngleOrFirstPoint)
    }
}

// ── Autocomplete registry ─────────────────────────────────
inventory::submit!(crate::command::CommandRegistration { names: &["TORIENT"] });
