use acadrust::entities::{
    ArcAlignedTextData, ExtendedEntity, ExtendedEntityData, GeoPositionMarkerData,
    PointCloudData, PointCloudExData, RemoteTextData, SectionObjectData,
};
use acadrust::types::{Handle, Transform, Vector3};

use crate::command::EntityTransform;
use crate::entities::common::{
    center_grip, edit_angle_prop, edit_prop, parse_f64, ro_prop, square_grip,
};
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
use crate::scene::convert::acad_to_truck::{TruckEntity, TruckObject};
use crate::scene::model::object::{
    GripApply, GripDef, PropSection, PropValue, Property,
};
use crate::scene::model::wire_model::SnapHint;

const NAN: [f64; 3] = [f64::NAN; 3];

fn vector_text(value: Vector3) -> String {
    format!("{:.6}, {:.6}, {:.6}", value.x, value.y, value.z)
}

fn handle_text(handle: Handle) -> String {
    if handle.is_null() {
        "None".to_string()
    } else {
        format!("{:X}", handle.value())
    }
}

fn text_prop(label: &'static str, field: &'static str, value: &str) -> Property {
    Property {
        label: label.into(),
        field,
        value: PropValue::EditText(value.to_string()),
    }
}

fn bool_prop(label: &'static str, field: &'static str, value: bool) -> Property {
    Property {
        label: label.into(),
        field,
        value: PropValue::BoolToggle { field, value },
    }
}

fn push_segment(points: &mut Vec<[f64; 3]>, first: [f64; 3], second: [f64; 3]) {
    if !points.is_empty() {
        points.push(NAN);
    }
    points.push(first);
    points.push(second);
}

fn push_chain(points: &mut Vec<[f64; 3]>, chain: impl IntoIterator<Item = [f64; 3]>) {
    let chain: Vec<_> = chain.into_iter().collect();
    if chain.len() < 2 {
        return;
    }
    if !points.is_empty() {
        points.push(NAN);
    }
    points.extend(chain);
}

fn push_box(points: &mut Vec<[f64; 3]>, min: Vector3, max: Vector3) {
    let corners = [
        [min.x, min.y, min.z],
        [max.x, min.y, min.z],
        [max.x, max.y, min.z],
        [min.x, max.y, min.z],
        [min.x, min.y, max.z],
        [max.x, min.y, max.z],
        [max.x, max.y, max.z],
        [min.x, max.y, max.z],
    ];
    for (a, b) in [
        (0, 1),
        (1, 2),
        (2, 3),
        (3, 0),
        (4, 5),
        (5, 6),
        (6, 7),
        (7, 4),
        (0, 4),
        (1, 5),
        (2, 6),
        (3, 7),
    ] {
        push_segment(points, corners[a], corners[b]);
    }
}

fn normalized(value: Vector3, fallback: Vector3) -> Vector3 {
    let length = value.length();
    if length > 1e-12 {
        value / length
    } else {
        fallback
    }
}

fn plane_axes(normal: Vector3) -> (Vector3, Vector3, Vector3) {
    let normal = normalized(normal, Vector3::UNIT_Z);
    let (x, y) =
        crate::scene::view::transform::ocs_axes((normal.x, normal.y, normal.z));
    (
        Vector3::new(x.0, x.1, x.2),
        Vector3::new(y.0, y.1, y.2),
        normal,
    )
}

fn add_scaled(origin: Vector3, x: Vector3, sx: f64, y: Vector3, sy: f64) -> [f64; 3] {
    [
        origin.x + x.x * sx + y.x * sy,
        origin.y + x.y * sx + y.y * sy,
        origin.z + x.z * sx + y.z * sy,
    ]
}

fn append_planar_text(
    points: &mut Vec<[f64; 3]>,
    text: &str,
    font: &str,
    origin: Vector3,
    normal: Vector3,
    rotation: f64,
    height: f64,
    width_factor: f64,
) {
    if text.is_empty() || height.abs() < 1e-12 {
        return;
    }
    let (axis_x, axis_y, _) = plane_axes(normal);
    let (cos, sin) = rotation.sin_cos();
    let baseline = axis_x * cos + axis_y * sin;
    let upward = axis_y * cos - axis_x * sin;
    let (strokes, _) = crate::scene::text::lff::tessellate_text_ex(
        [0.0, 0.0],
        height.abs() as f32,
        0.0,
        width_factor as f32,
        0.0,
        if font.trim().is_empty() { "standard" } else { font },
        text,
    );
    for stroke in strokes {
        push_chain(
            points,
            stroke
                .into_iter()
                .map(|[x, y]| add_scaled(origin, baseline, x as f64, upward, y as f64)),
        );
    }
}

fn append_arc_aligned_text(points: &mut Vec<[f64; 3]>, data: &ArcAlignedTextData) {
    if data.text.is_empty() || data.radius.abs() < 1e-12 || data.text_size.abs() < 1e-12 {
        return;
    }
    let (axis_x, axis_y, _) = plane_axes(data.normal);
    let direction = if data.reverse || data.text_direction < 0 {
        -1.0
    } else {
        1.0
    };
    let mut angle = if direction > 0.0 {
        data.start_angle
    } else {
        data.end_angle
    };
    let advance = data.text_size.abs()
        * data.x_scale.abs().max(0.01)
        * data.character_spacing.abs().max(0.01)
        * 0.72;
    let delta = direction * advance / data.radius.abs().max(1e-9);
    let radius = data.radius + data.offset_from_arc;
    let font = if data.font_name.trim().is_empty() {
        data.style_name.as_str()
    } else {
        data.font_name.as_str()
    };
    for character in data.text.chars() {
        let radial = axis_x * angle.cos() + axis_y * angle.sin();
        let tangent = (axis_y * angle.cos() - axis_x * angle.sin()) * direction;
        let origin = data.center + radial * radius;
        let (strokes, _) = crate::scene::text::lff::tessellate_text_ex(
            [0.0, 0.0],
            data.text_size.abs() as f32,
            0.0,
            data.x_scale.abs().max(0.01) as f32,
            0.0,
            if font.trim().is_empty() { "standard" } else { font },
            &character.to_string(),
        );
        for stroke in strokes {
            push_chain(
                points,
                stroke.into_iter().map(|[x, y]| {
                    add_scaled(origin, tangent, x as f64, radial, y as f64)
                }),
            );
        }
        angle += delta;
    }
}

fn section_lines(data: &SectionObjectData) -> Vec<[f64; 3]> {
    let mut points = Vec::new();
    push_chain(
        &mut points,
        data.vertices.iter().map(|p| [p.x, p.y, p.z]),
    );
    push_chain(
        &mut points,
        data.back_line_vertices.iter().map(|p| [p.x, p.y, p.z]),
    );
    points
}

