use crate::editor::{CONFIGEDITOR, PALETTE, SCENEMANAGER};
use crate::prelude::*;
use codegridfx::{Module, ModuleType};
use rusteria::{RenderBuffer, Rusteria};
use rusterix::{PixelSource, Value, ValueContainer, pixel_to_vec4};
use std::str::FromStr;
use toml::*;

pub fn default_preview_rigging_toml() -> String {
    if let Some(bytes) = crate::Embedded::get("toml/preview_rigging.toml")
        && let Ok(source) = std::str::from_utf8(bytes.data.as_ref())
    {
        return source.to_string();
    }
    "# Editor-only preview. Runtime equipment/animation comes from server scripts.\nanimation = \"Idle\"\nperspective = \"Front\"\nplay = true\nspeed = 1.0\n\n[slots]\n# main_hand = \"Item Name\"\n# off_hand = \"Item Name\"\n".to_string()
}

pub fn update_region_settings(
    project: &mut Project,
    server_ctx: &ServerContext,
) -> Result<bool, Box<dyn std::error::Error>> {
    let mut changed = false;
    if let Some(id) = server_ctx.pc.id() {
        if server_ctx.pc.is_region() {
            if let Some(region) = project.get_region_mut(&id) {
                let parsed: toml::Value = toml::from_str(&region.config)?;
                // Parse [render] section
                if let Some(section) = parsed.get("terrain") {
                    if let Some(enabled) = section.get("enabled") {
                        let enabled = enabled.as_bool().unwrap_or(false);
                        // let existing = region
                        //     .map
                        //     .properties
                        //     .get_bool_default("terain_enabled", false);
                        // if enabled != existing {
                        region
                            .map
                            .properties
                            .set("terrain_enabled", Value::Bool(enabled));
                        changed = true;
                        // }
                    }

                    let mut got_tile_id = false;
                    if let Some(enabled) = section.get("tile_id") {
                        if let Some(tile_id) = enabled.as_str() {
                            if let Ok(id) = Uuid::from_str(tile_id) {
                                region.map.properties.set(
                                    "default_terrain_tile",
                                    Value::Source(PixelSource::TileId(id)),
                                );
                                got_tile_id = true;
                            }
                        }
                    }
                    if !got_tile_id {
                        region.map.properties.remove("default_terrain_tile");
                    }
                }

                if let Some(section) = parsed.get("preview").and_then(toml::Value::as_table) {
                    region.map.properties.remove("preview_hide");
                    if let Some(values) = section.get("hide").and_then(toml::Value::as_array) {
                        let hide: Vec<String> = values
                            .iter()
                            .filter_map(toml::Value::as_str)
                            .map(str::trim)
                            .filter(|s| !s.is_empty())
                            .map(str::to_string)
                            .collect();
                        if !hide.is_empty() {
                            region
                                .map
                                .properties
                                .set("preview_hide", Value::StrArray(hide));
                        }
                    }
                    changed = true;
                } else {
                    region.map.properties.remove("preview_hide");
                }
            }
        }
    }

    Ok(changed)
}

