//! Application state management

use crate::config::Config;
use crate::rules::{Action, Condition, Rule};
use crate::theme::Theme;
use std::collections::VecDeque;
use std::path::PathBuf;

/// Check if the daemon is currently running by checking the PID file
#[cfg(unix)]
fn is_daemon_running() -> bool {
    let pid_file = dirs::state_dir()
        .unwrap_or_else(|| {
            dirs::home_dir()
                .map(|h| h.join(".local").join("state"))
                .unwrap_or_else(|| PathBuf::from("/tmp"))
        })
        .join("hazelnut")
        .join("hazelnutd.pid");

    if let Ok(pid_str) = std::fs::read_to_string(&pid_file)
        && let Ok(pid) = pid_str.trim().parse::<i32>()
    {
        // Check if process is running using kill -0
        return crate::process_is_running(pid);
    }
    false
}

#[cfg(not(unix))]
fn is_daemon_running() -> bool {
    false
}

/// Input mode for the application
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Mode {
    /// Normal navigation mode
    #[default]
    Normal,
    /// Theme picker dialog
    ThemePicker,
    /// Help dialog
    Help,
    /// Settings dialog
    Settings,
    /// Editing an existing rule
    EditRule,
    /// Adding a new rule
    AddRule,
    /// Editing an existing watch
    EditWatch,
    /// Adding a new watch
    AddWatch,
    /// About dialog
    About,
    /// Update confirmation dialog
    UpdateConfirm,
    /// Update in progress
    Updating,
}

/// Settings menu items
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SettingsItem {
    DaemonControl,
    #[cfg(unix)]
    AutoStartOnBoot,
    ThemeSelection,
    PollingInterval,
    LogRetention,
    StartupBehavior,
    Notifications,
}

impl SettingsItem {
    pub fn all() -> &'static [SettingsItem] {
        &[
            SettingsItem::DaemonControl,
            #[cfg(unix)]
            SettingsItem::AutoStartOnBoot,
            SettingsItem::ThemeSelection,
            SettingsItem::PollingInterval,
            SettingsItem::LogRetention,
            SettingsItem::StartupBehavior,
            SettingsItem::Notifications,
        ]
    }

    pub fn label(&self) -> &'static str {
        match self {
            SettingsItem::DaemonControl => "Start/Stop Daemon",
            #[cfg(unix)]
            SettingsItem::AutoStartOnBoot => "Auto-start on Boot",
            SettingsItem::ThemeSelection => "Theme",
            SettingsItem::PollingInterval => "Polling Interval",
            SettingsItem::LogRetention => "Log Retention",
            SettingsItem::StartupBehavior => "Start Daemon on Launch",
            SettingsItem::Notifications => "Notifications",
        }
    }

    pub fn icon(&self) -> &'static str {
        match self {
            SettingsItem::DaemonControl => "🔌",
            #[cfg(unix)]
            SettingsItem::AutoStartOnBoot => "🖥️",
            SettingsItem::ThemeSelection => "🎨",
            SettingsItem::PollingInterval => "⏱",
            SettingsItem::LogRetention => "📋",
            SettingsItem::StartupBehavior => "🚀",
            SettingsItem::Notifications => "🔔",
        }
    }
}

/// Main application state
#[derive(Debug)]
pub struct AppState {
    /// Current view/tab
    pub view: View,

    /// Current input mode
    pub mode: Mode,

    /// Loaded configuration
    pub config: Config,

    /// Current theme
    pub theme: Theme,

    /// Index of selected rule (if in rules view)
    pub selected_rule: Option<usize>,

    /// Index of selected watch folder (if in watches view)
    pub selected_watch: Option<usize>,

    /// Activity log entries
    pub log_entries: VecDeque<LogEntry>,

    /// Whether the app should quit
    pub should_quit: bool,

    /// Status message to display
    pub status_message: Option<String>,

