//! Pattern parsing and application for renaming files.
//! 
//! This module handles parsing rename patterns with placeholders (e.g., %N, %E)
//! and modifiers (e.g., %L, %U, %R) and applying them to file names.
//! All file system operations are async and non-blocking.

use std::path::Path;
use tokio::fs;
use chrono::{Local, DateTime};
use regex::Regex;

/// Result of applying a rename pattern to a file.
pub struct PatternResult {
    /// The generated filename (without path)
    pub name: String,
    /// Warnings about unknown tokens or other issues
    pub warnings: Vec<String>,
}

/// Applies a rename pattern to a file path asynchronously.
/// 
/// This function parses the pattern, extracts file information, and generates
/// the new filename according to the pattern rules. All file system operations
/// are non-blocking.
/// 
/// # Arguments
/// 
/// * `file_path` - Path to the file being renamed
/// * `pattern` - Rename pattern (e.g., "%N_v2.%E", "%L%N.%E")
/// * `counter` - Counter value for %C placeholder
/// 
/// # Returns
/// 
/// * `Ok(PatternResult)` - Generated filename and any warnings
/// * `Err(Box<dyn Error>)` - If pattern parsing fails
/// 
/// # Examples
/// 
/// ```
/// # tokio_test::block_on(async {
/// use freneng::pattern::apply_rename_pattern;
/// use std::path::PathBuf;
/// 
/// let file = PathBuf::from("MyFile.txt");
/// let result = apply_rename_pattern(&file, "%L%N.%E", 1).await.unwrap();
/// assert_eq!(result.name, "myfile.txt");
/// # })
/// ```
pub async fn apply_rename_pattern(
    file_path: &Path,
    pattern: &str,
    counter: usize,
) -> Result<PatternResult, Box<dyn std::error::Error>> {
    let file_name = file_path
        .file_name()
        .and_then(|n| n.to_str())
        .ok_or("Invalid file name")?;

    let (name, ext) = if let Some(dot_pos) = file_name.rfind('.') {
        let (n, e) = file_name.split_at(dot_pos);
        (n, &e[1..])
    } else {
        (file_name, "")
    };

    let mut result = String::new();
    let mut warnings = Vec::new();
    let chars: Vec<char> = pattern.chars().collect();
    let mut i = 0;

    let _valid_placeholders = "CNEFPDH";
    let valid_modifiers = "LUTRMX";

    while i < chars.len() {
        if chars[i] == '%' && i + 1 < chars.len() {
            let next_char = chars[i + 1].to_ascii_uppercase();
            
            // Check for multi-char tokens like %FD (File Date) FIRST
            if i + 2 < chars.len() {
                let token = format!("{}{}", next_char, chars[i + 2].to_ascii_uppercase());
                if token == "FD" || token == "FH" {
                    if let Ok(metadata) = fs::metadata(file_path).await {
                        if let Ok(mtime) = metadata.modified() {
                            let dt: DateTime<Local> = mtime.into();
                            if token == "FD" {
                                result.push_str(&dt.format("%Y-%m-%d").to_string());
                            } else {
                                result.push_str(&dt.format("%H-%M-%S").to_string());
                            }
                            i += 3;
                            continue;
                        }
                    }
                }
            }

            match next_char {
                'C' => {
                    let mut j = i + 2;
                    let mut digits = String::new();
                    while j < chars.len() && chars[j].is_ascii_digit() {
                        digits.push(chars[j]);
                        j += 1;
                    }
                    if !digits.is_empty() {
                        let width: usize = digits.parse().unwrap_or(1);
                        result.push_str(&format!("{:0width$}", counter, width = width));
                        i = j;
                        continue;
                    } else {
                        result.push_str(&counter.to_string());
                        i += 2;
                        continue;
                    }
                }
                'N' => {
                    let j = i + 2;
                    let (range_opt, next_j) = parse_range(&chars, j, name.len());
                    if let Some((start, end)) = range_opt {
                        if start < end && start < name.len() {
                            result.push_str(&name[start..end]);
                        }
                        i = next_j;
                        continue;
                    }
                    result.push_str(name);
                    i += 2;
                    continue;
                }
                'E' => {
                    let j = i + 2;
                    let (range_opt, next_j) = parse_range(&chars, j, ext.len());
                    if let Some((start, end)) = range_opt {
                        if start < end && start < ext.len() {
                            result.push_str(&ext[start..end]);
                        }
                        i = next_j;
                        continue;
                    }
                    result.push_str(ext);
                    i += 2;
                    continue;
                }
                'F' => {
                    let j = i + 2;
                    result.push_str(file_name);
                    i = j;
                    continue;
                }
                'P' => {
                    let j = i + 2;
                    let parent_name = if let Some(parent) = file_path.parent().and_then(|p| p.file_name()).and_then(|n| n.to_str()) {
                        parent.to_string()
                    } else if let Ok(abs) = fs::canonicalize(file_path).await {
                        abs.parent().and_then(|p| p.file_name()).and_then(|n| n.to_str()).unwrap_or("").to_string()
                    } else {
                        "".to_string()
                    };

                    let (range_opt, next_j) = parse_range(&chars, j, parent_name.len());
                    if let Some((start, end)) = range_opt {
                        if start < end && start < parent_name.len() {
                            result.push_str(&parent_name[start..end]);
                        }
                        i = next_j;
                    } else {
                        result.push_str(&parent_name);
                        i = j;
                    }
                    continue;
                }
                'D' => {
                    let j = i + 2;
                    result.push_str(&Local::now().format("%Y-%m-%d").to_string());
                    i = j;
                    continue;
                }
                'H' => {
                    let j = i + 2;
                    result.push_str(&Local::now().format("%H-%M-%S").to_string());
                    i = j;
                    continue;
                }
                _ => {
                    // Preserve modifiers in the result for later processing
                    if valid_modifiers.contains(next_char) {
                        result.push(chars[i]); // '%'
                        result.push(chars[i + 1]); // modifier char
                        i += 2;
                        continue;
                    }
                    if next_char.is_alphabetic() {
                        warnings.push(format!("Unknown token: %{}", chars[i+1]));
                    }
                    // For unknown tokens, preserve them as-is
                    result.push(chars[i]);
                    i += 1;
                    continue;
                }
            }
        }
        result.push(chars[i]);
        i += 1;
    }

    result = apply_modifiers_left_to_right(&result)?;
    Ok(PatternResult { name: result, warnings })
}

