use clap::Parser;
use std::fs;
use std::path::PathBuf;

const DEFAULT_CONFIG: &str = r#"## Fount configuration file
## Place this file at ~/.config/fount/fount.conf
##
## Use "set <option>" to enable a boolean option or assign a value.
## Use "unset <option>" to disable a boolean option.

## Editor View

# Show scene numbers in the left margin.
set show_scene_numbers

# Show page numbers on the right side of the screen.
set show_page_numbers

# Mirror scene numbers to the right margin instead of page numbers.
# Available values: "always" (editor and export), "export" (export only), "off"
set mirror_scene_numbers "export"

# Automatically hide Fountain markup when the cursor is not
# on the current line.
set hide_markup

# Highlight active action line (or nearest action line above)
# in bright white color.
unset highlight_active_action

# Typewriter mode
unset typewriter_mode

# Strict typewriter mode (forces the active line to stay in the exact
# vertical center of the terminal at all times, even at the beginning
# of the document).
unset strict_typewriter_mode

# Focus mode
unset focus_mode

## Editor Behavior

# Auto-complete scene headings (INT./EXT.) and character names.
set autocomplete

# Automatically append (CONT'D) to a character name when they speak
# consecutively.
set auto_contd

# Automatically insert paragraph breaks (double newlines) after Action,
# Dialogue, and similar elements.
set auto_paragraph_breaks

# Automatically insert a closing parenthesis when typing an opening one.
set match_parentheses

# Automatically close paired elements such as [[]], /**/, and ****.
set close_elements

# Insert a blank Title Page template when creating a new file.
unset auto_title_page

## Formatting

# The string appended to a character name when they speak consecutively.
set contd_extension "(CONT'D)"

# Allow action blocks to be split across pages.
# Use "unset break_actions" to keep action blocks on a single page.
set break_actions

# Open the file with the cursor at the end
unset goto_end

# Styling applied to scene headings. Available values: "bold",
# "underline", "bold underline"
set heading_style "bold"

# Number of blank lines before a scene heading. Set to 2 for double
# spacing before each new scene.
set heading_spacing 1

# Styling applied to shots (e.g. !! CLOSE UP). Available values: "bold",
# "underline", "bold underline"
set shot_style "bold"

## Display & Terminal

# Disable all terminal colors. Fount will still render bold, italic,
# and underline modifiers if supported by your terminal. Fount tries
# to detect color support automatically.
unset no_color

# Disable all text formatting (bold, italic, underline).
unset no_formatting

# Force output of ANSI color escape codes, even if Fount detects
# that your terminal does not support them.
unset force_ansi

# Force output of ASCII characters instead of Unicode (e.g., for page
# break lines). Useful for older terminals. Fount will try to detect
# Unicode support automatically.
unset force_ascii

# Enable Nerd Font icons (e.g., modern symbols for checkmarks).
# Default is true on most systems, but can be unset for standard Unicode.
set use_nerd_fonts

## PDF Export

# Paper size for PDF export. Available values: "a4", "letter"
set paper_size "a4"

# Force scene numbers to be generated in PDF export even if they
# are not explicitly numbered in the Fountain source.
unset force_scene_numbers

# Render scene headings in bold for exports (PDF/HTML).
set export_bold_scene_headings
"#;



#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub enum MirrorOption {
    
    Off,

    
    Always,

    
    #[default]
    ExportOnly,
}






#[derive(Parser, Debug, Default, Clone)]
#[command(name = "fount", author, version, about, long_about = None)]
pub struct Cli {
    
    #[arg(num_args = 0..)]
    pub files: Vec<PathBuf>,

    
    #[arg(long, value_name = "FILE")]
    pub config: Option<PathBuf>,

    
    #[arg(long)]
    pub hide_scene_numbers: bool,

    
    #[arg(long)]
    pub hide_page_numbers: bool,

    
    #[arg(long)]
    pub show_markup: bool,

    
    #[arg(long)]
    pub no_autocomplete: bool,

    
    #[arg(long)]
    pub no_auto_contd: bool,

    
    #[arg(long)]
    pub no_auto_paragraph_breaks: bool,

    
    #[arg(long)]
    pub auto_title_page: bool,

    
    #[arg(long)]
    pub typewriter_mode: bool,

    
    #[arg(long)]
    pub strict_typewriter_mode: bool,

    
    #[arg(long)]
    pub focus_mode: bool,

    
    #[arg(long)]
    pub no_break_actions: bool,

    
    #[arg(long)]
    pub goto_end: bool,

    
    #[arg(long, value_name = "MODE", num_args = 0..=1, default_missing_value = "always")]
    pub mirror_scene_numbers: Option<String>,

    
    #[arg(long)]
    pub contd_extension: Option<String>,

    
    #[arg(long)]
    pub heading_style: Option<String>,

    
    #[arg(long)]
    pub heading_spacing: Option<usize>,

    
    #[arg(long)]
    pub shot_style: Option<String>,

    
    #[arg(long)]
    pub no_color: bool,

    
    #[arg(long)]
    pub no_formatting: bool,

    
    #[arg(long)]
    pub force_ascii: bool,

    
    #[arg(long)]
    pub force_ansi: bool,