/// Sets the code for the code editor based on the current editor mode
pub fn set_code(
    ui: &mut TheUI,
    ctx: &mut TheContext,
    project: &mut Project,
    server_ctx: &ServerContext,
) {
    let mut success = false;

    match server_ctx.cc {
        ContentContext::CharacterInstance(uuid) => {
            if let Some(region) = project.get_region_mut(&server_ctx.curr_region) {
                if let Some(character_instance) = region.characters.get_mut(&uuid) {
                    ui.set_widget_value(
                        "CodeEdit",
                        ctx,
                        TheValue::Text(character_instance.source.clone()),
                    );
                    success = true;
                }
            }
        }
        ContentContext::Sector(uuid) => {
            if let Some(map) = project.get_map_mut(server_ctx) {
                for s in &map.sectors {
                    if s.creator_id == uuid {
                        if let Some(Value::Str(source)) = s.properties.get("source") {
                            ui.set_widget_value("CodeEdit", ctx, TheValue::Text(source.clone()));
                            success = true;
                        }
                        if let Some(Value::Str(data)) = s.properties.get("data") {
                            ui.set_widget_value("DataEdit", ctx, TheValue::Text(data.clone()));
                            success = true;
                        }
                        break;
                    }
                }
            }
        }
        ContentContext::CharacterTemplate(uuid) => {
            if let Some(character) = project.characters.get_mut(&uuid) {
                ui.set_widget_value("CodeEdit", ctx, TheValue::Text(character.source.clone()));
                ui.set_widget_value("DataEdit", ctx, TheValue::Text(character.data.clone()));
                character
                    .module
                    .set_module_type(ModuleType::CharacterTemplate);
                success = true;
            }
        }
        ContentContext::ItemTemplate(uuid) => {
            if let Some(item) = project.items.get_mut(&uuid) {
                ui.set_widget_value("CodeEdit", ctx, TheValue::Text(item.source.clone()));
                ui.set_widget_value("DataEdit", ctx, TheValue::Text(item.data.clone()));
                item.module.set_module_type(ModuleType::ItemTemplate);
                success = true;
            }
        }
        ContentContext::ItemInstance(uuid) => {
            let mut temp_id = None;

            if let Some(region) = project.get_region_mut(&server_ctx.curr_region) {
                if let Some(item_inst) = region.items.get_mut(&uuid) {
                    temp_id = Some(item_inst.item_id);
                }
            }

            if let Some(temp_id) = temp_id {
                if let Some(item) = project.items.get_mut(&temp_id) {
                    ui.set_widget_value("CodeEdit", ctx, TheValue::Text(item.source.clone()));
                    ui.set_widget_value("DataEdit", ctx, TheValue::Text(item.data.clone()));
                    item.module.set_module_type(ModuleType::ItemTemplate);
                    success = true;
                }
            }
        }
        _ => {}
    }

    /*
    let sidebarmode = SIDEBARMODE.read().unwrap();
    if *sidebarmode == SidebarMode::Region {
        if let Some(region_content_id) = server_ctx.curr_region_content {
            if let Some(region) = project.get_region_mut(&server_ctx.curr_region) {
                // Check for Character Instance
                if let Some(character_instance) = region.characters.get_mut(&region_content_id) {
                    ui.set_widget_value(
                        "CodeEdit",
                        ctx,
                        TheValue::Text(character_instance.source.clone()),
                    );
                    success = true;
                } else {
                    // Check for Sector
                    for s in &region.map.sectors {
                        if s.creator_id == region_content_id {
                            ui.set_widget_value("CodeEdit", ctx, TheValue::Text(String::new()));
                        }
                    }
                }
            }
        }
    } else if *sidebarmode == SidebarMode::Character {
        if let Some(character_id) = &server_ctx.curr_character {
            if let Some(character) = project.characters.get(character_id) {
                ui.set_widget_value("CodeEdit", ctx, TheValue::Text(character.source.clone()));
                success = true;
            }
        }
    }*/

    if !success {
        ui.set_widget_value("CodeEdit", ctx, TheValue::Text(String::new()));
    }
}

/// Returns the currently active source
pub fn get_source(_ui: &mut TheUI, server_ctx: &ServerContext) -> Option<PixelSource> {
    if let Some(source) = server_ctx.curr_tile_source {
        return Some(match source {
            TileSource::SingleTile(id) => PixelSource::TileId(id),
            TileSource::TileGroup(id) => server_ctx
                .curr_tile_id
                .map(PixelSource::TileId)
                .unwrap_or(PixelSource::TileGroup(id)),
            TileSource::TileGroupMember {
                group_id,
                member_index,
            } => server_ctx.curr_tile_id.map(PixelSource::TileId).unwrap_or(
                PixelSource::TileGroupMember {
                    group_id,
                    member_index,
                },
            ),
            TileSource::Procedural(id) => server_ctx
                .curr_tile_id
                .map(PixelSource::TileId)
                .unwrap_or(PixelSource::ProceduralTile(id)),
        });
    }

    server_ctx.curr_tile_id.map(PixelSource::TileId)
}

#[derive(Clone)]
pub enum SurfaceApplySource {
    Direct(PixelSource),
    TileGroup {
        group: rusterix::TileGroup,
        flip_y: bool,
    },
}

