//! This module defines the `Code2PromptConfig` struct and its Builder for configuring the behavior
//! of code2prompt in a stateless manner. It includes all parameters needed for file traversal,
//! code filtering, token counting, and more.

use crate::template::OutputFormat;
use crate::tokenizer::TokenizerType;
use crate::{sort::FileSortMethod, tokenizer::TokenFormat};
use derive_builder::Builder;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;

/// Configuration object defining preferences and filters for code prompt generation.
/// 
/// This stateless object can be cloned freely and shared across multiple sessions.
/// Use `Code2PromptConfigBuilder` for construction with validation.
/// 
/// # Example
/// ```
/// use code2prompt_core::configuration::Code2PromptConfig;
/// 
/// let config = Code2PromptConfig::builder()
///     .hidden(true)
///     .build()
///     .unwrap();
/// ```
///
/// 
/// # Errors
/// `build()` returns `ConfigError` if validation fails.
#[derive(Debug, Clone, Default, Builder)]
#[builder(setter(into), default)]
pub struct Code2PromptConfig {
    /// Path to the root directory of the codebase.
    pub path: PathBuf,

    /// List of glob-like patterns to include.
    pub include_patterns: Vec<String>,

    /// List of glob-like patterns to exclude.
    pub exclude_patterns: Vec<String>,

    /// If true, code lines will be numbered in the output.
    pub line_numbers: bool,

    /// If true, paths in the output will be absolute instead of relative.
    pub absolute_path: bool,

    /// If true, code2prompt will generate a full directory tree, ignoring include/exclude rules.
    pub full_directory_tree: bool,

    /// If true, code blocks will not be wrapped in Markdown fences (```).
    pub no_codeblock: bool,

    /// If true, symbolic links will be followed during traversal.
    pub follow_symlinks: bool,

    /// If true, extract an entity-level code map (functions, classes, ...) for
    /// each file via sem-core, exposed to templates as `FileEntry.entities` and a
    /// top-level `code_map`. Requires the `entity-map` build feature; without it
    /// this flag has no effect.
    ///
    /// Default: `false`
    pub entity_map: bool,

    /// Include hidden files and directories in processing.
    /// 
    /// Default: `false`
    pub hidden: bool,

    /// If true, .gitignore rules will be ignored.
    pub no_ignore: bool,

    /// Defines the sorting method for files.
    pub sort_method: Option<FileSortMethod>,

    /// Determines the output format of the final prompt.
    pub output_format: OutputFormat,

    /// Set custom template content for prompt generation.
    /// 
    /// Overrides default template. Template must be valid Handlebars format.
    /// 
    /// # Errors
    /// Returns error during `build()` if template syntax is invalid.
    pub custom_template: Option<String>,

    /// The tokenizer encoding to use for counting tokens.
    pub encoding: TokenizerType,

    /// The counting format to use for token counting.
    pub token_format: TokenFormat,

    /// If true, the git diff between HEAD and index will be included.
    pub diff_enabled: bool,

    /// If set, contains two branch names for which code2prompt will generate a git diff.
    pub diff_branches: Option<(String, String)>,

    /// If set, contains two branch names for which code2prompt will retrieve the git log.
    pub log_branches: Option<(String, String)>,

    /// The name of the template used.
    pub template_name: String,

    /// The template string itself.
    pub template_str: String,

    /// Extra template data
    pub user_variables: HashMap<String, String>,

    /// If true, detailed token map breakdown will be displayed in output.
    ///
    /// Note: Token counting always happens internally for performance optimization
    /// (parallelized during file I/O). This flag only controls whether the breakdown
    /// is shown to users in the final output.
    pub token_map_enabled: bool,

    /// If true, starts with all files deselected.
    pub deselected: bool,
}

impl Code2PromptConfig {
    pub fn builder() -> Code2PromptConfigBuilder {
        Code2PromptConfigBuilder::default()
    }
}

/// Output destination for code2prompt
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
#[serde(rename_all = "lowercase")]
pub enum OutputDestination {
    #[default]
    Stdout,
    Clipboard,
    File,
}

/// TOML configuration structure that can be serialized/deserialized
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(default)]
pub struct TomlConfig {
    /// Default output behavior: "stdout", "clipboard", or "file"
    pub default_output: OutputDestination,

    /// Path to the codebase directory
    pub path: Option<String>,