fn remote_text_lines(data: &RemoteTextData) -> Vec<[f64; 3]> {
    let mut points = Vec::new();
    append_planar_text(
        &mut points,
        &data.text,
        &data.style_name,
        data.position,
        data.normal,
        data.rotation,
        data.height,
        1.0,
    );
    points
}

fn geo_marker_lines(data: &GeoPositionMarkerData) -> Vec<[f64; 3]> {
    let mut points = Vec::new();
    let radius = data.radius.abs().max(1e-6);
    let count = 32;
    push_chain(
        &mut points,
        (0..=count).map(|index| {
            let angle = std::f64::consts::TAU * index as f64 / count as f64;
            [
                data.position.x + radius * angle.cos(),
                data.position.y + radius * angle.sin(),
                data.position.z,
            ]
        }),
    );
    push_segment(
        &mut points,
        [data.position.x - radius, data.position.y, data.position.z],
        [data.position.x + radius, data.position.y, data.position.z],
    );
    push_segment(
        &mut points,
        [data.position.x, data.position.y - radius, data.position.z],
        [data.position.x, data.position.y + radius, data.position.z],
    );
    let label = data
        .embedded_mtext
        .as_ref()
        .filter(|_| data.mtext_visible)
        .map(|text| text.value.as_str())
        .filter(|value| !value.trim().is_empty())
        .unwrap_or(data.notes.as_str());
    if !label.trim().is_empty() {
        let origin = Vector3::new(
            data.position.x + radius + data.landing_gap.max(0.0),
            data.position.y,
            data.position.z,
        );
        push_segment(
            &mut points,
            [
                data.position.x + radius,
                data.position.y,
                data.position.z,
            ],
            [origin.x, origin.y, origin.z],
        );
        append_planar_text(
            &mut points,
            label,
            "standard",
            origin,
            Vector3::UNIT_Z,
            0.0,
            radius * 0.8,
            1.0,
        );
    }
    points
}

fn point_cloud_lines(data: &PointCloudData) -> Vec<[f64; 3]> {
    let mut points = Vec::new();
    push_box(&mut points, data.extents_min, data.extents_max);
    if data.show_clipping {
        for clip in &data.clippings {
            if clip.vertices.len() < 2 {
                continue;
            }
            for z in [clip.z_min, clip.z_max] {
                let mut chain: Vec<[f64; 3]> = clip
                    .vertices
                    .iter()
                    .map(|point| [point.x, point.y, z])
                    .collect();
                chain.push(chain[0]);
                push_chain(&mut points, chain);
            }
        }
    }
    points
}

fn point_cloud_ex_lines(data: &PointCloudExData) -> Vec<[f64; 3]> {
    let mut points = Vec::new();
    push_box(&mut points, data.extents_min, data.extents_max);
    if data.show_cropping {
        for crop in &data.croppings {
            if crop.points.len() < 2 {
                continue;
            }
            let mut chain: Vec<[f64; 3]> =
                crop.points.iter().map(|p| [p.x, p.y, p.z]).collect();
            if crop.points.len() > 2 {
                chain.push(chain[0]);
            }
            push_chain(&mut points, chain);
        }
    }
    points
}

fn camera_lines(document: &acadrust::CadDocument, view_handle: Handle) -> Vec<[f64; 3]> {
    let Some(view) = document.views.iter().find(|view| view.handle == view_handle) else {
        return Vec::new();
    };
    let direction = normalized(view.direction, Vector3::UNIT_Z);
    let distance = view.direction.length().max(view.height.abs()).max(1.0);
    let eye = view.target + direction * distance;
    let (right, up, _) = plane_axes(direction);
    let half_width = view.width.abs().max(view.height.abs()) * 0.08;
    let half_height = view.height.abs().max(view.width.abs()) * 0.06;
    let corners = [
        view.target + right * half_width + up * half_height,
        view.target - right * half_width + up * half_height,
        view.target - right * half_width - up * half_height,
        view.target + right * half_width - up * half_height,
    ];
    let mut points = Vec::new();
    for corner in corners {
        push_segment(
            &mut points,
            [eye.x, eye.y, eye.z],
            [corner.x, corner.y, corner.z],
        );
    }
    push_chain(
        &mut points,
        corners
            .into_iter()
            .chain(std::iter::once(corners[0]))
            .map(|p| [p.x, p.y, p.z]),
    );
    points
}

fn to_truck(entity: &ExtendedEntity, document: &acadrust::CadDocument) -> Option<TruckEntity> {
    let (points, snaps, keys): (
        Vec<[f64; 3]>,
        Vec<(glam::DVec3, SnapHint)>,
        Vec<[f64; 3]>,
    ) = match &entity.data {
        ExtendedEntityData::Camera { view_handle } => {
            let points = camera_lines(document, *view_handle);
            (points, Vec::new(), Vec::new())
        }
        ExtendedEntityData::SectionObject(data) => {
            let snaps = data
                .vertices
                .iter()
                .chain(data.back_line_vertices.iter())
                .map(|point| {
                    (
                        glam::DVec3::new(point.x, point.y, point.z),
                        SnapHint::Node,
                    )
                })
                .collect();
            let keys = data
                .vertices
                .iter()
                .chain(data.back_line_vertices.iter())
                .map(|point| [point.x, point.y, point.z])
                .collect();
            (section_lines(data), snaps, keys)
        }
        ExtendedEntityData::ArcAlignedText(data) => {
            let mut points = Vec::new();
            append_arc_aligned_text(&mut points, data);
            (
                points,
                vec![(
                    glam::DVec3::new(data.center.x, data.center.y, data.center.z),
                    SnapHint::Center,
                )],
                vec![[data.center.x, data.center.y, data.center.z]],
            )
        }
        ExtendedEntityData::RemoteText(data) => (
            remote_text_lines(data),
            vec![(
                glam::DVec3::new(data.position.x, data.position.y, data.position.z),
                SnapHint::Insertion,
            )],
            vec![[data.position.x, data.position.y, data.position.z]],
        ),
        ExtendedEntityData::GeoPositionMarker(data) => (
            geo_marker_lines(data),
            vec![(
                glam::DVec3::new(data.position.x, data.position.y, data.position.z),
                SnapHint::Node,
            )],
            vec![[data.position.x, data.position.y, data.position.z]],
        ),
        ExtendedEntityData::PointCloud(data) => (
            point_cloud_lines(data),
            vec![(
                glam::DVec3::new(data.origin.x, data.origin.y, data.origin.z),
                SnapHint::Insertion,
            )],
            vec![
                [data.extents_min.x, data.extents_min.y, data.extents_min.z],
                [data.extents_max.x, data.extents_max.y, data.extents_max.z],
            ],
        ),
        ExtendedEntityData::PointCloudEx(data) => (
            point_cloud_ex_lines(data),
            Vec::new(),
            vec![
                [data.extents_min.x, data.extents_min.y, data.extents_min.z],
                [data.extents_max.x, data.extents_max.y, data.extents_max.z],
            ],
        ),
        _ => return None,
    };
    if points.len() < 2 {
        return None;
    }
    Some(TruckEntity {
        object: TruckObject::Lines(points),
        snap_pts: snaps,
        tangent_geoms: Vec::new(),
        key_vertices: keys,
        fill_tris: Vec::new(),
        pick_tris: Vec::new(),
    })
}