pub fn get_surface_apply_source(
    project: &Project,
    server_ctx: &ServerContext,
) -> Option<SurfaceApplySource> {
    if let Some(source) = server_ctx.curr_tile_source {
        return match source {
            TileSource::SingleTile(id) => Some(SurfaceApplySource::Direct(PixelSource::TileId(id))),
            TileSource::TileGroup(id) => {
                project
                    .tile_groups
                    .get(&id)
                    .cloned()
                    .map(|group| SurfaceApplySource::TileGroup {
                        group,
                        flip_y: project.tile_node_groups.contains_key(&id),
                    })
            }
            TileSource::TileGroupMember { .. } => server_ctx
                .curr_tile_id
                .map(PixelSource::TileId)
                .map(SurfaceApplySource::Direct),
            TileSource::Procedural(_) => server_ctx
                .curr_tile_id
                .map(PixelSource::TileId)
                .map(SurfaceApplySource::Direct),
        };
    }

    server_ctx
        .curr_tile_id
        .map(PixelSource::TileId)
        .map(SurfaceApplySource::Direct)
}

fn ensure_sector_surface(map: &mut Map, sector_id: u32) -> Option<rusterix::Surface> {
    if map.get_surface_for_sector_id(sector_id).is_none() {
        let mut surface = rusterix::Surface::new(sector_id);
        surface.calculate_geometry(map);
        let id = surface.id;
        map.surfaces.insert(id, surface);
    }
    map.get_surface_for_sector_id(sector_id).cloned()
}

fn clear_sector_tile_overrides(map: &mut Map, sector_id: u32) {
    if let Some(sector) = map.find_sector_mut(sector_id) {
        sector.properties.remove("tiles");
        sector.properties.remove("blend_tiles");
    }
}

fn point_in_polygon_2d(point: Vec2<f32>, polygon: &[Vec2<f32>]) -> bool {
    if polygon.len() < 3 {
        return false;
    }
    let mut inside = false;
    let mut j = polygon.len() - 1;
    for i in 0..polygon.len() {
        let pi = polygon[i];
        let pj = polygon[j];
        let crosses = (pi.y > point.y) != (pj.y > point.y);
        if crosses {
            let denom = (pj.y - pi.y).abs().max(1e-6);
            let x = (pj.x - pi.x) * (point.y - pi.y) / denom + pi.x;
            if point.x < x {
                inside = !inside;
            }
        }
        j = i;
    }
    inside
}

fn sector_local_tile_cells(map: &Map, surface: &rusterix::Surface) -> Vec<(i32, i32)> {
    let Some(loop_uv) = surface.sector_loop_uv(map) else {
        return Vec::new();
    };
    let local_loop: Vec<Vec2<f32>> = loop_uv
        .into_iter()
        .map(|uv| surface.uv_to_tile_local(uv, map))
        .collect();
    if local_loop.len() < 3 {
        return Vec::new();
    }

    let mut min = Vec2::new(f32::INFINITY, f32::INFINITY);
    let mut max = Vec2::new(f32::NEG_INFINITY, f32::NEG_INFINITY);
    for point in &local_loop {
        min.x = min.x.min(point.x);
        min.y = min.y.min(point.y);
        max.x = max.x.max(point.x);
        max.y = max.y.max(point.y);
    }

    let mut cells = Vec::new();
    for y in min.y.floor() as i32..max.y.ceil() as i32 {
        for x in min.x.floor() as i32..max.x.ceil() as i32 {
            let center = Vec2::new(x as f32 + 0.5, y as f32 + 0.5);
            if point_in_polygon_2d(center, &local_loop) {
                cells.push((x, y));
            }
        }
    }
    cells
}