    /// Patterns to include
    pub include_patterns: Vec<String>,

    /// Patterns to exclude
    pub exclude_patterns: Vec<String>,

    /// Display options
    pub line_numbers: bool,
    pub absolute_path: bool,
    pub full_directory_tree: bool,

    /// Output format
    pub output_format: Option<OutputFormat>,

    /// Sort method
    pub sort_method: Option<FileSortMethod>,

    /// Tokenizer settings
    pub encoding: Option<TokenizerType>,
    pub token_format: Option<TokenFormat>,

    /// Git settings
    pub diff_enabled: bool,
    pub diff_branches: Option<Vec<String>>,
    pub log_branches: Option<Vec<String>>,

    /// Template settings
    pub template_name: Option<String>,
    pub template_str: Option<String>,

    /// User variables
    pub user_variables: HashMap<String, String>,

    /// Token map
    pub token_map_enabled: bool,

    /// Initial selection state
    pub deselected: bool,
}

impl TomlConfig {
    /// Load TOML configuration from a string
    pub fn from_toml_str(content: &str) -> Result<Self, toml::de::Error> {
        toml::from_str(content)
    }

    /// Convert TOML configuration to string
    pub fn to_string(&self) -> Result<String, toml::ser::Error> {
        toml::to_string_pretty(self)
    }

    /// Convert TomlConfig to Code2PromptConfig
    pub fn to_code2prompt_config(&self) -> Code2PromptConfig {
        let mut builder = Code2PromptConfig::builder();

        if let Some(path) = &self.path {
            builder.path(PathBuf::from(path));
        }

        builder
            .include_patterns(self.include_patterns.clone())
            .exclude_patterns(self.exclude_patterns.clone())
            .line_numbers(self.line_numbers)
            .absolute_path(self.absolute_path)
            .full_directory_tree(self.full_directory_tree);

        builder.output_format(self.output_format.unwrap_or_default());

        builder.sort_method(self.sort_method);

        builder.encoding(self.encoding.unwrap_or_default());

        builder.token_format(self.token_format.unwrap_or_default());

        builder.diff_enabled(self.diff_enabled);

        if let Some(diff_branches) = &self.diff_branches
            && diff_branches.len() == 2
        {
            builder.diff_branches(Some((diff_branches[0].clone(), diff_branches[1].clone())));
        }

        if let Some(log_branches) = &self.log_branches
            && log_branches.len() == 2
        {
            builder.log_branches(Some((log_branches[0].clone(), log_branches[1].clone())));
        }

        if let Some(template_name) = &self.template_name {
            builder.template_name(template_name.clone());
        }

        if let Some(template_str) = &self.template_str {
            builder.template_str(template_str.clone());
        }

        builder
            .user_variables(self.user_variables.clone())
            .token_map_enabled(self.token_map_enabled)
            .deselected(self.deselected);

        builder.build().unwrap_or_default()
    }
}

/// Export a Code2PromptConfig to TOML format
pub fn export_config_to_toml(config: &Code2PromptConfig) -> Result<String, toml::ser::Error> {
    let toml_config = TomlConfig {
        default_output: OutputDestination::Stdout, // Default for new behavior
        path: Some(config.path.to_string_lossy().to_string()),
        include_patterns: config.include_patterns.clone(),
        exclude_patterns: config.exclude_patterns.clone(),
        line_numbers: config.line_numbers,
        absolute_path: config.absolute_path,
        full_directory_tree: config.full_directory_tree,
        output_format: Some(config.output_format),
        sort_method: config.sort_method,
        encoding: Some(config.encoding),
        token_format: Some(config.token_format),
        diff_enabled: config.diff_enabled,
        diff_branches: config
            .diff_branches
            .as_ref()
            .map(|(a, b)| vec![a.clone(), b.clone()]),
        log_branches: config
            .log_branches
            .as_ref()
            .map(|(a, b)| vec![a.clone(), b.clone()]),
        template_name: if config.template_name.is_empty() {
            None
        } else {
            Some(config.template_name.clone())
        },
        template_str: if config.template_str.is_empty() {
            None
        } else {
            Some(config.template_str.clone())
        },
        user_variables: config.user_variables.clone(),
        token_map_enabled: config.token_map_enabled,
        deselected: config.deselected,
    };

    toml_config.to_string()
}