fn section_properties(data: &SectionObjectData) -> Vec<PropSection> {
    let vertices = data
        .vertices
        .iter()
        .enumerate()
        .map(|(index, point)| format!("{}: {}", index + 1, vector_text(*point)))
        .collect::<Vec<_>>()
        .join("\n");
    let back_vertices = data
        .back_line_vertices
        .iter()
        .enumerate()
        .map(|(index, point)| format!("{}: {}", index + 1, vector_text(*point)))
        .collect::<Vec<_>>()
        .join("\n");
    vec![
        PropSection {
            title: "Section Object".into(),
            props: vec![
                text_prop("Name", "ext_section_name", &data.name),
                ro_prop("State", "ext_section_state", data.state.to_string()),
                ro_prop("Flags", "ext_section_flags", data.flags.to_string()),
                ro_prop(
                    "Vertical Direction",
                    "ext_section_vertical",
                    vector_text(data.vertical_direction),
                ),
                edit_prop("Top Height", "ext_section_top", data.top_height),
                edit_prop("Bottom Height", "ext_section_bottom", data.bottom_height),
                ro_prop(
                    "Indicator Alpha",
                    "ext_section_alpha",
                    data.indicator_alpha.to_string(),
                ),
                ro_prop(
                    "Indicator Color",
                    "ext_section_color",
                    format!("{:?}", data.indicator_color),
                ),
                ro_prop(
                    "Settings",
                    "ext_section_settings",
                    handle_text(data.settings_handle),
                ),
            ],
        },
        PropSection {
            title: "Section Vertices".into(),
            props: vec![
                ro_prop("Cutting Line", "ext_section_vertices", vertices),
                ro_prop("Back Line", "ext_section_back_vertices", back_vertices),
            ],
        },
    ]
}

fn arc_text_properties(data: &ArcAlignedTextData) -> Vec<PropSection> {
    vec![PropSection {
        title: "Arc-Aligned Text".into(),
        props: vec![
            text_prop("Text", "ext_arc_text", &data.text),
            text_prop("Font", "ext_arc_font", &data.font_name),
            text_prop("Big Font", "ext_arc_big_font", &data.big_font_name),
            text_prop("Style", "ext_arc_style", &data.style_name),
            ro_prop("Center", "ext_arc_center", vector_text(data.center)),
            edit_prop("Radius", "ext_arc_radius", data.radius),
            edit_prop("X Scale", "ext_arc_xscale", data.x_scale),
            edit_prop("Text Size", "ext_arc_size", data.text_size),
            edit_prop(
                "Character Spacing",
                "ext_arc_spacing",
                data.character_spacing,
            ),
            edit_prop(
                "Offset From Arc",
                "ext_arc_offset",
                data.offset_from_arc,
            ),
            edit_prop("Right Offset", "ext_arc_right", data.right_offset),
            edit_prop("Left Offset", "ext_arc_left", data.left_offset),
            edit_angle_prop(
                "Start Angle",
                "ext_arc_start",
                data.start_angle.to_degrees(),
            ),
            edit_angle_prop("End Angle", "ext_arc_end", data.end_angle.to_degrees()),
            bool_prop("Reverse", "ext_arc_reverse", data.reverse),
            ro_prop(
                "Text Direction",
                "ext_arc_direction",
                data.text_direction.to_string(),
            ),
            ro_prop("Alignment", "ext_arc_alignment", data.alignment.to_string()),
            ro_prop(
                "Text Position",
                "ext_arc_position",
                data.text_position.to_string(),
            ),
            bool_prop("Bold", "ext_arc_bold", data.bold),
            bool_prop("Italic", "ext_arc_italic", data.italic),
            bool_prop("Underlined", "ext_arc_underlined", data.underlined),
            ro_prop(
                "Character Set",
                "ext_arc_charset",
                data.character_set.to_string(),
            ),
            ro_prop(
                "Pitch And Family",
                "ext_arc_pitch",
                data.pitch_and_family.to_string(),
            ),
            bool_prop("SHX", "ext_arc_shx", data.is_shx),
            ro_prop("Text Color", "ext_arc_color", data.text_color.to_string()),
            ro_prop("Normal", "ext_arc_normal", vector_text(data.normal)),
            bool_prop("Wizard Flag", "ext_arc_wizard", data.wizard_flag),
            ro_prop("Arc", "ext_arc_handle", handle_text(data.arc_handle)),
        ],
    }]
}

fn remote_text_properties(data: &RemoteTextData) -> Vec<PropSection> {
    vec![PropSection {
        title: "Remote Text".into(),
        props: vec![
            text_prop("Text", "ext_rtext_text", &data.text),
            ro_prop("Position", "ext_rtext_position", vector_text(data.position)),
            ro_prop("Normal", "ext_rtext_normal", vector_text(data.normal)),
            edit_angle_prop(
                "Rotation",
                "ext_rtext_rotation",
                data.rotation.to_degrees(),
            ),
            edit_prop("Height", "ext_rtext_height", data.height),
            text_prop("Style Name", "ext_rtext_style", &data.style_name),
            ro_prop(
                "Style Handle",
                "ext_rtext_style_handle",
                handle_text(data.style_handle),
            ),
            ro_prop("Flags", "ext_rtext_flags", data.flags.to_string()),
        ],
    }]
}

fn geo_marker_properties(data: &GeoPositionMarkerData) -> Vec<PropSection> {
    vec![PropSection {
        title: "Geographic Position Marker".into(),
        props: vec![
            ro_prop(
                "Class Version",
                "ext_geo_version",
                data.class_version.to_string(),
            ),
            ro_prop("Position", "ext_geo_position", vector_text(data.position)),
            edit_prop("Radius", "ext_geo_radius", data.radius),
            text_prop("Notes", "ext_geo_notes", &data.notes),
            edit_prop("Landing Gap", "ext_geo_gap", data.landing_gap),
            bool_prop("Text Visible", "ext_geo_visible", data.mtext_visible),
            ro_prop(
                "Text Alignment",
                "ext_geo_alignment",
                data.text_alignment.to_string(),
            ),
            bool_prop("Frame Text", "ext_geo_frame", data.enable_frame_text),
            ro_prop(
                "Embedded MText",
                "ext_geo_mtext",
                data.embedded_mtext
                    .as_ref()
                    .map(|text| text.value.clone())
                    .unwrap_or_default(),
            ),
        ],
    }]
}