pub fn apply_surface_source_to_sector(
    map: &mut Map,
    sector_id: u32,
    source_key: &str,
    source: &SurfaceApplySource,
    tile_mode: Option<i32>,
) -> bool {
    let target_sector_ids: Vec<u32> = if let Some(sector) = map.find_sector(sector_id) {
        let generated_by = sector
            .properties
            .get_str_default("generated_by", String::new());
        let dungeon_part = sector
            .properties
            .get_str_default("dungeon_part", String::new());
        if generated_by == "dungeon_tool"
            && (dungeon_part == "stair_tread"
                || dungeon_part == "stair_riser"
                || dungeon_part == "stair_ceiling")
            && let (Some(layer_id), Some(cell_x), Some(cell_y)) = (
                sector.properties.get_id("dungeon_layer_id"),
                sector.properties.get_int("dungeon_cell_x"),
                sector.properties.get_int("dungeon_cell_y"),
            )
        {
            map.sectors
                .iter()
                .filter(|candidate| {
                    candidate
                        .properties
                        .get_str_default("generated_by", String::new())
                        == "dungeon_tool"
                        && candidate.properties.get_id("dungeon_layer_id") == Some(layer_id)
                        && candidate.properties.get_int("dungeon_cell_x") == Some(cell_x)
                        && candidate.properties.get_int("dungeon_cell_y") == Some(cell_y)
                        && matches!(
                            candidate
                                .properties
                                .get_str_default("dungeon_part", String::new())
                                .as_str(),
                            "stair_tread" | "stair_riser" | "stair_ceiling"
                        )
                })
                .map(|sector| sector.id)
                .collect()
        } else {
            vec![sector_id]
        }
    } else {
        vec![sector_id]
    };

    match source {
        SurfaceApplySource::Direct(pixel_source) => {
            let mut changed = false;
            for sector_id in target_sector_ids {
                clear_sector_tile_overrides(map, sector_id);
                if let Some(sector) = map.find_sector_mut(sector_id) {
                    sector
                        .properties
                        .set(source_key, Value::Source(pixel_source.clone()));
                    let dungeon_part = sector
                        .properties
                        .get_str_default("dungeon_part", String::new());
                    if source_key == "source"
                        && matches!(
                            dungeon_part.as_str(),
                            "stair_tread" | "stair_riser" | "stair_ceiling"
                        )
                    {
                        sector
                            .properties
                            .set("source", Value::Source(pixel_source.clone()));
                        sector
                            .properties
                            .set("floor_source", Value::Source(pixel_source.clone()));
                        sector
                            .properties
                            .set("ceiling_source", Value::Source(pixel_source.clone()));
                    }
                    if let Some(tile_mode) = tile_mode {
                        sector.properties.set("tile_mode", Value::Int(tile_mode));
                    }
                    changed = true;
                }
            }
            changed
        }
        SurfaceApplySource::TileGroup { group, flip_y } => {
            if group.width == 0 || group.height == 0 || group.members.is_empty() {
                return false;
            }
            if source_key != "source" {
                let mut changed = false;
                for sector_id in target_sector_ids {
                    if let Some(member) = group.members.first()
                        && let Some(sector) = map.find_sector_mut(sector_id)
                    {
                        sector.properties.set(
                            source_key,
                            Value::Source(PixelSource::TileId(member.tile_id)),
                        );
                        if let Some(tile_mode) = tile_mode {
                            sector.properties.set("tile_mode", Value::Int(tile_mode));
                        }
                        changed = true;
                    }
                }
                return changed;
            }

            clear_sector_tile_overrides(map, sector_id);
            let Some(surface) = ensure_sector_surface(map, sector_id) else {
                return false;
            };
            let cells = sector_local_tile_cells(map, &surface);
            if cells.is_empty() {
                return false;
            }

            let member_lookup: FxHashMap<(i32, i32), Uuid> = group
                .members
                .iter()
                .map(|member| ((member.x as i32, member.y as i32), member.tile_id))
                .collect();
            let mut tiles: FxHashMap<(i32, i32), PixelSource> = FxHashMap::default();

            for (x, y) in cells {
                let gx = x.rem_euclid(group.width as i32);
                let gy = if *flip_y {
                    (group.height as i32 - 1) - y.rem_euclid(group.height as i32)
                } else {
                    y.rem_euclid(group.height as i32)
                };
                if let Some(tile_id) = member_lookup.get(&(gx, gy)) {
                    tiles.insert((x, y), PixelSource::TileId(*tile_id));
                }
            }

            if let Some(sector) = map.find_sector_mut(sector_id) {
                if let Some(first) = group.members.first() {
                    sector
                        .properties
                        .set("source", Value::Source(PixelSource::TileId(first.tile_id)));
                }
                if let Some(tile_mode) = tile_mode {
                    sector.properties.set("tile_mode", Value::Int(tile_mode));
                }
                if tiles.is_empty() {
                    sector.properties.remove("tiles");
                } else {
                    sector.properties.set("tiles", Value::TileOverrides(tiles));
                }
                sector.properties.remove("blend_tiles");
                return true;
            }
            false
        }
    }
}