    #[arg(long)]
    pub no_nerd_fonts: bool,

    
    #[arg(long, value_name = "FILE", num_args = 0..=1, default_missing_value = "-")]
    pub export: Option<PathBuf>,

    
    #[arg(long, default_value = "plain", value_name = "FORMAT")]
    pub format: String,

    /// Paper size for PDF export (a4, letter)
    #[arg(long, value_name = "SIZE")]
    pub paper_size: Option<String>,

    /// Force scene numbers in PDF export
    #[arg(long)]
    pub force_scene_numbers: bool,

    /// Export scene headings in bold
    #[arg(long)]
    pub export_bold_scene_headings: bool,

    /// Include title page in exports
    #[arg(long)]
    pub include_title_page: bool,
}






#[derive(Clone, Debug)]
pub struct Config {
    
    pub show_scene_numbers: bool,

    
    
    pub show_page_numbers: bool,

    
    
    pub hide_markup: bool,

    
    pub autocomplete: bool,

    
    
    pub auto_contd: bool,

    
    
    pub auto_paragraph_breaks: bool,

    
    pub auto_title_page: bool,

    
    pub typewriter_mode: bool,

    
    
    pub strict_typewriter_mode: bool,

    
    pub focus_mode: bool,

    
    
    
    
    pub break_actions: bool,

    
    pub goto_end: bool,

    
    
    pub no_color: bool,

    
    
    pub no_formatting: bool,

    
    
    pub force_ascii: bool,

    
    
    pub force_ansi: bool,

    
    pub mirror_scene_numbers: MirrorOption,

    
    
    pub contd_extension: String,

    
    
    pub heading_style: String,

    
    
    pub heading_spacing: usize,

    
    
    pub shot_style: String,

    
    pub auto_save: bool,

    
    pub auto_save_interval: u64,

    /// PDF paper size
    pub paper_size: String,

    /// Force scene numbers in PDF export
    pub force_scene_numbers: bool,

    /// Export scene headings in bold
    pub export_bold_scene_headings: bool,

    /// Selected export format
    pub export_format: String,

    /// Selected report format
    pub report_format: String,

    /// Include title page in PDF/Fountain exports
    pub include_title_page: bool,

    /// When ON, scene numbers in text are frozen — no automatic re-indexing.
    pub production_lock: bool,

    /// Selected theme name
    pub theme: String,

    /// Whether to use Nerd Font icons
    pub use_nerd_fonts: bool,
}

impl Default for Config {
    fn default() -> Self {
        Self {
            show_scene_numbers: true,
            show_page_numbers: true,
            hide_markup: true,

            autocomplete: true,
            auto_contd: true,
            auto_paragraph_breaks: true,
            auto_title_page: false,
            typewriter_mode: true,
            strict_typewriter_mode: true,
            focus_mode: false,
            break_actions: true,
            goto_end: false,

            contd_extension: "(CONT'D)".to_string(),
            heading_style: "bold".to_string(),
            heading_spacing: 1,
            shot_style: "bold".to_string(),

            auto_save: true,
            auto_save_interval: 30,

            no_color: false,
            no_formatting: false,
            force_ascii: false,
            force_ansi: false,

            mirror_scene_numbers: MirrorOption::ExportOnly,

            paper_size: "a4".to_string(),
            force_scene_numbers: false,
            export_bold_scene_headings: true,
            export_format: "pdf".to_string(),
            report_format: "csv_scene".to_string(),
            include_title_page: true,
            production_lock: false,
            theme: "Adaptive".to_string(),
            use_nerd_fonts: true,
        }
    }
}