fn point_cloud_properties(data: &PointCloudData) -> Vec<PropSection> {
    let clips = data
        .clippings
        .iter()
        .enumerate()
        .map(|(index, clip)| {
            format!(
                "{}: type {}; inverted {}; vertices {}; Z {:.6}..{:.6}",
                index + 1,
                clip.clip_type,
                clip.inverted,
                clip.vertices.len(),
                clip.z_min,
                clip.z_max
            )
        })
        .collect::<Vec<_>>()
        .join("\n");
    vec![
        PropSection {
            title: "Point Cloud".into(),
            props: vec![
                ro_prop(
                    "Class Version",
                    "ext_pc_version",
                    data.class_version.to_string(),
                ),
                ro_prop("Origin", "ext_pc_origin", vector_text(data.origin)),
                text_prop("Saved File", "ext_pc_file", &data.saved_filename),
                ro_prop(
                    "Source Files",
                    "ext_pc_sources",
                    data.source_files.join("\n"),
                ),
                ro_prop("Extents Min", "ext_pc_min", vector_text(data.extents_min)),
                ro_prop("Extents Max", "ext_pc_max", vector_text(data.extents_max)),
                ro_prop(
                    "Point Count",
                    "ext_pc_count",
                    data.point_count.to_string(),
                ),
                text_prop("UCS Name", "ext_pc_ucs", &data.ucs_name),
                ro_prop(
                    "UCS Origin",
                    "ext_pc_ucs_origin",
                    vector_text(data.ucs_origin),
                ),
                ro_prop(
                    "UCS X",
                    "ext_pc_ucs_x",
                    vector_text(data.ucs_x_direction),
                ),
                ro_prop(
                    "UCS Y",
                    "ext_pc_ucs_y",
                    vector_text(data.ucs_y_direction),
                ),
                ro_prop(
                    "UCS Z",
                    "ext_pc_ucs_z",
                    vector_text(data.ucs_z_direction),
                ),
                ro_prop(
                    "Definition",
                    "ext_pc_definition",
                    handle_text(data.definition_handle),
                ),
                ro_prop(
                    "Reactor",
                    "ext_pc_reactor",
                    handle_text(data.reactor_handle),
                ),
            ],
        },
        PropSection {
            title: "Point Cloud Display".into(),
            props: vec![
                bool_prop("Show Intensity", "ext_pc_show_intensity", data.show_intensity),
                ro_prop(
                    "Intensity Scheme",
                    "ext_pc_intensity_scheme",
                    data.intensity_scheme.to_string(),
                ),
                edit_prop(
                    "Minimum Intensity",
                    "ext_pc_intensity_min",
                    data.minimum_intensity,
                ),
                edit_prop(
                    "Maximum Intensity",
                    "ext_pc_intensity_max",
                    data.maximum_intensity,
                ),
                edit_prop(
                    "Low Threshold",
                    "ext_pc_low_threshold",
                    data.low_intensity_threshold,
                ),
                edit_prop(
                    "High Threshold",
                    "ext_pc_high_threshold",
                    data.high_intensity_threshold,
                ),
                bool_prop("Show Clipping", "ext_pc_show_clipping", data.show_clipping),
                ro_prop("Clippings", "ext_pc_clippings", clips),
            ],
        },
    ]
}

fn point_cloud_ex_properties(data: &PointCloudExData) -> Vec<PropSection> {
    let crops = data
        .croppings
        .iter()
        .enumerate()
        .map(|(index, crop)| {
            format!(
                "{}: type {}; inside {}; inverted {}; points {}; plane [{}]",
                index + 1,
                crop.crop_type,
                crop.inside,
                crop.inverted,
                crop.points.len(),
                vector_text(crop.plane)
            )
        })
        .collect::<Vec<_>>()
        .join("\n");
    vec![
        PropSection {
            title: "Point Cloud Ex".into(),
            props: vec![
                ro_prop(
                    "Class Version",
                    "ext_pcx_version",
                    data.class_version.to_string(),
                ),
                text_prop("Name", "ext_pcx_name", &data.name),
                ro_prop("Extents Min", "ext_pcx_min", vector_text(data.extents_min)),
                ro_prop("Extents Max", "ext_pcx_max", vector_text(data.extents_max)),
                ro_prop(
                    "UCS Origin",
                    "ext_pcx_ucs_origin",
                    vector_text(data.ucs_origin),
                ),
                ro_prop(
                    "UCS X",
                    "ext_pcx_ucs_x",
                    vector_text(data.ucs_x_direction),
                ),
                ro_prop(
                    "UCS Y",
                    "ext_pcx_ucs_y",
                    vector_text(data.ucs_y_direction),
                ),
                ro_prop(
                    "UCS Z",
                    "ext_pcx_ucs_z",
                    vector_text(data.ucs_z_direction),
                ),
                bool_prop("Locked", "ext_pcx_locked", data.locked),
                ro_prop(
                    "Definition",
                    "ext_pcx_definition",
                    handle_text(data.definition_handle),
                ),
                ro_prop(
                    "Reactor",
                    "ext_pcx_reactor",
                    handle_text(data.reactor_handle),
                ),
            ],
        },
        PropSection {
            title: "Point Cloud Ex Display".into(),
            props: vec![
                bool_prop(
                    "Show Intensity",
                    "ext_pcx_show_intensity",
                    data.show_intensity,
                ),
                bool_prop("Show Cropping", "ext_pcx_show_cropping", data.show_cropping),
                ro_prop(
                    "Unknown Flags",
                    "ext_pcx_unknown",
                    format!("{}, {}", data.unknown_bl0, data.unknown_bl1),
                ),
                ro_prop(
                    "Stylization Type",
                    "ext_pcx_stylization",
                    data.stylization_type.to_string(),
                ),
                text_prop(
                    "Intensity Color Scheme",
                    "ext_pcx_intensity_scheme",
                    &data.intensity_color_scheme,
                ),
                text_prop(
                    "Current Color Scheme",
                    "ext_pcx_current_scheme",
                    &data.current_color_scheme,
                ),
                text_prop(
                    "Classification Scheme",
                    "ext_pcx_class_scheme",
                    &data.classification_color_scheme,
                ),
                edit_prop("Elevation Min", "ext_pcx_elevation_min", data.elevation_min),
                edit_prop("Elevation Max", "ext_pcx_elevation_max", data.elevation_max),
                ro_prop(
                    "Intensity Range",
                    "ext_pcx_intensity_range",
                    format!("{}..{}", data.intensity_min, data.intensity_max),
                ),
                ro_prop(
                    "Out Of Range Behavior",
                    "ext_pcx_out_of_range",
                    format!(
                        "intensity {}; elevation {}",
                        data.intensity_out_of_range_behavior,
                        data.elevation_out_of_range_behavior
                    ),
                ),
                bool_prop(
                    "Fixed Elevation Range",
                    "ext_pcx_fixed_range",
                    data.elevation_apply_to_fixed_range,
                ),
                bool_prop(
                    "Intensity Gradient",
                    "ext_pcx_intensity_gradient",
                    data.intensity_as_gradient,
                ),
                bool_prop(
                    "Elevation Gradient",
                    "ext_pcx_elevation_gradient",
                    data.elevation_as_gradient,
                ),
                ro_prop("Croppings", "ext_pcx_croppings", crops),
            ],
        },
    ]
}

