use acadrust::objects::ObjectType;
use acadrust::tables::{DimStyle, TextStyle};
use acadrust::{CadDocument, EntityType, Handle};

#[derive(Debug, Clone)]
pub struct TextCreationDefaults {
    pub style_name: String,
    pub height: f64,
    pub width_factor: f64,
    pub oblique_angle: f64,
}

#[derive(Debug, Clone)]
pub struct DimensionCreationDefaults {
    pub style_name: String,
    pub style_handle: Handle,
    pub text_style_name: String,
    pub text_height: f64,
    pub gap: f64,
    pub arrow_size: f64,
    pub scale: f64,
    pub annotative: bool,
}

fn named_text_style(doc: &CadDocument, name: &str) -> Option<TextStyle> {
    doc.text_styles
        .iter()
        .find(|style| style.name.eq_ignore_ascii_case(name))
        .or_else(|| {
            doc.text_styles
                .iter()
                .find(|style| style.name.eq_ignore_ascii_case("Standard"))
        })
        .cloned()
}

fn named_dim_style(doc: &CadDocument, name: &str) -> Option<DimStyle> {
    doc.dim_styles
        .iter()
        .find(|style| style.name.eq_ignore_ascii_case(name))
        .or_else(|| {
            doc.dim_styles
                .iter()
                .find(|style| style.name.eq_ignore_ascii_case("Standard"))
        })
        .cloned()
}

pub fn current_text_defaults(doc: &CadDocument) -> TextCreationDefaults {
    let requested = doc.header.current_text_style_name.trim();
    let style = named_text_style(doc, requested);
    let variable_height = if doc.header.text_height > 1.0e-9 {
        doc.header.text_height
    } else {
        style
            .as_ref()
            .map(|style| style.last_height)
            .filter(|height| *height > 1.0e-9)
            .unwrap_or(2.5)
    };
    TextCreationDefaults {
        style_name: style
            .as_ref()
            .map(|style| style.name.clone())
            .filter(|name| !name.trim().is_empty())
            .unwrap_or_else(|| "Standard".to_string()),
        height: style
            .as_ref()
            .map(|style| style.height)
            .filter(|height| *height > 1.0e-9)
            .unwrap_or(variable_height),
        width_factor: style.as_ref().map_or(1.0, |style| style.width_factor),
        oblique_angle: style.as_ref().map_or(0.0, |style| style.oblique_angle),
    }
}

pub fn current_dimension_defaults(doc: &CadDocument) -> DimensionCreationDefaults {
    let requested = doc.header.current_dimstyle_name.trim();
    let style = named_dim_style(doc, requested);
    DimensionCreationDefaults {
        style_name: style
            .as_ref()
            .map(|style| style.name.clone())
            .filter(|name| !name.trim().is_empty())
            .unwrap_or_else(|| "Standard".to_string()),
        style_handle: style.as_ref().map_or(Handle::NULL, |style| style.handle),
        text_style_name: style
            .as_ref()
            .map(|style| style.dimtxsty.clone())
            .filter(|name| !name.trim().is_empty())
            .unwrap_or_else(|| current_text_defaults(doc).style_name),
        text_height: style.as_ref().map_or(2.5, |style| style.dimtxt),
        gap: style.as_ref().map_or(0.625, |style| style.dimgap),
        arrow_size: style.as_ref().map_or(2.5, |style| style.dimasz),
        scale: style.as_ref().map_or(1.0, |style| {
            if style.annotative {
                1.0
            } else if style.dimscale > 1.0e-9 {
                style.dimscale
            } else {
                1.0
            }
        }),
        annotative: style.as_ref().is_some_and(|style| style.annotative),
    }
}

fn entity_uses_default_style(name: &str) -> bool {
    name.trim().is_empty() || name.eq_ignore_ascii_case("Standard")
}