    /// Scroll offset for log view
    pub log_scroll: usize,

    /// Show help popup (deprecated, use mode instead)
    pub show_help: bool,

    /// Animation frame counter
    pub frame: u64,

    /// Theme picker index
    pub theme_picker_index: usize,

    /// Settings dialog selected item index
    pub settings_index: usize,

    /// Whether daemon is currently running
    pub daemon_running: bool,

    /// Rule editor state
    pub rule_editor: Option<RuleEditorState>,

    /// Watch editor state
    pub watch_editor: Option<WatchEditorState>,

    /// Update available notification
    pub update_available: Option<String>,

    /// Detected package manager
    pub package_manager: crate::PackageManager,

    /// Update status message
    pub update_status: Option<String>,

    /// Original theme saved when entering theme picker
    pub original_theme: Option<Theme>,

    /// Flag to trigger update on next tick (allows UI to redraw first)
    pub pending_update: bool,

    /// Cached file position for daemon log reading
    pub log_file_position: u64,

    /// Flag: watcher needs restart (set when daemon is stopped from settings)
    pub watcher_needs_restart: bool,
}

/// Available views in the TUI
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum View {
    #[default]
    Dashboard,
    Rules,
    Watches,
    Log,
}

/// A log entry for activity tracking
#[derive(Debug, Clone)]
pub struct LogEntry {
    pub timestamp: chrono::DateTime<chrono::Local>,
    pub level: LogLevel,
    pub message: String,
    pub file: Option<PathBuf>,
    pub rule: Option<String>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LogLevel {
    Info,
    Success,
    Warning,
    Error,
}

impl AppState {
    /// Create a new application state from config
    pub fn new(config: Config, theme: Theme) -> Self {
        // Find current theme index
        let theme_picker_index = Theme::all()
            .iter()
            .position(|t| *t == theme.inner())
            .unwrap_or(0);

        let mut state = Self {
            view: View::default(),
            mode: Mode::default(),
            config,
            theme,
            selected_rule: None,
            selected_watch: None,
            log_entries: VecDeque::new(),
            should_quit: false,
            status_message: None,
            log_scroll: 0,
            show_help: false,
            frame: 0,
            theme_picker_index,
            settings_index: 0,
            daemon_running: is_daemon_running(),
            rule_editor: None,
            watch_editor: None,
            update_available: None,
            package_manager: crate::detect_package_manager(),
            update_status: None,
            original_theme: None,
            pending_update: false,
            log_file_position: 0,
            watcher_needs_restart: false,
        };

        // Add welcome log entries
        state.log(LogLevel::Info, "🌰 Hazelnut started");
        state.log(
            LogLevel::Info,
            format!(
                "Loaded {} rules, {} watch folders",
                state.config.rules.len(),
                state.config.watches.len()
            ),
        );

        state
    }

    /// Set update available (called from background task)
    pub fn set_update_available(&mut self, version: String) {
        self.update_available = Some(version.clone());
        self.log(
            LogLevel::Warning,
            format!(
                "Update available: v{} (current: v{})",
                version,
                crate::VERSION
            ),
        );
    }

    /// Get the currently selected rule, if any
    pub fn current_rule(&self) -> Option<&Rule> {
        self.selected_rule.and_then(|i| self.config.rules.get(i))
    }

    /// Get the currently selected rule mutably, if any
    pub fn current_rule_mut(&mut self) -> Option<&mut Rule> {
        self.selected_rule
            .and_then(|i| self.config.rules.get_mut(i))
    }

    /// Add a log entry
    pub fn log(&mut self, level: LogLevel, message: impl Into<String>) {
        self.log_entries.push_back(LogEntry {
            timestamp: chrono::Local::now(),
            level,
            message: message.into(),
            file: None,
            rule: None,
        });

        // Keep log bounded
        if self.log_entries.len() > 1000 {
            self.log_entries.pop_front();
        }
    }