fn semantic_properties(
    properties: &[acadrust::objects::SemanticProperty],
) -> String {
    properties
        .iter()
        .map(|property| {
            format!(
                "{} [{}]: {:?}",
                property.subclass, property.code, property.value
            )
        })
        .collect::<Vec<_>>()
        .join("\n")
}

fn reference_properties(
    references: &[acadrust::objects::ProxyObjectReference],
) -> String {
    references
        .iter()
        .map(|reference| {
            format!(
                "{:X}: {:?}",
                reference.handle.value(),
                reference.kind
            )
        })
        .collect::<Vec<_>>()
        .join("\n")
}

fn properties(entity: &ExtendedEntity) -> Vec<PropSection> {
    match &entity.data {
        ExtendedEntityData::Camera { view_handle } => vec![PropSection {
            title: "Camera".into(),
            props: vec![ro_prop("View", "ext_camera_view", handle_text(*view_handle))],
        }],
        ExtendedEntityData::SectionObject(data) => section_properties(data),
        ExtendedEntityData::ArcAlignedText(data) => arc_text_properties(data),
        ExtendedEntityData::RemoteText(data) => remote_text_properties(data),
        ExtendedEntityData::GeoPositionMarker(data) => geo_marker_properties(data),
        ExtendedEntityData::CoordinationModel(data) => vec![PropSection {
            title: "Coordination Model".into(),
            props: vec![
                ro_prop("Flags", "ext_coord_flags", data.flags.to_string()),
                ro_prop(
                    "Definition",
                    "ext_coord_definition",
                    handle_text(data.definition_handle),
                ),
                edit_prop("Unit Factor", "ext_coord_unit", data.unit_factor),
                ro_prop(
                    "Transform",
                    "ext_coord_transform",
                    data.transform
                        .chunks_exact(4)
                        .map(|row| {
                            format!(
                                "{:.6}, {:.6}, {:.6}, {:.6}",
                                row[0], row[1], row[2], row[3]
                            )
                        })
                        .collect::<Vec<_>>()
                        .join("\n"),
                ),
            ],
        }],
        ExtendedEntityData::PointCloud(data) => point_cloud_properties(data),
        ExtendedEntityData::PointCloudEx(data) => point_cloud_ex_properties(data),
        ExtendedEntityData::Proxy(data) => vec![PropSection {
            title: "Proxy Entity".into(),
            props: vec![
                ro_prop("Proxy ID", "ext_proxy_id", data.proxy_id.to_string()),
                ro_prop("Class ID", "ext_proxy_class", data.class_id.to_string()),
                ro_prop("DXF Subclass", "ext_proxy_subclass", data.dxf_subclass.clone()),
                ro_prop("Version", "ext_proxy_version", data.version.to_string()),
                ro_prop(
                    "DWG Version",
                    "ext_proxy_dwg_version",
                    format!("{}.{}", data.dwg_version, data.maintenance_version),
                ),
                ro_prop("From DXF", "ext_proxy_from_dxf", data.from_dxf.to_string()),
                ro_prop(
                    "Graphics",
                    "ext_proxy_graphics",
                    format!("{} bits", data.graphics.bit_count),
                ),
                ro_prop(
                    "Payload",
                    "ext_proxy_payload",
                    format!("{} bits", data.payload.bit_count),
                ),
                ro_prop(
                    "Text Payload",
                    "ext_proxy_text_payload",
                    format!("{} bits", data.text_payload.bit_count),
                ),
                ro_prop(
                    "Object References",
                    "ext_proxy_references",
                    reference_properties(&data.object_ids),
                ),
            ],
        }],
        ExtendedEntityData::OleFrame(data) => vec![PropSection {
            title: "OLE Frame".into(),
            props: vec![
                ro_prop("Flag", "ext_ole_flag", data.flag.to_string()),
                ro_prop("Mode", "ext_ole_mode", data.mode.to_string()),
                ro_prop(
                    "Storage Size",
                    "ext_ole_size",
                    format!("{} bytes", data.storage.encoded_len()),
                ),
            ],
        }],
        ExtendedEntityData::LayoutPrintConfig(data) => vec![PropSection {
            title: "Layout Print Configuration".into(),
            props: vec![
                ro_prop(
                    "Class Version",
                    "ext_print_version",
                    data.class_version.to_string(),
                ),
                ro_prop("Flag", "ext_print_flag", data.flag.to_string()),
            ],
        }],
        ExtendedEntityData::Format(data) => vec![PropSection {
            title: "Format".into(),
            props: vec![
                ro_prop(
                    "DWG Payload",
                    "ext_format_dwg",
                    format!(
                        "{} bytes / {} handle bits",
                        data.raw_dwg_data.as_ref().map_or(0, Vec::len),
                        data.raw_dwg_handle_bits
                    ),
                ),
                ro_prop(
                    "DWG Version",
                    "ext_format_version",
                    format!("{:?}", data.raw_dwg_version),
                ),
                ro_prop(
                    "DXF Codes",
                    "ext_format_dxf",
                    data.raw_dxf_codes.as_ref().map_or(0, Vec::len).to_string(),
                ),
            ],
        }],
        ExtendedEntityData::Legacy(data) => vec![PropSection {
            title: "Legacy Entity".into(),
            props: vec![ro_prop("Data", "ext_legacy_data", format!("{data:#?}"))],
        }],
        ExtendedEntityData::DynamicBlock(data) => vec![PropSection {
            title: "Dynamic Block Entity".into(),
            props: vec![
                ro_prop(
                    "Class",
                    "ext_dynamic_class",
                    data.entity_dxf_name().unwrap_or("Helper"),
                ),
                ro_prop("Decoded Data", "ext_dynamic_data", format!("{data:#?}")),
            ],
        }],
        ExtendedEntityData::RegisteredClass(data) => vec![PropSection {
            title: "Registered Class Entity".into(),
            props: vec![
                ro_prop("DXF Name", "ext_registered_dxf", data.dxf_name.clone()),
                ro_prop(
                    "C++ Class",
                    "ext_registered_cpp",
                    data.cpp_class_name.clone(),
                ),
                ro_prop(
                    "Properties",
                    "ext_registered_properties",
                    semantic_properties(&data.properties),
                ),
                ro_prop(
                    "Payload",
                    "ext_registered_payload",
                    format!("{} bits", data.payload.bit_count),
                ),
                ro_prop(
                    "Object References",
                    "ext_registered_references",
                    reference_properties(&data.object_ids),
                ),
            ],
        }],
    }
}