impl Config {
    
    
    
    
    
    
    pub fn parse_config_str(&mut self, content: &str) {
        for line in content.lines() {
            let line = line.trim();
            if line.is_empty() || line.starts_with('#') {
                continue;
            }
            let parts: Vec<&str> = line.split_whitespace().collect();
            if parts.len() >= 2 {
                let cmd = parts[0];
                let key = parts[1];
                let val = if parts.len() > 2 {
                    parts[2..].join(" ").trim_matches('"').to_string()
                } else {
                    String::new()
                };

                if cmd == "set" {
                    match key {
                        "show_scene_numbers" => self.show_scene_numbers = true,
                        "show_page_numbers" => self.show_page_numbers = true,
                        "hide_markup" => self.hide_markup = true,
                        "autocomplete" => self.autocomplete = true,
                        "auto_contd" => self.auto_contd = true,
                        "auto_paragraph_breaks" => self.auto_paragraph_breaks = true,
                        "auto_title_page" => self.auto_title_page = true,
                        "typewriter_mode" => self.typewriter_mode = true,
                        "strict_typewriter_mode" => self.strict_typewriter_mode = true,
                        "focus_mode" => self.focus_mode = true,
                        "break_actions" => self.break_actions = true,
                        "goto_end" => self.goto_end = true,
                        "mirror_scene_numbers" => {
                            self.mirror_scene_numbers = match val.as_str() {
                                "export" => MirrorOption::ExportOnly,
                                "off" | "false" => MirrorOption::Off,
                                _ => MirrorOption::Always,
                            };
                        }
                        "contd_extension" => self.contd_extension = val,
                        "heading_style" => self.heading_style = val,
                        "heading_spacing" => {
                            if let Ok(v) = val.parse() {
                                self.heading_spacing = v
                            }
                        }
                        "shot_style" => self.shot_style = val,
                        "auto_save" => self.auto_save = true,
                        "auto_save_interval" => {
                            if let Ok(v) = val.parse() {
                                self.auto_save_interval = v
                            }
                        }
                        "no_color" => self.no_color = true,
                        "no_formatting" => self.no_formatting = true,
                        "force_ascii" => self.force_ascii = true,
                        "force_ansi" => self.force_ansi = true,
                        "paper_size" => self.paper_size = val,
                        "export_bold_scene_headings" => self.export_bold_scene_headings = true,
                        "export_format" => self.export_format = val,
                        "report_format" => self.report_format = val,
                        "include_title_page" => self.include_title_page = true,
                        "theme" => self.theme = val,
                        "use_nerd_fonts" => self.use_nerd_fonts = true,
                        _ => {}
                    }
                } else if cmd == "unset" {
                    match key {
                        "show_scene_numbers" => self.show_scene_numbers = false,
                        "show_page_numbers" => self.show_page_numbers = false,
                        "hide_markup" => self.hide_markup = false,
                        "autocomplete" => self.autocomplete = false,
                        "auto_contd" => self.auto_contd = false,
                        "auto_paragraph_breaks" => self.auto_paragraph_breaks = false,
                        "auto_title_page" => self.auto_title_page = false,
                        "typewriter_mode" => self.typewriter_mode = false,
                        "strict_typewriter_mode" => self.strict_typewriter_mode = false,
                        "focus_mode" => self.focus_mode = false,
                        "break_actions" => self.break_actions = false,
                        "goto_end" => self.goto_end = false,
                        "mirror_scene_numbers" => self.mirror_scene_numbers = MirrorOption::Off,
                        "auto_save" => self.auto_save = false,
                        "no_color" => self.no_color = false,
                        "no_formatting" => self.no_formatting = false,
                        "force_ascii" => self.force_ascii = false,
                        "force_ansi" => self.force_ansi = false,
                        "use_nerd_fonts" => self.use_nerd_fonts = false,
                        "force_scene_numbers" => self.force_scene_numbers = false,
                        "export_bold_scene_headings" => self.export_bold_scene_headings = false,
                        "include_title_page" => self.include_title_page = false,
                        _ => {}
                    }
                }
            }
        }
    }

    
    
    
    
    
    
    
    
    
    
    
    
    pub fn config_path() -> Option<PathBuf> {
        #[cfg(windows)]
        {
            directories::ProjectDirs::from("", "", "Fount")
                .map(|proj_dirs| proj_dirs.config_dir().join("fount.conf"))
        }
        #[cfg(not(windows))]
        {
            let config_dir = std::env::var_os("XDG_CONFIG_HOME")
                .map(std::path::PathBuf::from)
                .unwrap_or_else(|| {
                    directories::BaseDirs::new()
                        .map(|base| base.home_dir().join(".config"))
                        .unwrap_or_default()
                });

            Some(config_dir.join("fount").join("fount.conf"))
        }
    }