    /// Load daemon log entries from the log file (incremental)
    pub fn load_daemon_logs(&mut self) {
        use std::io::{Read, Seek, SeekFrom};

        // Use ~/.local/state/hazelnut/ on all platforms for consistency
        // This matches the path used by the daemon
        let log_path = dirs::state_dir()
            .unwrap_or_else(|| {
                dirs::home_dir()
                    .map(|h| h.join(".local").join("state"))
                    .unwrap_or_else(|| PathBuf::from("/tmp"))
            })
            .join("hazelnut")
            .join("hazelnutd.log");

        let Ok(mut file) = std::fs::File::open(&log_path) else {
            return;
        };

        let file_len = file.metadata().map(|m| m.len()).unwrap_or(0);

        if file_len < self.log_file_position {
            // File was truncated/rotated — reset
            self.log_entries.clear();
            self.log_file_position = 0;
        }

        if file_len == self.log_file_position {
            return; // No new data
        }

        if file.seek(SeekFrom::Start(self.log_file_position)).is_err() {
            return;
        }

        let mut new_content = String::new();
        if file.read_to_string(&mut new_content).is_err() {
            return;
        }

        self.log_file_position = file_len;

        let max_entries = self.config.general.log_retention;

        for line in new_content.lines() {
            let clean_line = strip_ansi_codes(line);
            if let Some(entry) = parse_daemon_log_line(&clean_line) {
                self.log_entries.push_back(entry);
            }
        }

        // Trim to max
        while self.log_entries.len() > max_entries {
            self.log_entries.pop_front();
        }
    }

    /// Set a temporary status message
    pub fn set_status(&mut self, message: impl Into<String>) {
        self.status_message = Some(message.into());
    }

    /// Clear the status message
    pub fn clear_status(&mut self) {
        self.status_message = None;
    }

    /// Navigate to the next view
    pub fn next_view(&mut self) {
        self.view = match self.view {
            View::Dashboard => View::Rules,
            View::Rules => View::Watches,
            View::Watches => View::Log,
            View::Log => View::Dashboard,
        };
    }

    /// Navigate to the previous view
    pub fn prev_view(&mut self) {
        self.view = match self.view {
            View::Dashboard => View::Log,
            View::Rules => View::Dashboard,
            View::Watches => View::Rules,
            View::Log => View::Watches,
        };
    }

    /// Increment frame counter (for animations) and refresh daemon logs periodically
    pub fn tick(&mut self) {
        self.frame = self.frame.wrapping_add(1);

        // Refresh daemon logs every ~2 seconds (20 frames at 100ms poll)
        if self.frame.is_multiple_of(20) {
            self.load_daemon_logs();
        }
    }
}

/// Fields in the rule editor
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum RuleEditorField {
    #[default]
    Name,
    Enabled,
    // Conditions
    Extension,
    NameGlob,
    NameRegex,
    SizeGreater,
    SizeLess,
    AgeGreater,
    AgeLess,
    IsDirectory,
    IsHidden,
    // Action
    ActionType,
    ActionDestination,
    ActionPattern,
    ActionCommand,
    ActionArgs,
}

impl RuleEditorField {
    /// Get the next field in tab order
    pub fn next(self) -> Self {
        match self {
            Self::Name => Self::Enabled,
            Self::Enabled => Self::Extension,
            Self::Extension => Self::NameGlob,
            Self::NameGlob => Self::NameRegex,
            Self::NameRegex => Self::SizeGreater,
            Self::SizeGreater => Self::SizeLess,
            Self::SizeLess => Self::AgeGreater,
            Self::AgeGreater => Self::AgeLess,
            Self::AgeLess => Self::IsDirectory,
            Self::IsDirectory => Self::IsHidden,
            Self::IsHidden => Self::ActionType,
            Self::ActionType => Self::ActionDestination,
            Self::ActionDestination => Self::ActionPattern,
            Self::ActionPattern => Self::ActionCommand,
            Self::ActionCommand => Self::ActionArgs,
            Self::ActionArgs => Self::Name,
        }
    }

