use egui::{Pos2, Vec2};
use egui_dock::{DockState, NodeIndex};
use serde::{Deserialize, Serialize};
use std::{fs, path::Path};

use crate::ViewId;

/// Defines a `Layout`.
#[derive(Clone, Serialize, Deserialize)]
pub struct Layout {
    window: Option<egui::Rect>,
    window_maximized: bool,
    dock_state: DockState<ViewId>,
}

/// Methods for `Layout`.
impl Layout {
    /// Creates a new `Layout`.
    pub fn new() -> Self {
        let mut dock_state: DockState<ViewId> = DockState::new(Vec::new());
        let surface = dock_state.main_surface_mut();

        surface.push_to_first_leaf(ViewId::Backup);
        surface.push_to_first_leaf(ViewId::Restore);
        surface.push_to_first_leaf(ViewId::Config);
        surface.push_to_first_leaf(ViewId::Keyring);

        let bottom = surface.split_below(NodeIndex::root(), 0.6, vec![ViewId::InfoLog]);

        surface.split_right(bottom[1], 0.5, vec![ViewId::ErrorLog]);

        Self {
            window: Some(egui::Rect::from_min_size(
                Pos2::new(0.0, 0.0),
                Vec2::new(1200.0, 800.0),
            )),
            window_maximized: false,
            dock_state,
        }
    }

    /// Returns a layout with the given `egui::Context`.
    pub fn with_ctx(&self, ctx: &egui::Context) -> Self {
        let viewport = ctx.input(|input_state| input_state.viewport().clone());

        let mut window = self.window;
        let mut window_maximized = self.window_maximized;

        if let Some(maximized) = viewport.maximized {
            window_maximized = maximized;
        }

        if window_maximized {
            window = None;
        } else if let (Some(outer), Some(inner)) = (viewport.outer_rect, viewport.inner_rect) {
            window = Some(egui::Rect::from_min_size(outer.min, inner.size()));
        }

        Self {
            window,
            window_maximized,
            dock_state: self.dock_state.clone(),
        }
    }

    /// Returns a layout with the given `DockState<ViewId>`.
    pub fn with_dock_state(&self, dock_state: DockState<ViewId>) -> Self {
        Self {
            window: self.window,
            window_maximized: self.window_maximized,
            dock_state,
        }
    }

    /// Saves the layout to a file.
    pub fn save(&self, path: &Path) {
        if let Ok(data) = serde_json::to_string_pretty(self) {
            let _ = fs::write(path, data);
        }
    }

    /// Load the layout from a file.
    pub fn load(path: &Path) -> Option<Self> {
        let data = fs::read_to_string(path).ok()?;
        serde_json::from_str(&data).ok()
    }

    /// Applies the layout to a `egui::ViewportBuilder`.
    pub fn apply_to_viewport_builder(
        &self,
        viewport_builder: egui::ViewportBuilder,
    ) -> egui::ViewportBuilder {
        let mut viewport_builder = viewport_builder.with_maximized(self.window_maximized);

        if let Some(window) = self.window {
            viewport_builder = viewport_builder
                .with_position(window.min)
                .with_inner_size(window.size());
        }

        viewport_builder
    }

    /// Applies the layout to a `egui::Context`.
    pub fn apply_to_context(&self, ctx: &egui::Context) {
        if let Some(window) = self.window {
            ctx.send_viewport_cmd(egui::ViewportCommand::OuterPosition(window.min));
            ctx.send_viewport_cmd(egui::ViewportCommand::InnerSize(window.size()));
        }

        ctx.send_viewport_cmd(egui::ViewportCommand::Maximized(self.window_maximized));
    }

    /// Applies the layout to a `DockState<ViewId>`.
    pub fn apply_to_dockstate(&self, dock_state: &mut DockState<ViewId>) {
        *dock_state = self.dock_state.clone();
    }
}

/// Impl `Layout` for Layout.
impl Default for Layout {
    fn default() -> Self {
        Layout::new()
    }
}