/// Applies modifiers to the accumulated pattern result.
/// 
/// Modifiers are applied left-to-right and affect the entire accumulated
/// string up to that point. This includes case modifiers (%L, %U, %T),
/// trim (%M), and replacement modifiers (%R, %X).
fn apply_modifiers_left_to_right(pattern: &str) -> Result<String, Box<dyn std::error::Error>> {
    let mut result = String::new();
    let mut i = 0;
    let mut mode = ModifierMode::None;
    let chars: Vec<char> = pattern.chars().collect();
    
    while i < chars.len() {
        if i < chars.len() - 1 && chars[i] == '%' {
            match chars[i + 1] {
                'L' | 'l' => {
                    // Apply lowercase to entire result accumulated so far
                    result = result.to_ascii_lowercase();
                    mode = ModifierMode::Lowercase;
                    i += 2;
                    continue;
                }
                'U' | 'u' => {
                    // Apply uppercase to entire result accumulated so far
                    result = result.to_ascii_uppercase();
                    mode = ModifierMode::Uppercase;
                    i += 2;
                    continue;
                }
                'T' | 't' => {
                    // Apply title case to entire result accumulated so far
                    result = to_title_case(&result);
                    mode = ModifierMode::TitleCase;
                    i += 2;
                    continue;
                }
                'M' | 'm' => {
                    result = result.trim().to_string();
                    i += 2;
                    continue;
                }
                _ => {}
            }
        }
        
        let mut found_modifier = false;
        let delimiters = ['/', '|', ':', ',', '@'];
        for delimiter in delimiters {
            // Replacement: %R/old/new
            if chars.len() >= 3 && i < chars.len() - 3 
                && chars[i] == '%' && (chars[i + 1] == 'R' || chars[i + 1] == 'r') 
                && chars[i + 2] == delimiter {
                if let Some((old, new, end_pos)) = parse_pattern_args(&chars, i + 3, delimiter)? {
                    result = result.replace(&old, &new);
                    i = end_pos;
                    found_modifier = true;
                    break;
                }
            }
            // Regex Replacement: %X/pattern/new
            if chars.len() >= 3 && i < chars.len() - 3 
                && chars[i] == '%' && (chars[i + 1] == 'X' || chars[i + 1] == 'x') 
                && chars[i + 2] == delimiter {
                if let Some((pattern_str, new, end_pos)) = parse_pattern_args(&chars, i + 3, delimiter)? {
                    let re = Regex::new(&pattern_str)?;
                    result = re.replace_all(&result, new.as_str()).to_string();
                    i = end_pos;
                    found_modifier = true;
                    break;
                }
            }
        }
        
        if found_modifier {
            continue;
        }
        
        let ch = chars[i];
        match mode {
            ModifierMode::Lowercase => result.push(ch.to_ascii_lowercase()),
            ModifierMode::Uppercase => result.push(ch.to_ascii_uppercase()),
            ModifierMode::TitleCase => {
                result.push(ch);
                result = to_title_case(&result);
            }
            ModifierMode::None => result.push(ch),
        }
        i += 1;
    }
    
    Ok(result)
}