    pub fn save_setting(key: &str, value: bool) -> std::io::Result<()> {
        let path = Self::config_path().ok_or_else(|| {
            std::io::Error::new(std::io::ErrorKind::NotFound, "Config path not found")
        })?;
        if !path.exists() {
            return Ok(());
        }

        let content = std::fs::read_to_string(&path)?;
        let mut lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
        let mut found = false;

        let search_prefix_set = format!("set {}", key);
        let search_prefix_unset = format!("unset {}", key);

        let new_line = if value {
            format!("set {}", key)
        } else {
            format!("unset {}", key)
        };

        for line in &mut lines {
            let trimmed = line.trim();
            // Match exact `set key` or `unset key` or starting with `set key ` (e.g. if it had value like `set paper_size "a4"`)
            if trimmed == search_prefix_set
                || trimmed == search_prefix_unset
                || trimmed.starts_with(&format!("{} ", search_prefix_set))
            {
                // We keep indentation? Not super necessary, usually it's none
                *line = new_line.clone();
                found = true;
                break;
            }
        }

        if !found {
            lines.push(new_line);
        }

        std::fs::write(&path, lines.join("\n"))?;
        Ok(())
    }

    pub fn save_string_setting(key: &str, value: &str) -> std::io::Result<()> {
        let path = Self::config_path().ok_or_else(|| {
            std::io::Error::new(std::io::ErrorKind::NotFound, "Config path not found")
        })?;
        if !path.exists() {
            return Ok(());
        }

        let content = std::fs::read_to_string(&path)?;
        let mut lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
        let mut found = false;

        let search_prefix = format!("set {}", key);
        let new_line = format!("set {} \"{}\"", key, value);

        for line in &mut lines {
            let trimmed = line.trim();
            if trimmed.starts_with(&search_prefix) {
                *line = new_line.clone();
                found = true;
                break;
            }
        }

        if !found {
            lines.push(new_line);
        }

        std::fs::write(&path, lines.join("\n"))?;
        Ok(())
    }

    pub fn load(cli: &Cli) -> Self {
        let mut config = Self::default();

        let is_custom_path = cli.config.is_some();
        let config_path = cli.config.clone().or_else(Self::config_path);

        if let Some(path) = config_path {
            if !is_custom_path && !path.exists() {
                if let Some(parent) = path.parent() {
                    let _ = fs::create_dir_all(parent);
                }
                let _ = fs::write(&path, DEFAULT_CONFIG);
            }

            match fs::read_to_string(&path) {
                Ok(content) => config.parse_config_str(&content),
                Err(e) if is_custom_path => {
                    eprintln!(
                        "Warning: Failed to load custom config file at '{}': {}",
                        path.display(),
                        e
                    );
                }
                _ => {}
            }
        }

        config.show_scene_numbers &= !cli.hide_scene_numbers;
        config.show_page_numbers &= !cli.hide_page_numbers;
        config.hide_markup &= !cli.show_markup;
        config.autocomplete &= !cli.no_autocomplete;
        config.auto_contd &= !cli.no_auto_contd;
        config.auto_paragraph_breaks &= !cli.no_auto_paragraph_breaks;
        config.break_actions &= !cli.no_break_actions;

        config.auto_title_page |= cli.auto_title_page;
        config.typewriter_mode |= cli.typewriter_mode;
        config.strict_typewriter_mode |= cli.strict_typewriter_mode;
        config.focus_mode |= cli.focus_mode;
        config.no_color |= cli.no_color;
        config.no_formatting |= cli.no_formatting;
        config.force_ascii |= cli.force_ascii;
        config.force_ansi |= cli.force_ansi;
        config.goto_end |= cli.goto_end;
        config.force_scene_numbers |= cli.force_scene_numbers;
        config.export_bold_scene_headings |= cli.export_bold_scene_headings;
        config.include_title_page |= cli.include_title_page;
        config.use_nerd_fonts &= !cli.no_nerd_fonts;

        if config.export_format.is_empty() {
            config.export_format = "pdf".to_string();
        }

        if let Some(ref size) = cli.paper_size {
            config.paper_size = size.clone();
        }

        if let Some(ref mode) = cli.mirror_scene_numbers {
            config.mirror_scene_numbers = match mode.as_str() {
                "export" => MirrorOption::ExportOnly,
                "off" | "false" => MirrorOption::Off,
                _ => MirrorOption::Always,
            };
        }

        if let Some(ref ext) = cli.contd_extension {
            config.contd_extension = ext.clone();
        }
        if let Some(ref style) = cli.heading_style {
            config.heading_style = style.clone();
        }
        if let Some(spacing) = cli.heading_spacing {
            config.heading_spacing = spacing;
        }
        if let Some(ref style) = cli.shot_style {
            config.shot_style = style.clone();
        }

        let supports_unicode = supports_unicode::on(supports_unicode::Stream::Stdout);
        let supports_color = supports_color::on(supports_color::Stream::Stdout).is_some();

        config.force_ascii |= !supports_unicode;

        if config.force_ansi {
            config.no_color = false;
        } else if !supports_color {
            config.no_color = true;
        }

        config
    }
}