    /// Get the previous field in tab order
    pub fn prev(self) -> Self {
        match self {
            Self::Name => Self::ActionArgs,
            Self::Enabled => Self::Name,
            Self::Extension => Self::Enabled,
            Self::NameGlob => Self::Extension,
            Self::NameRegex => Self::NameGlob,
            Self::SizeGreater => Self::NameRegex,
            Self::SizeLess => Self::SizeGreater,
            Self::AgeGreater => Self::SizeLess,
            Self::AgeLess => Self::AgeGreater,
            Self::IsDirectory => Self::AgeLess,
            Self::IsHidden => Self::IsDirectory,
            Self::ActionType => Self::IsHidden,
            Self::ActionDestination => Self::ActionType,
            Self::ActionPattern => Self::ActionDestination,
            Self::ActionCommand => Self::ActionPattern,
            Self::ActionArgs => Self::ActionCommand,
        }
    }
}

/// Available action types for the editor
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ActionTypeSelection {
    #[default]
    Move,
    Copy,
    Rename,
    Trash,
    Delete,
    Run,
    Archive,
    Nothing,
}

impl ActionTypeSelection {
    pub fn all() -> &'static [Self] {
        &[
            Self::Move,
            Self::Copy,
            Self::Rename,
            Self::Trash,
            Self::Delete,
            Self::Run,
            Self::Archive,
            Self::Nothing,
        ]
    }

    pub fn name(self) -> &'static str {
        match self {
            Self::Move => "Move",
            Self::Copy => "Copy",
            Self::Rename => "Rename",
            Self::Trash => "Trash",
            Self::Delete => "Delete",
            Self::Run => "Run Command",
            Self::Archive => "Archive",
            Self::Nothing => "Nothing",
        }
    }

    pub fn next(self) -> Self {
        match self {
            Self::Move => Self::Copy,
            Self::Copy => Self::Rename,
            Self::Rename => Self::Trash,
            Self::Trash => Self::Delete,
            Self::Delete => Self::Run,
            Self::Run => Self::Archive,
            Self::Archive => Self::Nothing,
            Self::Nothing => Self::Move,
        }
    }

    pub fn prev(self) -> Self {
        match self {
            Self::Move => Self::Nothing,
            Self::Copy => Self::Move,
            Self::Rename => Self::Copy,
            Self::Trash => Self::Rename,
            Self::Delete => Self::Trash,
            Self::Run => Self::Delete,
            Self::Archive => Self::Run,
            Self::Nothing => Self::Archive,
        }
    }
}

/// Fields in the watch editor dialog
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum WatchEditorField {
    #[default]
    Path,
    Recursive,
    Rules,
}

impl WatchEditorField {
    pub fn next(self) -> Self {
        match self {
            Self::Path => Self::Recursive,
            Self::Recursive => Self::Rules,
            Self::Rules => Self::Path,
        }
    }

    pub fn prev(self) -> Self {
        match self {
            Self::Path => Self::Rules,
            Self::Recursive => Self::Path,
            Self::Rules => Self::Recursive,
        }
    }
}

/// State for the watch editor dialog
#[derive(Debug, Clone, Default)]
pub struct WatchEditorState {
    /// Currently focused field
    pub field: WatchEditorField,

    /// Watch index being edited (None if adding new)
    pub editing_index: Option<usize>,

    /// Path to watch
    pub path: String,

    /// Watch recursively
    pub recursive: bool,

    /// Selected rule names (empty = all rules apply)
    pub rules_filter: Vec<String>,

    /// All available rule names (for display in selector)
    pub available_rules: Vec<String>,

    /// Currently highlighted rule in the rules list
    pub rules_cursor: usize,

    /// Cursor position for path field
    pub cursor_path: usize,
}

