use alacritty_terminal::vte::ansi::{
    CursorShape as AlacCursorShape, CursorStyle as AlacCursorStyle,
};
use collections::HashMap;
use gpui::{App, FontFallbacks, FontFeatures, FontWeight, Pixels, px};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

pub use settings::AlternateScroll;
use settings::{
    CursorShapeContent, SettingsContent, ShowScrollbar, TerminalBlink, TerminalDockPosition,
    TerminalLineHeight, TerminalSettingsContent, VenvSettings, WorkingDirectory,
};
use task::Shell;
use theme::FontFamilyName;
use util::MergeFrom;

#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub struct Toolbar {
    pub breadcrumbs: bool,
}

#[derive(Clone, Debug, Deserialize)]
pub struct TerminalSettings {
    pub shell: Shell,
    pub working_directory: WorkingDirectory,
    pub font_size: Option<Pixels>, // todo(settings_refactor) can be non-optional...
    pub font_family: Option<FontFamilyName>,
    pub font_fallbacks: Option<FontFallbacks>,
    pub font_features: Option<FontFeatures>,
    pub font_weight: Option<FontWeight>,
    pub line_height: TerminalLineHeight,
    pub env: HashMap<String, String>,
    pub cursor_shape: Option<CursorShape>,
    pub blinking: TerminalBlink,
    pub alternate_scroll: AlternateScroll,
    pub option_as_meta: bool,
    pub copy_on_select: bool,
    pub keep_selection_on_copy: bool,
    pub button: bool,
    pub dock: TerminalDockPosition,
    pub default_width: Pixels,
    pub default_height: Pixels,
    pub detect_venv: VenvSettings,
    pub max_scroll_history_lines: Option<usize>,
    pub toolbar: Toolbar,
    pub scrollbar: ScrollbarSettings,
    pub minimum_contrast: f32,
}

#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub struct ScrollbarSettings {
    /// When to show the scrollbar in the terminal.
    ///
    /// Default: inherits editor scrollbar settings
    pub show: Option<ShowScrollbar>,
}

fn settings_shell_to_task_shell(shell: settings::Shell) -> Shell {
    match shell {
        settings::Shell::System => Shell::System,
        settings::Shell::Program(program) => Shell::Program(program),
        settings::Shell::WithArguments {
            program,
            args,
            title_override,
        } => Shell::WithArguments {
            program,
            args,
            title_override,
        },
    }
}