#[cfg(test)]
mod config_tests {
    use super::*;

    #[test]
    fn test_config_default_values() {
        let config = Config::default();

        assert!(config.show_scene_numbers);
        assert!(config.show_page_numbers);
        assert!(config.hide_markup);
        assert!(config.typewriter_mode);
        assert!(config.strict_typewriter_mode);
        assert!(!config.focus_mode);
        assert!(config.autocomplete);
        assert!(config.auto_contd);
        assert!(config.auto_paragraph_breaks);
        assert!(!config.auto_title_page);
        assert!(config.break_actions);
        assert!(!config.goto_end);
        assert_eq!(config.contd_extension, "(CONT'D)");
        assert_eq!(config.heading_style, "bold");
        assert_eq!(config.heading_spacing, 1);
        assert_eq!(config.shot_style, "bold");
        assert!(!config.no_color);
        assert!(!config.no_formatting);
        assert!(!config.force_ascii);
        assert!(!config.force_ansi);
    }

    #[test]
    fn test_config_parsing_appearance_flags() {
        let mut config = Config::default();

        let mock_file_content = "
            set no_color
            set no_formatting
            set force_ascii
            set force_ansi
        ";

        config.parse_config_str(mock_file_content);

        assert!(config.no_color, "no_color should be set by parsing");
        assert!(
            config.no_formatting,
            "no_formatting should be set by parsing"
        );
        assert!(config.force_ascii, "force_ascii should be set by parsing");
        assert!(config.force_ansi, "force_ansi should be set by parsing");
    }

    #[test]
    fn test_config_parsing_behavior_flags() {
        let mut config = Config::default();

        let mock_file_content = "
            set strict_typewriter_mode
            set goto_end
            unset break_actions
        ";

        config.parse_config_str(mock_file_content);

        assert!(
            config.strict_typewriter_mode,
            "strict_typewriter_mode should be set by parsing"
        );
        assert!(config.goto_end, "goto_end should be set by parsing");
        assert!(
            !config.break_actions,
            "break_actions should be unset by parsing"
        );
    }

    #[test]
    fn test_cli_overrides_for_appearance() {
        let mut cli = Cli::default();
        cli.force_ascii = true;
        cli.no_color = true;
        cli.no_formatting = true;

        let config = Config::load(&cli);
        assert!(config.no_color);
        assert!(config.no_formatting);
        assert!(config.force_ascii);
        assert!(!config.force_ansi);
    }

    #[test]
    fn test_cli_overrides_for_behavior_flags() {
        let mut cli = Cli::default();
        cli.strict_typewriter_mode = true;
        cli.goto_end = true;
        cli.no_break_actions = true;

        let config = Config::load(&cli);

        assert!(config.strict_typewriter_mode);
        assert!(config.goto_end);
        assert!(
            !config.break_actions,
            "no_break_actions CLI flag should unset break_actions"
        );
    }

    #[test]
    fn test_force_ansi_overrides_no_color() {
        let mut cli = Cli::default();
        cli.no_color = true;
        cli.force_ansi = true;

        let config = Config::load(&cli);
        assert!(
            !config.no_color,
            "force_ansi should override no_color to false"
        );
        assert!(config.force_ansi);
    }

    #[test]
    fn test_config_load_cli_overrides_values() {
        let mut cli = Cli::default();
        cli.contd_extension = Some(" (ПРОД.)".to_string());
        cli.heading_style = Some("underline".to_string());
        cli.heading_spacing = Some(3);
        cli.shot_style = Some("italic".to_string());

        let config = Config::load(&cli);
        assert_eq!(config.contd_extension, " (ПРОД.)");
        assert_eq!(config.heading_style, "underline");
        assert_eq!(config.heading_spacing, 3);
        assert_eq!(config.shot_style, "italic");
    }

    #[test]
    fn test_custom_config_file_error() {
        let mut cli = Cli::default();
        cli.config = Some(std::path::PathBuf::from(
            "/this/path/doesnt/exist/neither/does/the/meaning/of/life.conf",
        ));

        let config = Config::load(&cli);
        assert_eq!(config.heading_spacing, 1);
        assert!(config.strict_typewriter_mode);
    }
}