fn set_f64(value: &str, target: &mut f64) {
    if let Some(value) = parse_f64(value) {
        *target = value;
    }
}

fn apply_geom_prop(entity: &mut ExtendedEntity, field: &str, value: &str) {
    match &mut entity.data {
        ExtendedEntityData::SectionObject(data) => match field {
            "ext_section_name" => data.name = value.to_string(),
            "ext_section_top" => set_f64(value, &mut data.top_height),
            "ext_section_bottom" => set_f64(value, &mut data.bottom_height),
            _ => {}
        },
        ExtendedEntityData::ArcAlignedText(data) => match field {
            "ext_arc_text" => data.text = value.to_string(),
            "ext_arc_font" => data.font_name = value.to_string(),
            "ext_arc_big_font" => data.big_font_name = value.to_string(),
            "ext_arc_style" => data.style_name = value.to_string(),
            "ext_arc_radius" => set_f64(value, &mut data.radius),
            "ext_arc_xscale" => set_f64(value, &mut data.x_scale),
            "ext_arc_size" => set_f64(value, &mut data.text_size),
            "ext_arc_spacing" => set_f64(value, &mut data.character_spacing),
            "ext_arc_offset" => set_f64(value, &mut data.offset_from_arc),
            "ext_arc_right" => set_f64(value, &mut data.right_offset),
            "ext_arc_left" => set_f64(value, &mut data.left_offset),
            "ext_arc_start" => {
                if let Some(value) = parse_f64(value) {
                    data.start_angle = value.to_radians();
                }
            }
            "ext_arc_end" => {
                if let Some(value) = parse_f64(value) {
                    data.end_angle = value.to_radians();
                }
            }
            "ext_arc_reverse" => data.reverse = !data.reverse,
            "ext_arc_bold" => data.bold = !data.bold,
            "ext_arc_italic" => data.italic = !data.italic,
            "ext_arc_underlined" => data.underlined = !data.underlined,
            "ext_arc_shx" => data.is_shx = !data.is_shx,
            "ext_arc_wizard" => data.wizard_flag = !data.wizard_flag,
            _ => {}
        },
        ExtendedEntityData::RemoteText(data) => match field {
            "ext_rtext_text" => data.text = value.to_string(),
            "ext_rtext_style" => data.style_name = value.to_string(),
            "ext_rtext_height" => set_f64(value, &mut data.height),
            "ext_rtext_rotation" => {
                if let Some(value) = parse_f64(value) {
                    data.rotation = value.to_radians();
                }
            }
            _ => {}
        },
        ExtendedEntityData::GeoPositionMarker(data) => match field {
            "ext_geo_radius" => set_f64(value, &mut data.radius),
            "ext_geo_notes" => data.notes = value.to_string(),
            "ext_geo_gap" => set_f64(value, &mut data.landing_gap),
            "ext_geo_visible" => data.mtext_visible = !data.mtext_visible,
            "ext_geo_frame" => data.enable_frame_text = !data.enable_frame_text,
            _ => {}
        },
        ExtendedEntityData::CoordinationModel(data) => {
            if field == "ext_coord_unit" {
                set_f64(value, &mut data.unit_factor);
            }
        }
        ExtendedEntityData::PointCloud(data) => match field {
            "ext_pc_file" => data.saved_filename = value.to_string(),
            "ext_pc_ucs" => data.ucs_name = value.to_string(),
            "ext_pc_show_intensity" => data.show_intensity = !data.show_intensity,
            "ext_pc_show_clipping" => data.show_clipping = !data.show_clipping,
            "ext_pc_intensity_min" => set_f64(value, &mut data.minimum_intensity),
            "ext_pc_intensity_max" => set_f64(value, &mut data.maximum_intensity),
            "ext_pc_low_threshold" => set_f64(value, &mut data.low_intensity_threshold),
            "ext_pc_high_threshold" => set_f64(value, &mut data.high_intensity_threshold),
            _ => {}
        },
        ExtendedEntityData::PointCloudEx(data) => match field {
            "ext_pcx_name" => data.name = value.to_string(),
            "ext_pcx_locked" => data.locked = !data.locked,
            "ext_pcx_show_intensity" => data.show_intensity = !data.show_intensity,
            "ext_pcx_show_cropping" => data.show_cropping = !data.show_cropping,
            "ext_pcx_intensity_scheme" => data.intensity_color_scheme = value.to_string(),
            "ext_pcx_current_scheme" => data.current_color_scheme = value.to_string(),
            "ext_pcx_class_scheme" => data.classification_color_scheme = value.to_string(),
            "ext_pcx_elevation_min" => set_f64(value, &mut data.elevation_min),
            "ext_pcx_elevation_max" => set_f64(value, &mut data.elevation_max),
            "ext_pcx_fixed_range" => {
                data.elevation_apply_to_fixed_range = !data.elevation_apply_to_fixed_range
            }
            "ext_pcx_intensity_gradient" => {
                data.intensity_as_gradient = !data.intensity_as_gradient
            }
            "ext_pcx_elevation_gradient" => {
                data.elevation_as_gradient = !data.elevation_as_gradient
            }
            _ => {}
        },
        _ => {}
    }
}

fn apply_point(point: &mut Vector3, apply: GripApply) {
    match apply {
        GripApply::Translate(delta) => {
            point.x += delta.x;
            point.y += delta.y;
            point.z += delta.z;
        }
        GripApply::Absolute(position) => {
            *point = Vector3::new(position.x, position.y, position.z);
        }
    }
}

