pub mod documentation;
pub mod editor;
pub mod about;

use std::any::Any;
use egui::{Id, Response, Stroke, StrokeKind, Ui, Widget};
use egui_tiles::{Behavior, Container, SimplificationOptions, TabState, Tile, TileId, Tiles, Tree, UiResponse};
use crate::ui::debugger::Action;
use crate::ui::debugger::Action::RequestCloseTab;

/// Anything that can live inside a tab in the central tiled area.
/// `as_any_mut` lets callers reach back into a specific pane's concrete
/// type after it's been handed off to the tree (e.g. to write into the
/// editor's buffer from an `Action` handler).
pub trait TilePane: Any {

    /// the title that's shown on the tab in the UI.
    fn title(&self) -> egui::WidgetText;

    /// main UI function. returns an action in order to communicate with the parent UI
    fn ui(&mut self, ui: &mut egui::Ui, _tile_id: TileId) -> Option<Action>;

    /// required in order to be used with a pane
    fn as_any_mut(&mut self) -> &mut dyn Any;

    /// the menu options exposed by the tile. these inform the main application menu based on the current focused tile
    fn menu(&mut self, _ui: &mut egui::Ui, _tile_id: TileId) -> Option<Action> {
        None
    }

    fn close_requires_confirmation(&mut self, _tile_id: TileId) -> bool {
        false
    }
}

type Pane = Box<dyn TilePane>;

#[derive(Default)]
struct TileBehavior {
    pending_action: Option<Action>,
}

impl Behavior<Pane> for TileBehavior {
    fn pane_ui(
        &mut self,
        ui: &mut egui::Ui,
        tile_id: TileId,
        pane: &mut Box<dyn TilePane>,
    ) -> UiResponse {
        egui::Frame::new()
            .inner_margin(egui::Margin::same(10))
            .stroke(ui.visuals().widgets.active.bg_stroke)
            .show(ui, |ui| {
                if let Some(action) = pane.ui(ui, tile_id) {
                    self.pending_action = Some(action);
                }
            });

        UiResponse::None
    }

    fn tab_title_for_pane(&mut self, pane: &Pane) -> egui::WidgetText {
        pane.title()
    }

    fn is_tab_closable(&self, _tiles: &Tiles<Pane>, _tile_id: TileId) -> bool {
        true
    }

    fn on_tab_close(&mut self, tiles: &mut Tiles<Pane>, tile_id: TileId) -> bool {
        let Some(Tile::Pane(pane)) = tiles.get_mut(tile_id) else {
            return false
        };

        if pane.close_requires_confirmation(tile_id) {
            self.pending_action = Some(RequestCloseTab(tile_id));
            return false;
        }

        tiles.remove(tile_id);
        true
    }


    fn tab_bar_height(&self, _style: &egui::Style) -> f32 {
        36.0 // taller tab row = more vertical padding around the label
    }


    fn simplification_options(&self) -> SimplificationOptions {
        SimplificationOptions {
            // Keep the tab bar around even with only one tab, and don't let
            // panes get dragged out into their own floating windows.
            all_panes_must_have_tabs: true,
            ..Default::default()
        }
    }

    fn tab_bg_color(&self, visuals: &egui::Visuals, _tiles: &Tiles<Box<dyn TilePane>>, _tile_id: TileId, tab_state: &TabState) -> egui::Color32 {
        visuals.widgets.inactive.bg_fill
    }
}

/// A single top-level tab strip that tools can add tabs to at runtime.
pub struct CentralTiles {
    tree: Tree<Pane>,
    behavior: TileBehavior,
}

impl CentralTiles {
    pub fn new() -> Self {
        let mut tiles = Tiles::<Pane>::default();
        let root = tiles.insert_tab_tile(vec![]);
        Self {
            tree: Tree::empty("central_tiles"),
            behavior: TileBehavior::default(),
        }
    }

    pub fn active_tile(&self) -> Option<TileId> {
        match self.tree.root.and_then(|root| self.tree.tiles.get(root)) {
            Some(Tile::Container(Container::Tabs(tabs))) => tabs.active,
            _ => None,
        }
    }

    pub fn contains(&self, tile_id: TileId) -> bool {
        self.tree.tiles.get(tile_id).is_some()
    }

    pub fn add_tab(&mut self, pane: impl TilePane) -> TileId {
        self.add_dyn_tab(Box::new(pane))
    }

    pub fn close_tab(&mut self, tile_id: TileId) {
        self.tree.tiles.remove(tile_id);
    }

    /// Add a new tab and make it active. Returns the `TileId` so the
    /// caller can hold onto it for later access via `pane_mut`.
    pub fn add_dyn_tab(&mut self, pane: Box<dyn TilePane>) -> TileId {
        let tile_id = self.tree.tiles.insert_pane(pane);

        match self.tree.root {
            Some(root) => {
                if let Some(Tile::Container(Container::Tabs(tabs))) = self.tree.tiles.get_mut(root) {
                    tabs.children.push(tile_id);
                    tabs.active = Some(tile_id);
                } else {
                    // Root exists but isn't a Tabs container for some reason —
                    // wrap both under a fresh tab container rather than losing it.
                    let new_root = self.tree.tiles.insert_tab_tile(vec![root, tile_id]);
                    self.tree.root = Some(new_root);
                }
            }
            None => {
                // Tree was empty (or got simplified down to nothing) —
                // this is the first tab, so create the root container now.
                let root = self.tree.tiles.insert_tab_tile(vec![tile_id]);
                self.tree.root = Some(root);
            }
        }

        tile_id
    }

    /// Bring an already-open tab to the front (e.g. re-focus "Docs"
    /// instead of opening a duplicate tab).
    pub fn activate_tab(&mut self, tile_id: TileId) {
        self.tree.make_active(|id, _tile| id == tile_id);
    }

    /// Reach into a specific pane's concrete type by id.
    pub fn pane_mut<T: 'static>(&mut self, tile_id: TileId) -> Option<&mut T> {
        match self.tree.tiles.get_mut(tile_id) {
            Some(Tile::Pane(pane)) => pane.as_any_mut().downcast_mut::<T>(),
            _ => None,
        }
    }

    pub fn ui(&mut self, ui: &mut egui::Ui) -> Option<Action> {
        self.behavior.pending_action = None;
        self.tree.ui(&mut self.behavior, ui);
        self.behavior.pending_action.take()
    }
}

pub struct PanelFactory(pub String, pub Box<dyn Fn() -> Box<dyn TilePane>>);

impl PanelFactory {
    pub fn register<T, F>(name: &str, constructor: F) -> Self
    where
        T: TilePane + 'static,
        F: Fn() -> T + 'static,
    {
        Self(name.to_string(), Box::new(move || Box::new(constructor()) as Box<dyn TilePane>))
    }
}