impl WatchEditorState {
    /// Create a new watch editor for adding
    pub fn new_watch(available_rules: Vec<String>) -> Self {
        Self {
            field: WatchEditorField::Path,
            editing_index: None,
            path: String::new(),
            recursive: false,
            rules_filter: Vec::new(),
            available_rules,
            rules_cursor: 0,
            cursor_path: 0,
        }
    }

    /// Create editor state from an existing watch
    pub fn from_watch(
        index: usize,
        watch: &crate::config::WatchConfig,
        available_rules: Vec<String>,
    ) -> Self {
        let path = watch.path.display().to_string();
        let cursor_path = path.len();
        let rules_cursor = 0;
        Self {
            field: WatchEditorField::Path,
            editing_index: Some(index),
            path,
            recursive: watch.recursive,
            rules_filter: watch.rules.clone(),
            available_rules,
            rules_cursor,
            cursor_path,
        }
    }

    /// Check if a rule is selected
    pub fn is_rule_selected(&self, rule_name: &str) -> bool {
        self.rules_filter.contains(&rule_name.to_string())
    }

    /// Toggle a rule selection
    pub fn toggle_rule(&mut self, rule_name: &str) {
        if let Some(pos) = self.rules_filter.iter().position(|r| r == rule_name) {
            self.rules_filter.remove(pos);
        } else {
            self.rules_filter.push(rule_name.to_string());
        }
    }

    /// Build a WatchConfig from the editor state
    pub fn to_watch(&self) -> crate::config::WatchConfig {
        crate::config::WatchConfig {
            path: std::path::PathBuf::from(&self.path),
            recursive: self.recursive,
            rules: self.rules_filter.clone(),
        }
    }
}

/// State for the rule editor dialog
#[derive(Debug, Clone, Default)]
pub struct RuleEditorState {
    /// Currently focused field
    pub field: RuleEditorField,

    /// Rule index being edited (None if adding new)
    pub editing_index: Option<usize>,

    // Basic fields
    pub name: String,
    pub enabled: bool,
    pub stop_processing: bool,

    // Condition fields
    pub extension: String,
    pub name_glob: String,
    pub name_regex: String,
    pub size_greater: String,
    pub size_less: String,
    pub age_greater: String,
    pub age_less: String,
    pub is_directory: Option<bool>,
    pub is_hidden: Option<bool>,

    // Action fields
    pub action_type: ActionTypeSelection,
    pub action_destination: String,
    pub action_pattern: String,
    pub action_command: String,
    pub action_args: String,
    pub action_overwrite: bool,
    pub action_delete_original: bool,

    // Cursor positions for text fields
    pub cursor_name: usize,
    pub cursor_extension: usize,
    pub cursor_name_glob: usize,
    pub cursor_name_regex: usize,
    pub cursor_size_greater: usize,
    pub cursor_size_less: usize,
    pub cursor_age_greater: usize,
    pub cursor_age_less: usize,
    pub cursor_action_destination: usize,
    pub cursor_action_pattern: usize,
    pub cursor_action_command: usize,
    pub cursor_action_args: usize,
}

impl RuleEditorState {
    /// Create a new empty editor state for adding a rule
    pub fn new_rule() -> Self {
        Self {
            enabled: true,
            ..Default::default()
        }
    }