#[derive(PartialEq)]
enum ModifierMode {
    None,
    Lowercase,
    Uppercase,
    TitleCase,
}

/// Converts a string to title case.
/// 
/// Capitalizes the first letter of each word, where words are separated
/// by whitespace, underscores, dots, or hyphens.
fn to_title_case(s: &str) -> String {
    let mut result = String::with_capacity(s.len());
    let mut capitalize_next = true;

    for c in s.chars() {
        if c.is_whitespace() || c == '_' || c == '.' || c == '-' {
            result.push(c);
            capitalize_next = true;
        } else if capitalize_next {
            result.push(c.to_ascii_uppercase());
            capitalize_next = false;
        } else {
            result.push(c.to_ascii_lowercase());
        }
    }
    result
}

/// Parses a range specification for substring selection.
/// 
/// Supports formats like:
/// - `1-6` - characters from index 1 to 6 (1-based)
/// - `5-` - characters from index 5 to end
/// - `--12` - all characters except last 12
/// - `3--4` - characters from index 3 to (end - 4)
fn parse_range(chars: &[char], start_pos: usize, len: usize) -> (Option<(usize, usize)>, usize) {
    let mut j = start_pos;
    let mut start_str = String::new();
    
    while j < chars.len() && chars[j].is_ascii_digit() {
        start_str.push(chars[j]);
        j += 1;
    }
    
    if j < chars.len() && chars[j] == '-' {
        j += 1;
        let mut is_negative = false;
        if j < chars.len() && chars[j] == '-' {
            is_negative = true;
            j += 1;
        }
        
        let mut end_str = String::new();
        while j < chars.len() && chars[j].is_ascii_digit() {
            end_str.push(chars[j]);
            j += 1;
        }
        
        let start = if start_str.is_empty() {
            0
        } else {
            start_str.parse::<usize>().unwrap_or(1).saturating_sub(1)
        };
        
        let end = if end_str.is_empty() {
            len
        } else {
            let end_val = end_str.parse::<usize>().unwrap_or(len);
            if is_negative {
                len.saturating_sub(end_val)
            } else {
                end_val.min(len)
            }
        };
        
        return (Some((start, end)), j);
    }
    
    (None, start_pos)
}

/// Parses arguments for replacement modifiers (%R and %X).
/// 
/// Extracts two arguments separated by a delimiter, stopping at
/// the next placeholder or end of pattern.
fn parse_pattern_args(
    chars: &[char],
    start: usize,
    delimiter: char,
) -> Result<Option<(String, String, usize)>, Box<dyn std::error::Error>> {
    let mut i = start;
    let mut arg1 = String::new();
    let mut arg2 = String::new();
    let mut found_delimiter = false;
    
    while i < chars.len() {
        if chars[i] == delimiter {
            found_delimiter = true;
            i += 1;
            break;
        }
        arg1.push(chars[i]);
        i += 1;
    }
    
    if !found_delimiter {
        return Ok(None);
    }
    
    while i < chars.len() {
        if chars[i] == '.' {
            break;
        }
        if i < chars.len() - 1 && chars[i] == '%' {
            let next = chars[i + 1].to_ascii_uppercase();
            if "RUL T MN EC FPDH X".contains(next) {
                break;
            }
        }
        arg2.push(chars[i]);
        i += 1;
    }
    
    Ok(Some((arg1, arg2, i)))
}