fn apply_text_defaults(doc: &CadDocument, entity: &mut EntityType) {
    let active = current_text_defaults(doc);
    let style_name = match entity {
        EntityType::Text(text) => &mut text.style,
        EntityType::MText(text) => &mut text.style,
        EntityType::AttributeEntity(attribute) => &mut attribute.text_style,
        EntityType::AttributeDefinition(attribute) => &mut attribute.text_style,
        _ => return,
    };
    if style_name.trim().is_empty() {
        style_name.clone_from(&active.style_name);
    }
    let resolved = named_text_style(doc, style_name)
        .unwrap_or_else(|| TextStyle::new(style_name.clone()));
    match entity {
        EntityType::Text(text) => {
            if resolved.height > 1.0e-9 {
                text.height = resolved.height;
            }
            if (text.width_factor - 1.0).abs() <= 1.0e-9 {
                text.width_factor = resolved.width_factor;
            }
            if text.oblique_angle.abs() <= 1.0e-9 {
                text.oblique_angle = resolved.oblique_angle;
            }
            if text.generation_flags == 0 {
                text.generation_flags = (if resolved.flags.backward { 2 } else { 0 })
                    | (if resolved.flags.upside_down { 4 } else { 0 });
            }
        }
        EntityType::MText(text) => {
            if resolved.height > 1.0e-9 {
                text.height = resolved.height;
            }
        }
        EntityType::AttributeEntity(attribute) => {
            if resolved.height > 1.0e-9 {
                attribute.height = resolved.height;
            }
            if (attribute.width_factor - 1.0).abs() <= 1.0e-9 {
                attribute.width_factor = resolved.width_factor;
            }
            if attribute.oblique_angle.abs() <= 1.0e-9 {
                attribute.oblique_angle = resolved.oblique_angle;
            }
            if attribute.text_generation_flags == 0 {
                attribute.text_generation_flags =
                    (if resolved.flags.backward { 2 } else { 0 })
                        | (if resolved.flags.upside_down { 4 } else { 0 });
            }
        }
        EntityType::AttributeDefinition(attribute) => {
            if resolved.height > 1.0e-9 {
                attribute.height = resolved.height;
            }
            if (attribute.width_factor - 1.0).abs() <= 1.0e-9 {
                attribute.width_factor = resolved.width_factor;
            }
            if attribute.oblique_angle.abs() <= 1.0e-9 {
                attribute.oblique_angle = resolved.oblique_angle;
            }
            if attribute.text_generation_flags == 0 {
                attribute.text_generation_flags =
                    (if resolved.flags.backward { 2 } else { 0 })
                        | (if resolved.flags.upside_down { 4 } else { 0 });
            }
        }
        _ => {}
    }
}

fn apply_dimension_defaults(doc: &CadDocument, entity: &mut EntityType) {
    let active = current_dimension_defaults(doc);
    match entity {
        EntityType::Dimension(dimension) => {
            if entity_uses_default_style(&dimension.base().style_name) {
                dimension.base_mut().style_name.clone_from(&active.style_name);
            }
        }
        EntityType::Leader(leader) => {
            if entity_uses_default_style(&leader.dimension_style) {
                leader.dimension_style.clone_from(&active.style_name);
                leader.text_height = active.text_height;
                leader.dimension_gap = active.gap;
                leader.arrow_size = active.arrow_size;
            }
        }
        EntityType::Tolerance(tolerance) => {
            if entity_uses_default_style(&tolerance.dimension_style_name) {
                tolerance.dimension_style_name.clone_from(&active.style_name);
                tolerance.dimension_style_handle = (!active.style_handle.is_null())
                    .then_some(active.style_handle);
                tolerance.text_height = active.text_height;
                tolerance.dimension_gap = active.gap;
            }
        }
        _ => {}
    }
}

fn apply_object_defaults(doc: &CadDocument, entity: &mut EntityType) {
    match entity {
        EntityType::MultiLeader(leader) if leader.style_handle.is_none() => {
            let name = doc.header.current_mleader_style_name.trim();
            if let Some(style) = doc.objects.iter().find_map(|(handle, object)| match object {
                ObjectType::MultiLeaderStyle(style) if style.name.eq_ignore_ascii_case(name) => {
                    let mut style = style.clone();
                    style.handle = *handle;
                    Some(style)
                }
                _ => None,
            }) {
                crate::scene::annotative::apply_mleader_style(leader, &style);
            }
        }
        EntityType::Table(table) if table.table_style_handle.is_none() => {
            let name = doc.header.current_table_style_name.trim();
            table.table_style_handle = doc.objects.iter().find_map(|(handle, object)| match object {
                ObjectType::TableStyle(style) if style.name.eq_ignore_ascii_case(name) => {
                    Some(*handle)
                }
                _ => None,
            });
        }
        EntityType::MLine(mline) if mline.style_handle.is_none() => {
            let name = doc.header.multiline_style.trim();
            if let Some((handle, style_name)) =
                doc.objects.iter().find_map(|(handle, object)| match object {
                    ObjectType::MLineStyle(style) if style.name.eq_ignore_ascii_case(name) => {
                        Some((*handle, style.name.clone()))
                    }
                    _ => None,
                })
            {
                mline.style_handle = Some(handle);
                mline.style_name = style_name;
            }
        }
        _ => {}
    }
}

pub fn apply_current_creation_styles(doc: &CadDocument, entity: &mut EntityType) {
    apply_text_defaults(doc, entity);
    apply_dimension_defaults(doc, entity);
    apply_object_defaults(doc, entity);
}