    /// Create editor state from an existing rule
    pub fn from_rule(index: usize, rule: &Rule) -> Self {
        let (
            action_type,
            action_destination,
            action_pattern,
            action_command,
            action_args,
            action_overwrite,
            action_delete_original,
        ) = match &rule.action {
            Action::Move {
                destination,
                overwrite,
                ..
            } => (
                ActionTypeSelection::Move,
                destination.display().to_string(),
                String::new(),
                String::new(),
                String::new(),
                *overwrite,
                false,
            ),
            Action::Copy {
                destination,
                overwrite,
                ..
            } => (
                ActionTypeSelection::Copy,
                destination.display().to_string(),
                String::new(),
                String::new(),
                String::new(),
                *overwrite,
                false,
            ),
            Action::Rename { pattern } => (
                ActionTypeSelection::Rename,
                String::new(),
                pattern.clone(),
                String::new(),
                String::new(),
                false,
                false,
            ),
            Action::Trash => (
                ActionTypeSelection::Trash,
                String::new(),
                String::new(),
                String::new(),
                String::new(),
                false,
                false,
            ),
            Action::Delete => (
                ActionTypeSelection::Delete,
                String::new(),
                String::new(),
                String::new(),
                String::new(),
                false,
                false,
            ),
            Action::Run { command, args } => (
                ActionTypeSelection::Run,
                String::new(),
                String::new(),
                command.clone(),
                shlex::try_join(args.iter().map(|s| s.as_str())).unwrap_or_else(|_| args.join(" ")),
                false,
                false,
            ),
            Action::Archive {
                destination,
                delete_original,
            } => (
                ActionTypeSelection::Archive,
                destination
                    .as_ref()
                    .map(|p| p.display().to_string())
                    .unwrap_or_default(),
                String::new(),
                String::new(),
                String::new(),
                false,
                *delete_original,
            ),
            Action::Nothing => (
                ActionTypeSelection::Nothing,
                String::new(),
                String::new(),
                String::new(),
                String::new(),
                false,
                false,
            ),
        };

        Self {
            field: RuleEditorField::Name,
            editing_index: Some(index),
            name: rule.name.clone(),
            enabled: rule.enabled,
            stop_processing: rule.stop_processing,
            extension: rule.condition.extension.clone().unwrap_or_default(),
            name_glob: rule.condition.name_matches.clone().unwrap_or_default(),
            name_regex: rule.condition.name_regex.clone().unwrap_or_default(),
            size_greater: rule
                .condition
                .size_greater_than
                .map(|v| v.to_string())
                .unwrap_or_default(),
            size_less: rule
                .condition
                .size_less_than
                .map(|v| v.to_string())
                .unwrap_or_default(),
            age_greater: rule
                .condition
                .age_days_greater_than
                .map(|v| v.to_string())
                .unwrap_or_default(),
            age_less: rule
                .condition
                .age_days_less_than
                .map(|v| v.to_string())
                .unwrap_or_default(),
            is_directory: rule.condition.is_directory,
            is_hidden: rule.condition.is_hidden,
            action_type,
            action_destination: action_destination.clone(),
            action_pattern: action_pattern.clone(),
            action_command: action_command.clone(),
            action_args: action_args.clone(),
            action_overwrite,
            action_delete_original,
            // Set cursor positions to end of each field
            cursor_name: rule.name.len(),
            cursor_extension: rule
                .condition
                .extension
                .as_ref()
                .map(|s| s.len())
                .unwrap_or(0),
            cursor_name_glob: rule
                .condition
                .name_matches
                .as_ref()
                .map(|s| s.len())
                .unwrap_or(0),
            cursor_name_regex: rule
                .condition
                .name_regex
                .as_ref()
                .map(|s| s.len())
                .unwrap_or(0),
            cursor_size_greater: rule
                .condition
                .size_greater_than
                .map(|v| v.to_string().len())
                .unwrap_or(0),
            cursor_size_less: rule
                .condition
                .size_less_than
                .map(|v| v.to_string().len())
                .unwrap_or(0),
            cursor_age_greater: rule
                .condition
                .age_days_greater_than
                .map(|v| v.to_string().len())
                .unwrap_or(0),
            cursor_age_less: rule
                .condition
                .age_days_less_than
                .map(|v| v.to_string().len())
                .unwrap_or(0),
            cursor_action_destination: action_destination.len(),
            cursor_action_pattern: action_pattern.len(),
            cursor_action_command: action_command.len(),
            cursor_action_args: action_args.len(),
        }
    }