fn move_extents(
    min: &mut Vector3,
    max: &mut Vector3,
    grip_id: usize,
    apply: GripApply,
) {
    match grip_id {
        0 => apply_point(min, apply),
        1 => apply_point(max, apply),
        2 => {
            let center = (*min + *max) * 0.5;
            let delta = match apply {
                GripApply::Translate(delta) => delta,
                GripApply::Absolute(position) => {
                    position - glam::DVec3::new(center.x, center.y, center.z)
                }
            };
            let offset = Vector3::new(delta.x, delta.y, delta.z);
            *min = *min + offset;
            *max = *max + offset;
        }
        _ => {}
    }
}

fn grips(entity: &ExtendedEntity) -> Vec<GripDef> {
    match &entity.data {
        ExtendedEntityData::SectionObject(data) => data
            .vertices
            .iter()
            .chain(data.back_line_vertices.iter())
            .enumerate()
            .map(|(index, point)| {
                square_grip(index, glam::DVec3::new(point.x, point.y, point.z))
            })
            .collect(),
        ExtendedEntityData::ArcAlignedText(data) => vec![center_grip(
            0,
            glam::DVec3::new(data.center.x, data.center.y, data.center.z),
        )],
        ExtendedEntityData::RemoteText(data) => vec![square_grip(
            0,
            glam::DVec3::new(data.position.x, data.position.y, data.position.z),
        )],
        ExtendedEntityData::GeoPositionMarker(data) => vec![center_grip(
            0,
            glam::DVec3::new(data.position.x, data.position.y, data.position.z),
        )],
        ExtendedEntityData::PointCloud(data) => vec![
            square_grip(
                0,
                glam::DVec3::new(
                    data.extents_min.x,
                    data.extents_min.y,
                    data.extents_min.z,
                ),
            ),
            square_grip(
                1,
                glam::DVec3::new(
                    data.extents_max.x,
                    data.extents_max.y,
                    data.extents_max.z,
                ),
            ),
            center_grip(
                2,
                glam::DVec3::new(
                    (data.extents_min.x + data.extents_max.x) * 0.5,
                    (data.extents_min.y + data.extents_max.y) * 0.5,
                    (data.extents_min.z + data.extents_max.z) * 0.5,
                ),
            ),
        ],
        ExtendedEntityData::PointCloudEx(data) => vec![
            square_grip(
                0,
                glam::DVec3::new(
                    data.extents_min.x,
                    data.extents_min.y,
                    data.extents_min.z,
                ),
            ),
            square_grip(
                1,
                glam::DVec3::new(
                    data.extents_max.x,
                    data.extents_max.y,
                    data.extents_max.z,
                ),
            ),
            center_grip(
                2,
                glam::DVec3::new(
                    (data.extents_min.x + data.extents_max.x) * 0.5,
                    (data.extents_min.y + data.extents_max.y) * 0.5,
                    (data.extents_min.z + data.extents_max.z) * 0.5,
                ),
            ),
        ],
        _ => Vec::new(),
    }
}

fn apply_grip(entity: &mut ExtendedEntity, grip_id: usize, apply: GripApply) {
    match &mut entity.data {
        ExtendedEntityData::SectionObject(data) => {
            if grip_id < data.vertices.len() {
                apply_point(&mut data.vertices[grip_id], apply);
            } else if let Some(point) = data
                .back_line_vertices
                .get_mut(grip_id - data.vertices.len())
            {
                apply_point(point, apply);
            }
        }
        ExtendedEntityData::ArcAlignedText(data) if grip_id == 0 => {
            apply_point(&mut data.center, apply)
        }
        ExtendedEntityData::RemoteText(data) if grip_id == 0 => {
            apply_point(&mut data.position, apply)
        }
        ExtendedEntityData::GeoPositionMarker(data) if grip_id == 0 => {
            let before = data.position;
            apply_point(&mut data.position, apply);
            if let Some(text) = data.embedded_mtext.as_mut() {
                text.insertion_point =
                    text.insertion_point + (data.position - before);
            }
        }
        ExtendedEntityData::PointCloud(data) => {
            let before = (data.extents_min + data.extents_max) * 0.5;
            move_extents(
                &mut data.extents_min,
                &mut data.extents_max,
                grip_id,
                apply,
            );
            if grip_id == 2 {
                let after = (data.extents_min + data.extents_max) * 0.5;
                let delta = after - before;
                data.origin = data.origin + delta;
                data.ucs_origin = data.ucs_origin + delta;
            }
        }
        ExtendedEntityData::PointCloudEx(data) => {
            let before = (data.extents_min + data.extents_max) * 0.5;
            move_extents(
                &mut data.extents_min,
                &mut data.extents_max,
                grip_id,
                apply,
            );
            if grip_id == 2 {
                let after = (data.extents_min + data.extents_max) * 0.5;
                let delta = after - before;
                data.ucs_origin = data.ucs_origin + delta;
                for crop in &mut data.croppings {
                    for point in &mut crop.points {
                        *point = *point + delta;
                    }
                }
            }
        }
        _ => {}
    }
}

fn entity_transform(transform: &EntityTransform) -> Transform {
    match transform {
        EntityTransform::Translate(delta) => {
            Transform::from_translation(Vector3::new(delta.x, delta.y, delta.z))
        }
        EntityTransform::Rotate { center, angle_rad } => {
            Transform::from_translation(Vector3::new(-center.x, -center.y, -center.z))
                .then(&Transform::from_rotation(Vector3::UNIT_Z, *angle_rad))
                .then(&Transform::from_translation(Vector3::new(
                    center.x, center.y, center.z,
                )))
        }
        EntityTransform::Scale { center, factor } => Transform::from_scaling_with_origin(
            Vector3::new(*factor, *factor, *factor),
            Vector3::new(center.x, center.y, center.z),
        ),
        EntityTransform::Mirror { p1, p2 } => {
            crate::scene::view::transform::reflection_about_xy_line(*p1, *p2)
        }
    }
}

fn scalar_scale(transform: &EntityTransform) -> f64 {
    match transform {
        EntityTransform::Scale { factor, .. } => factor.abs(),
        _ => 1.0,
    }
}

fn transform_extents(min: Vector3, max: Vector3, transform: &Transform) -> (Vector3, Vector3) {
    let corners = [
        Vector3::new(min.x, min.y, min.z),
        Vector3::new(max.x, min.y, min.z),
        Vector3::new(max.x, max.y, min.z),
        Vector3::new(min.x, max.y, min.z),
        Vector3::new(min.x, min.y, max.z),
        Vector3::new(max.x, min.y, max.z),
        Vector3::new(max.x, max.y, max.z),
        Vector3::new(min.x, max.y, max.z),
    ];
    let mut out_min = Vector3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
    let mut out_max = Vector3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
    for point in corners.map(|point| transform.apply(point)) {
        out_min.x = out_min.x.min(point.x);
        out_min.y = out_min.y.min(point.y);
        out_min.z = out_min.z.min(point.z);
        out_max.x = out_max.x.max(point.x);
        out_max.y = out_max.y.max(point.y);
        out_max.z = out_max.z.max(point.z);
    }
    (out_min, out_max)
}

