use crate::ecs::text::resources::FontEngine;
use crate::ecs::world::World;

pub(super) fn validate_widget_text(world: &mut World, entity: freecs::Entity, text: &str) {
    let rules: Vec<crate::ecs::ui::components::ValidationRule> = world
        .ui
        .get_ui_node_interaction(entity)
        .map(|interaction| interaction.validation_rules.clone())
        .unwrap_or_default();

    if rules.is_empty() {
        return;
    }

    let mut error: Option<String> = None;
    for rule in &rules {
        if let Err(message) = rule.validate(text) {
            error = Some(message);
            break;
        }
    }

    if let Some(interaction) = world.ui.get_ui_node_interaction_mut(entity) {
        interaction.error_text = error.clone();
        interaction.tooltip_text = error;
    }
}

fn measure_text_width_with_kind(
    font_engine: &mut FontEngine,
    text: &str,
    font_size: f32,
    font_kind: crate::ecs::text::resources::font_engine::FontKind,
) -> f32 {
    if text.is_empty() {
        return 0.0;
    }
    let line_height = font_size * 1.2;
    let buffer = font_engine.shape_buffer(text, font_size, line_height, None, font_kind);
    let mut width = 0.0f32;
    for run in buffer.layout_runs() {
        if run.line_w > width {
            width = run.line_w;
        }
    }
    width
}

pub fn measure_text_width(font_engine: &mut FontEngine, text: &str, font_size: f32) -> f32 {
    measure_text_width_with_kind(
        font_engine,
        text,
        font_size,
        crate::ecs::text::resources::font_engine::FontKind::Default,
    )
}

pub fn measure_text_width_monospace(
    font_engine: &mut FontEngine,
    text: &str,
    font_size: f32,
) -> f32 {
    measure_text_width_with_kind(
        font_engine,
        text,
        font_size,
        crate::ecs::text::resources::font_engine::FontKind::Mono,
    )
}

pub fn byte_index_at_x(
    font_engine: &mut FontEngine,
    text: &str,
    font_size: f32,
    target_x: f32,
) -> usize {
    if text.is_empty() {
        return 0;
    }
    let line_height = font_size * 1.2;
    let buffer = font_engine.shape_buffer(
        text,
        font_size,
        line_height,
        None,
        crate::ecs::text::resources::font_engine::FontKind::Default,
    );
    if let Some(cursor) = buffer.hit(target_x.max(0.0), 0.0) {
        let byte_index = cursor.index;
        text[..byte_index.min(text.len())].chars().count()
    } else {
        text.chars().count()
    }
}

pub fn prev_word_boundary(text: &str, position: usize) -> usize {
    let chars: Vec<char> = text.chars().collect();
    if position == 0 {
        return 0;
    }
    let mut index = position - 1;
    while index > 0 && chars[index].is_whitespace() {
        index -= 1;
    }
    while index > 0 && !chars[index - 1].is_whitespace() {
        index -= 1;
    }
    index
}

pub fn next_word_boundary(text: &str, position: usize) -> usize {
    let chars: Vec<char> = text.chars().collect();
    let len = chars.len();
    if position >= len {
        return len;
    }
    let mut index = position;
    while index < len && !chars[index].is_whitespace() {
        index += 1;
    }
    while index < len && chars[index].is_whitespace() {
        index += 1;
    }
    index
}

pub(super) fn line_col_from_char_position(text: &str, position: usize) -> (usize, usize) {
    let mut line = 0;
    let mut col = 0;
    for (index, character) in text.chars().enumerate() {
        if index == position {
            return (line, col);
        }
        if character == '\n' {
            line += 1;
            col = 0;
        } else {
            col += 1;
        }
    }
    (line, col)
}

pub(super) fn char_position_from_line_col(
    text: &str,
    target_line: usize,
    target_col: usize,
) -> usize {
    let mut line = 0;
    let mut col = 0;
    for (index, character) in text.chars().enumerate() {
        if line == target_line && col == target_col {
            return index;
        }
        if line > target_line {
            return index.saturating_sub(1);
        }
        if character == '\n' {
            if line == target_line {
                return index;
            }
            line += 1;
            col = 0;
        } else {
            col += 1;
        }
    }
    text.chars().count()
}

pub(super) fn line_count(text: &str) -> usize {
    text.chars().filter(|c| *c == '\n').count() + 1
}

pub(super) fn line_start_char_index(text: &str, target_line: usize) -> usize {
    let mut line = 0;
    for (index, character) in text.chars().enumerate() {
        if line == target_line {
            return index;
        }
        if character == '\n' {
            line += 1;
        }
    }
    text.chars().count()
}

pub(super) fn line_text(text: &str, target_line: usize) -> &str {
    text.split('\n').nth(target_line).unwrap_or("")
}