    /// Build a Rule from the editor state
    pub fn to_rule(&self) -> Rule {
        let condition = Condition {
            extension: if self.extension.is_empty() {
                None
            } else {
                Some(self.extension.clone())
            },
            extensions: Vec::new(),
            name_matches: if self.name_glob.is_empty() {
                None
            } else {
                Some(self.name_glob.clone())
            },
            name_regex: if self.name_regex.is_empty() {
                None
            } else {
                Some(self.name_regex.clone())
            },
            size_greater_than: self.size_greater.parse().ok(),
            size_less_than: self.size_less.parse().ok(),
            age_days_greater_than: self.age_greater.parse().ok(),
            age_days_less_than: self.age_less.parse().ok(),
            is_directory: self.is_directory,
            is_hidden: self.is_hidden,
        };

        let action = match self.action_type {
            ActionTypeSelection::Move => Action::Move {
                destination: PathBuf::from(&self.action_destination),
                create_destination: true,
                overwrite: self.action_overwrite,
            },
            ActionTypeSelection::Copy => Action::Copy {
                destination: PathBuf::from(&self.action_destination),
                create_destination: true,
                overwrite: self.action_overwrite,
            },
            ActionTypeSelection::Rename => Action::Rename {
                pattern: self.action_pattern.clone(),
            },
            ActionTypeSelection::Trash => Action::Trash,
            ActionTypeSelection::Delete => Action::Delete,
            ActionTypeSelection::Run => Action::Run {
                command: self.action_command.clone(),
                args: shlex::split(&self.action_args).unwrap_or_else(|| {
                    self.action_args
                        .split_whitespace()
                        .map(String::from)
                        .collect()
                }),
            },
            ActionTypeSelection::Archive => Action::Archive {
                destination: if self.action_destination.is_empty() {
                    None
                } else {
                    Some(PathBuf::from(&self.action_destination))
                },
                delete_original: self.action_delete_original,
            },
            ActionTypeSelection::Nothing => Action::Nothing,
        };

        Rule {
            name: self.name.clone(),
            enabled: self.enabled,
            condition,
            action,
            stop_processing: self.stop_processing,
        }
    }
}

/// Strip ANSI escape codes from a string
fn strip_ansi_codes(s: &str) -> String {
    static ANSI_RE: std::sync::LazyLock<regex::Regex> =
        std::sync::LazyLock::new(|| regex::Regex::new(r"\x1b\[[0-9;]*[a-zA-Z]").unwrap());
    ANSI_RE.replace_all(s, "").to_string()
}

/// Parse a daemon log line into a LogEntry
fn parse_daemon_log_line(line: &str) -> Option<LogEntry> {
    // Format: 2026-02-04T20:12:37.235953Z  INFO message
    let line = line.trim();
    if line.is_empty() {
        return None;
    }

    // Find timestamp and level using split_whitespace (handles multiple spaces
    // in tracing output like "2026-02-04T20:12:37Z  INFO message")
    let mut words = line.split_whitespace();
    let timestamp_str = words.next()?;
    let level_str = words.next()?;
    let message: String = words.collect::<Vec<&str>>().join(" ");

    // Parse timestamp
    let timestamp = chrono::DateTime::parse_from_rfc3339(timestamp_str)
        .map(|dt| dt.with_timezone(&chrono::Local))
        .unwrap_or_else(|_| chrono::Local::now());

    // Parse level
    let level = match level_str.to_uppercase().as_str() {
        "INFO" => LogLevel::Info,
        "WARN" | "WARNING" => LogLevel::Warning,
        "ERROR" => LogLevel::Error,
        "DEBUG" | "TRACE" => LogLevel::Info,
        _ => LogLevel::Info,
    };

    Some(LogEntry {
        timestamp,
        level,
        message,
        file: None,
        rule: None,
    })
}