pub fn clear_surface_source_on_sector(map: &mut Map, sector_id: u32, source_key: &str) -> bool {
    if source_key == "source" {
        clear_sector_tile_overrides(map, sector_id);
    }
    if let Some(sector) = map.find_sector_mut(sector_id) {
        sector
            .properties
            .set(source_key, Value::Source(PixelSource::Off));
        return true;
    }
    false
}

pub fn extract_build_values_from_config(values: &mut ValueContainer) {
    let config = CONFIGEDITOR.read().unwrap();
    let sample_mode = config.get_string_default("render", "sample_mode", "nearest");
    if sample_mode == "linear" {
        values.set(
            "sample_mode",
            Value::SampleMode(rusterix::SampleMode::Linear),
        );
    } else {
        values.set(
            "sample_mode",
            Value::SampleMode(rusterix::SampleMode::Nearest),
        );
    }
}

/// Reads map relevant region settings from the TOML config and stores it in the map.
pub fn apply_region_config(map: &mut Map, config: String) {
    // Reset editor-only preview filters and repopulate from config if present.
    map.properties.remove("preview_hide");

    if let Ok(table) = config.parse::<Table>() {
        if let Some(rendering) = table.get("rendering").and_then(toml::Value::as_table) {
            // Daylight
            if let Some(value) = rendering.get("receives_daylight") {
                if let Some(v) = value.as_bool() {
                    map.properties.set("receives_daylight", Value::Bool(v));
                }
            }

            // Fog
            if let Some(value) = rendering.get("fog_enabled") {
                if let Some(v) = value.as_bool() {
                    map.properties.set("fog_enabled", Value::Bool(v));
                }
            }
            if let Some(value) = rendering.get("fog_start_distance") {
                if let Some(v) = value.as_float() {
                    map.properties
                        .set("fog_start_distance", Value::Float(v as f32));
                }
            }
            if let Some(value) = rendering.get("fog_end_distance") {
                if let Some(v) = value.as_float() {
                    map.properties
                        .set("fog_end_distance", Value::Float(v as f32));
                }
            }
            let mut fog_color = Vec4::zero();
            if let Some(value) = rendering.get("fog_color") {
                if let Some(v) = value.as_str() {
                    let c = hex_to_rgba_u8(v);
                    fog_color = pixel_to_vec4(&c);
                }
            }
            map.properties.set(
                "fog_color",
                Value::Vec4([fog_color.x, fog_color.y, fog_color.z, fog_color.w]),
            );
        }

        if let Some(preview) = table.get("preview").and_then(toml::Value::as_table)
            && let Some(hide_values) = preview.get("hide").and_then(toml::Value::as_array)
        {
            let hide: Vec<String> = hide_values
                .iter()
                .filter_map(toml::Value::as_str)
                .map(str::to_string)
                .filter(|s| !s.is_empty())
                .collect();
            if !hide.is_empty() {
                map.properties.set("preview_hide", Value::StrArray(hide));
            }
        }
    }
}

/// Converts an hex string to a vec4 color
pub fn hex_to_rgba_u8(hex: &str) -> [u8; 4] {
    let hex = hex.trim_start_matches('#');

    match hex.len() {
        6 => match (
            u8::from_str_radix(&hex[0..2], 16),
            u8::from_str_radix(&hex[2..4], 16),
            u8::from_str_radix(&hex[4..6], 16),
        ) {
            (Ok(r), Ok(g), Ok(b)) => [r, g, b, 255],
            _ => [255, 255, 255, 255],
        },
        8 => match (
            u8::from_str_radix(&hex[0..2], 16),
            u8::from_str_radix(&hex[2..4], 16),
            u8::from_str_radix(&hex[4..6], 16),
            u8::from_str_radix(&hex[6..8], 16),
        ) {
            (Ok(r), Ok(g), Ok(b), Ok(a)) => [r, g, b, a],
            _ => [255, 255, 255, 255],
        },
        _ => [255, 255, 255, 255],
    }
}