impl settings::Settings for TerminalSettings {
    fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
        let content = content.terminal.clone().unwrap();
        TerminalSettings {
            shell: settings_shell_to_task_shell(content.shell.unwrap()),
            working_directory: content.working_directory.unwrap(),
            font_size: content.font_size.map(px),
            font_family: content.font_family,
            font_fallbacks: content.font_fallbacks.map(|fallbacks| {
                FontFallbacks::from_fonts(
                    fallbacks
                        .into_iter()
                        .map(|family| family.0.to_string())
                        .collect(),
                )
            }),
            font_features: content.font_features,
            font_weight: content.font_weight.map(FontWeight),
            line_height: content.line_height.unwrap(),
            env: content.env.unwrap(),
            cursor_shape: content.cursor_shape.map(Into::into),
            blinking: content.blinking.unwrap(),
            alternate_scroll: content.alternate_scroll.unwrap(),
            option_as_meta: content.option_as_meta.unwrap(),
            copy_on_select: content.copy_on_select.unwrap(),
            keep_selection_on_copy: content.keep_selection_on_copy.unwrap(),
            button: content.button.unwrap(),
            dock: content.dock.unwrap(),
            default_width: px(content.default_width.unwrap()),
            default_height: px(content.default_height.unwrap()),
            detect_venv: content.detect_venv.unwrap(),
            max_scroll_history_lines: content.max_scroll_history_lines,
            toolbar: Toolbar {
                breadcrumbs: content.toolbar.unwrap().breadcrumbs.unwrap(),
            },
            scrollbar: ScrollbarSettings {
                show: content.scrollbar.unwrap().show.flatten(),
            },
            minimum_contrast: content.minimum_contrast.unwrap(),
        }
    }

    fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) {
        let Some(content) = &content.terminal else {
            return;
        };
        self.shell
            .merge_from(&content.shell.clone().map(settings_shell_to_task_shell));
        self.working_directory
            .merge_from(&content.working_directory);
        if let Some(font_size) = content.font_size.map(px) {
            self.font_size = Some(font_size)
        }
        if let Some(font_family) = content.font_family.clone() {
            self.font_family = Some(font_family);
        }
        if let Some(fallbacks) = content.font_fallbacks.clone() {
            self.font_fallbacks = Some(FontFallbacks::from_fonts(
                fallbacks
                    .into_iter()
                    .map(|family| family.0.to_string())
                    .collect(),
            ))
        }
        if let Some(font_features) = content.font_features.clone() {
            self.font_features = Some(font_features)
        }
        if let Some(font_weight) = content.font_weight {
            self.font_weight = Some(FontWeight(font_weight));
        }
        self.line_height.merge_from(&content.line_height);
        if let Some(env) = &content.env {
            for (key, value) in env {
                self.env.insert(key.clone(), value.clone());
            }
        }
        if let Some(cursor_shape) = content.cursor_shape {
            self.cursor_shape = Some(cursor_shape.into())
        }
        self.blinking.merge_from(&content.blinking);
        self.alternate_scroll.merge_from(&content.alternate_scroll);
        self.option_as_meta.merge_from(&content.option_as_meta);
        self.copy_on_select.merge_from(&content.copy_on_select);
        self.keep_selection_on_copy
            .merge_from(&content.keep_selection_on_copy);
        self.button.merge_from(&content.button);
        self.dock.merge_from(&content.dock);
        self.default_width
            .merge_from(&content.default_width.map(px));
        self.default_height
            .merge_from(&content.default_height.map(px));
        self.detect_venv.merge_from(&content.detect_venv);
        if let Some(max_scroll_history_lines) = content.max_scroll_history_lines {
            self.max_scroll_history_lines = Some(max_scroll_history_lines)
        }
        self.toolbar.breadcrumbs.merge_from(
            &content
                .toolbar
                .as_ref()
                .and_then(|toolbar| toolbar.breadcrumbs),
        );
        self.scrollbar.show.merge_from(
            &content
                .scrollbar
                .as_ref()
                .and_then(|scrollbar| scrollbar.show),
        );
        self.minimum_contrast.merge_from(&content.minimum_contrast);
    }

    fn import_from_vscode(vscode: &settings::VsCodeSettings, content: &mut SettingsContent) {
        let mut default = TerminalSettingsContent::default();
        let current = content.terminal.as_mut().unwrap_or(&mut default);
        let name = |s| format!("terminal.integrated.{s}");

        vscode.f32_setting(&name("fontSize"), &mut current.font_size);
        if let Some(font_family) = vscode.read_string(&name("fontFamily")) {
            current.font_family = Some(FontFamilyName(font_family.into()));
        }
        vscode.bool_setting(&name("copyOnSelection"), &mut current.copy_on_select);
        vscode.bool_setting("macOptionIsMeta", &mut current.option_as_meta);
        vscode.usize_setting("scrollback", &mut current.max_scroll_history_lines);
        match vscode.read_bool(&name("cursorBlinking")) {
            Some(true) => current.blinking = Some(TerminalBlink::On),
            Some(false) => current.blinking = Some(TerminalBlink::Off),
            None => {}
        }
        vscode.enum_setting(
            &name("cursorStyle"),
            &mut current.cursor_shape,
            |s| match s {
                "block" => Some(CursorShapeContent::Block),
                "line" => Some(CursorShapeContent::Bar),
                "underline" => Some(CursorShapeContent::Underline),
                _ => None,
            },
        );
        // they also have "none" and "outline" as options but just for the "Inactive" variant
        if let Some(height) = vscode
            .read_value(&name("lineHeight"))
            .and_then(|v| v.as_f64())
        {
            current.line_height = Some(TerminalLineHeight::Custom(height as f32))
        }

        #[cfg(target_os = "windows")]
        let platform = "windows";
        #[cfg(target_os = "linux")]
        let platform = "linux";
        #[cfg(target_os = "macos")]
        let platform = "osx";
        #[cfg(target_os = "freebsd")]
        let platform = "freebsd";

        // TODO: handle arguments
        let shell_name = format!("{platform}Exec");
        if let Some(s) = vscode.read_string(&name(&shell_name)) {
            current.shell = Some(settings::Shell::Program(s.to_owned()))
        }

        if let Some(env) = vscode
            .read_value(&name(&format!("env.{platform}")))
            .and_then(|v| v.as_object())
        {
            for (k, v) in env {
                if v.is_null()
                    && let Some(zed_env) = current.env.as_mut()
                {
                    zed_env.remove(k);
                }
                let Some(v) = v.as_str() else { continue };
                if let Some(zed_env) = current.env.as_mut() {
                    zed_env.insert(k.clone(), v.to_owned());
                } else {
                    current.env = Some([(k.clone(), v.to_owned())].into_iter().collect())
                }
            }
        }
        if content.terminal.is_none() && default != TerminalSettingsContent::default() {
            content.terminal = Some(default)
        }
    }
}

#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum CursorShape {
    /// Cursor is a block like `█`.
    #[default]
    Block,
    /// Cursor is an underscore like `_`.
    Underline,
    /// Cursor is a vertical bar like `⎸`.
    Bar,
    /// Cursor is a hollow box like `▯`.
    Hollow,
}

impl From<settings::CursorShapeContent> for CursorShape {
    fn from(value: settings::CursorShapeContent) -> Self {
        match value {
            settings::CursorShapeContent::Block => CursorShape::Block,
            settings::CursorShapeContent::Underline => CursorShape::Underline,
            settings::CursorShapeContent::Bar => CursorShape::Bar,
            settings::CursorShapeContent::Hollow => CursorShape::Hollow,
        }
    }
}

impl From<CursorShape> for AlacCursorShape {
    fn from(value: CursorShape) -> Self {
        match value {
            CursorShape::Block => AlacCursorShape::Block,
            CursorShape::Underline => AlacCursorShape::Underline,
            CursorShape::Bar => AlacCursorShape::Beam,
            CursorShape::Hollow => AlacCursorShape::HollowBlock,
        }
    }
}

impl From<CursorShape> for AlacCursorStyle {
    fn from(value: CursorShape) -> Self {
        AlacCursorStyle {
            shape: value.into(),
            blinking: false,
        }
    }
}
