use crate::prelude::*;
use rusterix::{PixelSource, Value};

pub struct Window {
    id: TheId,
    nodeui: TheNodeUI,
}

impl Action for Window {
    fn new() -> Self
    where
        Self: Sized,
    {
        let mut nodeui: TheNodeUI = TheNodeUI::default();

        nodeui.add_item(TheNodeUIItem::OpenTree("window".into()));
        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
            "actionWindowInset".into(),
            "".into(),
            "".into(),
            0.0,
            -1.0..=1.0,
            false,
        ));
        nodeui.add_item(TheNodeUIItem::FloatEditSlider(
            "actionWindowFrameWidth".into(),
            "".into(),
            "".into(),
            0.08,
            0.01..=1.0,
            false,
        ));
        nodeui.add_item(TheNodeUIItem::CloseTree);

        nodeui.add_item(TheNodeUIItem::OpenTree("material".into()));
        nodeui.add_item(TheNodeUIItem::Icons(
            "actionMaterialFrameTile".into(),
            "".into(),
            "".into(),
            vec![(
                TheRGBABuffer::new(TheDim::sized(36, 36)),
                "".to_string(),
                Uuid::nil(),
            )],
        ));
        nodeui.add_item(TheNodeUIItem::Text(
            "actionMaterialFrameTileId".into(),
            "".into(),
            "".into(),
            "".into(),
            None,
            false,
        ));
        nodeui.add_item(TheNodeUIItem::Icons(
            "actionMaterialGlassTile".into(),
            "".into(),
            "".into(),
            vec![(
                TheRGBABuffer::new(TheDim::sized(36, 36)),
                "".to_string(),
                Uuid::nil(),
            )],
        ));
        nodeui.add_item(TheNodeUIItem::Text(
            "actionMaterialGlassTileId".into(),
            "".into(),
            "".into(),
            "".into(),
            None,
            false,
        ));
        nodeui.add_item(TheNodeUIItem::CloseTree);

        nodeui.add_item(TheNodeUIItem::Markdown("desc".into(), "".into()));

        Self {
            id: TheId::named(&fl!("action_window")),
            nodeui,
        }
    }

    fn id(&self) -> TheId {
        self.id.clone()
    }

    fn info(&self) -> String {
        fl!("action_window_desc")
    }

    fn role(&self) -> ActionRole {
        ActionRole::Editor
    }

    fn is_applicable(&self, map: &Map, _ctx: &mut TheContext, server_ctx: &ServerContext) -> bool {
        let profile_edit_active = (server_ctx.editor_view_mode == EditorViewMode::D2
            && server_ctx.editing_surface.is_some())
            || (server_ctx.editor_view_mode != EditorViewMode::D2
                && server_ctx.geometry_edit_mode == GeometryEditMode::Detail
                && server_ctx.active_detail_surface.is_some());
        !map.selected_sectors.is_empty() && profile_edit_active
    }

    fn load_params(&mut self, map: &Map) {
        if let Some(sector_id) = map.selected_sectors.first()
            && let Some(sector) = map.find_sector(*sector_id)
        {
            self.nodeui.set_f32_value(
                "actionWindowInset",
                sector.properties.get_float_default("profile_inset", 0.0),
            );
            self.nodeui.set_f32_value(
                "actionWindowFrameWidth",
                sector
                    .properties
                    .get_float_default("window_frame_width", 0.08),
            );
        }
    }

    fn load_params_project(&mut self, project: &Project, server_ctx: &mut ServerContext) {
        let mut frame_icon = TheRGBABuffer::new(TheDim::sized(36, 36));
        let mut frame_id = Uuid::nil();
        let mut frame_text = String::new();
        let mut glass_icon = TheRGBABuffer::new(TheDim::sized(36, 36));
        let mut glass_id = Uuid::nil();
        let mut glass_text = String::new();

        if let Some(map) = project.get_map(server_ctx)
            && let Some(sector_id) = map.selected_sectors.first()
            && let Some(sector) = map.find_sector(*sector_id)
        {
            if let Some(Value::Source(PixelSource::TileId(id))) =
                sector.properties.get("window_frame_source")
            {
                if let Some(tile) = project.tiles.get(id)
                    && !tile.is_empty()
                {
                    frame_icon = tile.textures[0].to_rgba();
                    frame_id = *id;
                    frame_text = id.to_string();
                }
            }
            if let Some(Value::Source(PixelSource::TileId(id))) =
                sector.properties.get("window_glass_source")
            {
                if let Some(tile) = project.tiles.get(id)
                    && !tile.is_empty()
                {
                    glass_icon = tile.textures[0].to_rgba();
                    glass_id = *id;
                    glass_text = id.to_string();
                }
            }
            if let Some(Value::Source(PixelSource::PaletteIndex(index))) =
                sector.properties.get("window_frame_source")
            {
                frame_id = Uuid::nil();
                frame_icon = TheRGBABuffer::new(TheDim::sized(36, 36));
                frame_text = index.to_string();
            }
            if let Some(Value::Source(PixelSource::PaletteIndex(index))) =
                sector.properties.get("window_glass_source")
            {
                glass_id = Uuid::nil();
                glass_icon = TheRGBABuffer::new(TheDim::sized(36, 36));
                glass_text = index.to_string();
            }
        }
        self.nodeui
            .set_text_value("actionMaterialFrameTileId", frame_text);
        self.nodeui
            .set_text_value("actionMaterialGlassTileId", glass_text);

        if let Some(TheNodeUIItem::Icons(_, _, _, items)) =
            self.nodeui.get_item_mut("actionMaterialFrameTile")
            && items.len() == 1
        {
            items[0].0 = frame_icon;
            items[0].2 = frame_id;
        }

        if let Some(TheNodeUIItem::Icons(_, _, _, items)) =
            self.nodeui.get_item_mut("actionMaterialGlassTile")
            && items.len() == 1
        {
            items[0].0 = glass_icon;
            items[0].2 = glass_id;
        }
    }

    fn apply(
        &self,
        map: &mut Map,
        _ui: &mut TheUI,
        _ctx: &mut TheContext,
        server_ctx: &mut ServerContext,
    ) -> Option<ProjectUndoAtom> {
        let prev = map.clone();
        let mut changed = false;

        let inset = self
            .nodeui
            .get_f32_value("actionWindowInset")
            .unwrap_or(0.0);
        let frame_w = self
            .nodeui
            .get_f32_value("actionWindowFrameWidth")
            .unwrap_or(0.08);

        let frame_icon_id = self
            .nodeui
            .get_tile_id("actionMaterialFrameTile", 0)
            .unwrap_or(Uuid::nil());
        let glass_icon_id = self
            .nodeui
            .get_tile_id("actionMaterialGlassTile", 0)
            .unwrap_or(Uuid::nil());

        let frame_text = self
            .nodeui
            .get_text_value("actionMaterialFrameTileId")
            .unwrap_or_default();
        let glass_text = self
            .nodeui
            .get_text_value("actionMaterialGlassTileId")
            .unwrap_or_default();

        let parse_source = |text: &str, fallback: Uuid| -> Option<PixelSource> {
            let t = text.trim();
            if !t.is_empty() {
                if let Ok(id) = Uuid::parse_str(t) {
                    return Some(PixelSource::TileId(id));
                }
                if let Ok(idx) = t.parse::<u16>() {
                    return Some(PixelSource::PaletteIndex(idx));
                }
            }
            if fallback != Uuid::nil() {
                Some(PixelSource::TileId(fallback))
            } else {
                None
            }
        };
        let frame_source = parse_source(&frame_text, frame_icon_id);
        let glass_source = parse_source(&glass_text, glass_icon_id);

        for sector_id in &map.selected_sectors.clone() {
            if let Some(sector) = map.find_sector_mut(*sector_id) {
                sector.properties.set("profile_op", Value::Int(4));
                sector.properties.set("profile_inset", Value::Float(inset));
                sector
                    .properties
                    .set("window_frame_width", Value::Float(frame_w.max(0.01)));

                if let Some(source) = frame_source.clone() {
                    sector
                        .properties
                        .set("window_frame_source", Value::Source(source));
                } else {
                    sector.properties.remove("window_frame_source");
                }

                if let Some(source) = glass_source.clone() {
                    sector
                        .properties
                        .set("window_glass_source", Value::Source(source));
                } else {
                    sector.properties.remove("window_glass_source");
                }

                changed = true;
            }
        }

        if changed {
            Some(ProjectUndoAtom::MapEdit(
                server_ctx.pc,
                Box::new(prev),
                Box::new(map.clone()),
            ))
        } else {
            None
        }
    }

    fn params(&self) -> TheNodeUI {
        self.nodeui.clone()
    }

    fn hud_material_slots(
        &self,
        _map: &Map,
        _server_ctx: &ServerContext,
    ) -> Option<Vec<ActionMaterialSlot>> {
        let frame_icon_id = self
            .nodeui
            .get_tile_id("actionMaterialFrameTile", 0)
            .unwrap_or(Uuid::nil());
        let glass_icon_id = self
            .nodeui
            .get_tile_id("actionMaterialGlassTile", 0)
            .unwrap_or(Uuid::nil());
        let parse_source = |text: &str, fallback: Uuid| -> Option<PixelSource> {
            let t = text.trim();
            if !t.is_empty() {
                if let Ok(id) = Uuid::parse_str(t) {
                    return Some(PixelSource::TileId(id));
                }
                if let Ok(idx) = t.parse::<u16>() {
                    return Some(PixelSource::PaletteIndex(idx));
                }
            }
            if fallback != Uuid::nil() {
                Some(PixelSource::TileId(fallback))
            } else {
                None
            }
        };

        Some(vec![
            ActionMaterialSlot {
                label: "FRAME".to_string(),
                source: parse_source(
                    &self
                        .nodeui
                        .get_text_value("actionMaterialFrameTileId")
                        .unwrap_or_default(),
                    frame_icon_id,
                ),
            },
            ActionMaterialSlot {
                label: "GLASS".to_string(),
                source: parse_source(
                    &self
                        .nodeui
                        .get_text_value("actionMaterialGlassTileId")
                        .unwrap_or_default(),
                    glass_icon_id,
                ),
            },
        ])
    }

    fn set_hud_material_from_tile(
        &mut self,
        _map: &Map,
        _server_ctx: &ServerContext,
        slot_index: i32,
        tile_id: Uuid,
    ) -> bool {
        match slot_index {
            0 => {
                self.nodeui
                    .set_text_value("actionMaterialFrameTileId", tile_id.to_string());
                set_nodeui_icon_tile_id(&mut self.nodeui, "actionMaterialFrameTile", 0, tile_id);
            }
            1 => {
                self.nodeui
                    .set_text_value("actionMaterialGlassTileId", tile_id.to_string());
                set_nodeui_icon_tile_id(&mut self.nodeui, "actionMaterialGlassTile", 0, tile_id);
            }
            _ => return false,
        }
        true
    }

    fn clear_hud_material_slot(
        &mut self,
        _map: &Map,
        _server_ctx: &ServerContext,
        slot_index: i32,
    ) -> bool {
        match slot_index {
            0 => {
                self.nodeui
                    .set_text_value("actionMaterialFrameTileId", String::new());
                clear_nodeui_icon_tile_id(&mut self.nodeui, "actionMaterialFrameTile", 0);
            }
            1 => {
                self.nodeui
                    .set_text_value("actionMaterialGlassTileId", String::new());
                clear_nodeui_icon_tile_id(&mut self.nodeui, "actionMaterialGlassTile", 0);
            }
            _ => return false,
        }
        true
    }

    fn handle_event(
        &mut self,
        event: &TheEvent,
        project: &mut Project,
        _ui: &mut TheUI,
        ctx: &mut TheContext,
        _server_ctx: &mut ServerContext,
    ) -> bool {
        if let TheEvent::TileDropped(id, tile_id, index) = event
            && let Some(item) = self.nodeui.get_item_mut(&id.name)
            && let TheNodeUIItem::Icons(_, _, _, items) = item
            && *index < items.len()
            && let Some(tile) = project.tiles.get(tile_id)
            && !tile.is_empty()
        {
            items[*index].0 = tile.textures[0].to_rgba();
            items[*index].2 = *tile_id;

            if id.name == "actionMaterialFrameTile" {
                self.nodeui
                    .set_text_value("actionMaterialFrameTileId", tile_id.to_string());
            } else if id.name == "actionMaterialGlassTile" {
                self.nodeui
                    .set_text_value("actionMaterialGlassTileId", tile_id.to_string());
            }

            ctx.ui.send(TheEvent::Custom(
                TheId::named("Update Action List"),
                TheValue::Empty,
            ));
            return true;
        }
        self.nodeui.handle_event(event)
    }
}