/// Checks if the string is a valid python variable name
pub fn is_valid_python_variable(name: &str) -> bool {
    // Must not be empty, must start with a letter or underscore, and only contain letters, digits, or underscores
    let mut chars = name.chars();
    match chars.next() {
        Some(c) if c.is_ascii_alphabetic() || c == '_' => (),
        _ => return false,
    }
    if name.is_empty() {
        return false;
    }
    if name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
        // Python keywords are not valid variable names
        const PYTHON_KEYWORDS: &[&str] = &[
            "False", "None", "True", "and", "as", "assert", "break", "class", "continue", "def",
            "del", "elif", "else", "except", "finally", "for", "from", "global", "if", "import",
            "in", "is", "lambda", "nonlocal", "not", "or", "pass", "raise", "return", "try",
            "while", "with", "yield",
        ];
        !PYTHON_KEYWORDS.contains(&name)
    } else {
        false
    }
}

/// Draws the shader into the given buffer
pub fn draw_shader_into(module: &Module, buffer: &mut TheRGBABuffer) {
    use std::sync::{Arc, Mutex};

    let mut rs = Rusteria::default();
    let code = module.build_shader();

    let _module = match rs.parse_str(&code) {
        Ok(module) => match rs.compile(&module) {
            Ok(()) => {
                println!("Module '{}' compiled successfully.", module.name);
            }
            Err(e) => {
                eprintln!("Error compiling module: {e}");
                return;
            }
        },
        Err(e) => {
            eprintln!("Error parsing module: {e}");
            return;
        }
    };

    let width = buffer.dim().width as usize;
    let height = buffer.dim().height as usize;

    if let Some(shade_index) = rs.context.program.shade_index {
        let mut rbuffer = Arc::new(Mutex::new(RenderBuffer::new(width, height)));
        let t0 = rs.get_time();
        rs.shade(&mut rbuffer, shade_index, &PALETTE.read().unwrap());
        let t1 = rs.get_time();
        println!("Rendered in {}ms", t1 - t0);

        let b = rbuffer.lock().unwrap().as_rgba_bytes();
        *buffer = TheRGBABuffer::from(b, width as u32, height as u32)
    }
}

/// Renders the current map in the scenemanager depending on the current viewmode.
pub fn scenemanager_render_map(project: &Project, server_ctx: &ServerContext) {
    // Screens should not trigger SceneManager SetMap updates.
    if server_ctx.get_map_context() == MapContext::Screen {
        return;
    }

    if server_ctx.editor_view_mode == EditorViewMode::D2 {
        // In 2D we render the current map (region/profile/character/item), but never screens.
        if let Some(map) = project.get_map(server_ctx) {
            let mut map = map.clone();
            map.properties.set(
                "editing_filter_dungeon",
                Value::Bool(server_ctx.editing_geo_filter == EditingGeoFilter::DungeonOnly),
            );
            map.properties.set(
                "dungeon_no_ceiling",
                Value::Bool(server_ctx.dungeon_no_ceiling),
            );
            SCENEMANAGER.write().unwrap().set_map(map);
        }
    } else {
        // In 3D, SceneManager builds region geometry. This includes profile/surface editing
        // contexts, but excludes non-region contexts.
        if server_ctx.get_map_context() != MapContext::Region {
            return;
        }

        // In 3D region mode (including profiles), render the region map.
        if let Some(id) = server_ctx.pc.id() {
            if let Some(region) = project.get_region(&id) {
                let mut map = region.map.clone();
                // Keep editor-only preview filters in sync with region config for 3D builds.
                apply_region_config(&mut map, region.config.clone());
                map.properties.set(
                    "editing_filter_dungeon",
                    Value::Bool(server_ctx.editing_geo_filter == EditingGeoFilter::DungeonOnly),
                );
                map.properties.set(
                    "dungeon_no_ceiling",
                    Value::Bool(server_ctx.dungeon_no_ceiling),
                );
                SCENEMANAGER.write().unwrap().set_map(map);
            }
        }
    }
}