fn transformed_planar_angle(
    normal: Vector3,
    angle: f64,
    transform: &Transform,
) -> (Vector3, f64) {
    let (axis_x, axis_y, _) = plane_axes(normal);
    let direction = axis_x * angle.cos() + axis_y * angle.sin();
    let new_normal = normalized(transform.apply_rotation(normal), Vector3::UNIT_Z);
    let new_direction = normalized(transform.apply_rotation(direction), Vector3::UNIT_X);
    let (new_x, new_y, _) = plane_axes(new_normal);
    (
        new_normal,
        new_direction.dot(&new_y).atan2(new_direction.dot(&new_x)),
    )
}

fn apply_transform(entity: &mut ExtendedEntity, requested: &EntityTransform) {
    let transform = entity_transform(requested);
    let scale = scalar_scale(requested);
    match &mut entity.data {
        ExtendedEntityData::SectionObject(data) => {
            for point in data
                .vertices
                .iter_mut()
                .chain(data.back_line_vertices.iter_mut())
            {
                *point = transform.apply(*point);
            }
            data.vertical_direction =
                normalized(transform.apply_rotation(data.vertical_direction), Vector3::UNIT_Z);
            data.top_height *= scale;
            data.bottom_height *= scale;
        }
        ExtendedEntityData::ArcAlignedText(data) => {
            let old_normal = data.normal;
            let (_, start) =
                transformed_planar_angle(old_normal, data.start_angle, &transform);
            let (normal, end) =
                transformed_planar_angle(old_normal, data.end_angle, &transform);
            data.center = transform.apply(data.center);
            data.normal = normal;
            data.start_angle = start;
            data.end_angle = end;
            data.radius *= scale;
            data.text_size *= scale;
            data.offset_from_arc *= scale;
            data.right_offset *= scale;
            data.left_offset *= scale;
        }
        ExtendedEntityData::RemoteText(data) => {
            let (normal, rotation) =
                transformed_planar_angle(data.normal, data.rotation, &transform);
            data.position = transform.apply(data.position);
            data.normal = normal;
            data.rotation = rotation;
            data.height *= scale;
        }
        ExtendedEntityData::GeoPositionMarker(data) => {
            data.position = transform.apply(data.position);
            data.radius *= scale;
            data.landing_gap *= scale;
            if let Some(text) = data.embedded_mtext.as_mut() {
                acadrust::Entity::apply_transform(text, &transform);
            }
        }
        ExtendedEntityData::CoordinationModel(data) => {
            let mut matrix = acadrust::types::Matrix4::zero();
            for row in 0..4 {
                for column in 0..4 {
                    matrix.m[row][column] = data.transform[row * 4 + column];
                }
            }
            let composed = transform.matrix * matrix;
            for row in 0..4 {
                for column in 0..4 {
                    data.transform[row * 4 + column] = composed.m[row][column];
                }
            }
            data.unit_factor *= scale;
        }
        ExtendedEntityData::PointCloud(data) => {
            data.origin = transform.apply(data.origin);
            data.ucs_origin = transform.apply(data.ucs_origin);
            data.ucs_x_direction = normalized(
                transform.apply_rotation(data.ucs_x_direction),
                Vector3::UNIT_X,
            );
            data.ucs_y_direction = normalized(
                transform.apply_rotation(data.ucs_y_direction),
                Vector3::UNIT_Y,
            );
            data.ucs_z_direction = normalized(
                transform.apply_rotation(data.ucs_z_direction),
                Vector3::UNIT_Z,
            );
            (data.extents_min, data.extents_max) =
                transform_extents(data.extents_min, data.extents_max, &transform);
            for clip in &mut data.clippings {
                let mut min = Vector3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
                let mut max =
                    Vector3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
                for point in &clip.vertices {
                    for z in [clip.z_min, clip.z_max] {
                        let point = transform.apply(Vector3::new(point.x, point.y, z));
                        min.x = min.x.min(point.x);
                        min.y = min.y.min(point.y);
                        min.z = min.z.min(point.z);
                        max.x = max.x.max(point.x);
                        max.y = max.y.max(point.y);
                        max.z = max.z.max(point.z);
                    }
                }
                for point in &mut clip.vertices {
                    let transformed = transform.apply(Vector3::new(point.x, point.y, clip.z_min));
                    point.x = transformed.x;
                    point.y = transformed.y;
                }
                if min.z.is_finite() {
                    clip.z_min = min.z;
                    clip.z_max = max.z;
                }
            }
        }
        ExtendedEntityData::PointCloudEx(data) => {
            data.ucs_origin = transform.apply(data.ucs_origin);
            data.ucs_x_direction = normalized(
                transform.apply_rotation(data.ucs_x_direction),
                Vector3::UNIT_X,
            );
            data.ucs_y_direction = normalized(
                transform.apply_rotation(data.ucs_y_direction),
                Vector3::UNIT_Y,
            );
            data.ucs_z_direction = normalized(
                transform.apply_rotation(data.ucs_z_direction),
                Vector3::UNIT_Z,
            );
            (data.extents_min, data.extents_max) =
                transform_extents(data.extents_min, data.extents_max, &transform);
            for crop in &mut data.croppings {
                crop.plane =
                    normalized(transform.apply_rotation(crop.plane), Vector3::UNIT_Z);
                crop.x_direction = normalized(
                    transform.apply_rotation(crop.x_direction),
                    Vector3::UNIT_X,
                );
                crop.y_direction = normalized(
                    transform.apply_rotation(crop.y_direction),
                    Vector3::UNIT_Y,
                );
                for point in &mut crop.points {
                    *point = transform.apply(*point);
                }
            }
        }
        _ => {}
    }
}

impl TruckConvertible for ExtendedEntity {
    fn to_truck(&self, document: &acadrust::CadDocument) -> Option<TruckEntity> {
        to_truck(self, document)
    }
}

impl Grippable for ExtendedEntity {
    fn grips(&self) -> Vec<GripDef> {
        grips(self)
    }

    fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
        apply_grip(self, grip_id, apply);
    }
}

impl PropertyEditable for ExtendedEntity {
    fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
        properties(self)
    }

    fn apply_geom_prop(&mut self, field: &str, value: &str) {
        apply_geom_prop(self, field, value);
    }
}

impl Transformable for ExtendedEntity {
    fn apply_transform(&mut self, transform: &EntityTransform) {
        apply_transform(self, transform);
    }
}
