use crate::field::{FieldRange, get_string_by_field};
use crate::helper::item::strip_ansi;
use crate::item::MatchedItem;
use regex::Regex;
use std::fmt::Write as _;
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::prelude::v1::*;

#[cfg(feature = "cli")]
/// Unescape a delimiter string to handle escape sequences like \x00, \t, \n, etc.
///
/// Supported escape sequences:
/// - `\x00` - `\xff`: hexadecimal byte values
/// - `\t`: tab
/// - `\n`: newline
/// - `\r`: carriage return
/// - `\\`: backslash
///
/// # Examples
///
/// ```ignore
/// use skim::util::unescape_delimiter;
///
/// assert_eq!(unescape_delimiter(r"\x00"), "\0");
/// assert_eq!(unescape_delimiter(r"\t"), "\t");
/// assert_eq!(unescape_delimiter(r"\n"), "\n");
/// assert_eq!(unescape_delimiter(r"\\"), "\\");
/// ```
pub fn unescape_delimiter(s: &str) -> String {
    let mut result = String::new();
    let mut chars = s.chars();

    while let Some(c) = chars.next() {
        if c == '\\' {
            match chars.next() {
                Some('x') => {
                    // Handle \xNN hex escape
                    let hex: String = chars.by_ref().take(2).collect();
                    if hex.len() == 2 {
                        if let Ok(byte) = u8::from_str_radix(&hex, 16) {
                            // For null byte and other non-UTF8 safe bytes, we need to handle carefully
                            // Regex works with strings, so we push the byte as a char
                            result.push(byte as char);
                        } else {
                            // Invalid hex, keep as literal
                            result.push('\\');
                            result.push('x');
                            result.push_str(&hex);
                        }
                    } else {
                        // Not enough hex digits
                        result.push('\\');
                        result.push('x');
                        result.push_str(&hex);
                    }
                }
                Some('t') => result.push('\t'),
                Some('n') => result.push('\n'),
                Some('r') => result.push('\r'),
                Some('\\') | None => result.push('\\'),
                Some(other) => {
                    // Unknown escape, keep both backslash and character
                    result.push('\\');
                    result.push(other);
                }
            }
        } else {
            result.push(c);
        }
    }

    result
}

pub fn read_file_lines(filename: &str) -> std::result::Result<Vec<String>, std::io::Error> {
    let file = File::open(filename)?;
    BufReader::new(file).lines().collect()
}

/// Replace the fields in `pattern` with the items, expanding {...} patterns
///
/// Replaces:
/// - `{}` -> currently selected item
/// - `{1..}` etc -> fields of currently selected item, whose index is `selected`
/// - `{+}` -> all selected items (multi-select)
/// - `{q}` -> current query
/// - `{cq}` -> current command query
#[allow(clippy::too_many_arguments)]
#[allow(clippy::too_many_lines)]
pub fn printf<'a>(
    pattern: &str,
    delimiter: &Regex,
    replstr: &str,
    selected: &(impl Iterator<Item = &'a MatchedItem> + std::clone::Clone),
    current: &Option<MatchedItem>,
    query: &str,
    command_query: &str,
    mut quote_args: bool,
) -> String {
    // Windows uses different shell quoting conventions (double quotes, caret escaping)
    // that are incompatible with the Unix-style single-quote escaping implemented here.
    if cfg!(windows) {
        quote_args = false;
    }
    let escape_arg = |s: &str, quote: bool| {
        let mut res = s.replace('\0', "\\0").clone();
        if quote && quote_args {
            res = format!("'{}'", res.replace('\'', "'\\''"));
        }
        res
    };

    let item_text = current.as_ref().map(|s| strip_ansi(&s.output()).0).unwrap_or_default();
    let escaped_item = escape_arg(&item_text, true);
    let escaped_query = escape_arg(query, true);
    let escaped_cmd_query = escape_arg(command_query, true);

    // Split on replstr first
    let replstr_parts = pattern.split(replstr);
    let mut replaced_parts = Vec::new();

    // Deal with every part to expand inside

    for part in replstr_parts {
        let mut sub = part.split('{');
        let mut replaced = sub.next().unwrap_or_default().to_string();
        for s in sub {
            let mut inside = true;
            let mut content = String::new();
            for c in s.chars() {
                if inside {
                    if c == '}' {
                        match content.as_str() {
                            "" => replaced.push_str("{}"),
                            "q" => replaced.push_str(&escaped_query),
                            "cq" => replaced.push_str(&escaped_cmd_query),
                            "n" if current.as_ref().is_some() => {
                                replaced.push_str(
                                    current
                                        .as_ref()
                                        .map(|i| i.rank.index)
                                        .unwrap_or_default()
                                        .to_string()
                                        .as_str(),
                                );
                            }
                            s if s == "+n" || s.starts_with("+n:") || s == "+" || s.starts_with("+:") => {
                                let is_n = s.starts_with("+n");
                                let accessor = if is_n {
                                    |i: &MatchedItem| i.rank.index.to_string()
                                } else {
                                    |i: &MatchedItem| strip_ansi(&i.output()).0
                                };
                                let mut quote_individually = false;

                                let delim = s.rsplit_once(':').map_or_else(
                                    || {
                                        quote_individually = quote_args;
                                        " "
                                    },
                                    |x| x.1,
                                );

                                let mut expanded = selected
                                    .clone()
                                    .map(|i| escape_arg(&accessor(i), quote_individually))
                                    .reduce(|a: String, b| a.clone() + delim + b.as_str())
                                    .unwrap_or_default();
                                if expanded.is_empty() {
                                    expanded = current
                                        .as_ref()
                                        .map(|i| escape_arg(&accessor(i), quote_args))
                                        .unwrap_or_default();
                                }

                                if quote_args && !quote_individually {
                                    let _ = write!(replaced, "'{expanded}'");
                                } else {
                                    replaced.push_str(&expanded);
                                }
                            }
                            s => {
                                let (is_plus, stripped) = match s.strip_prefix('+') {
                                    Some(x) => (true, x),
                                    None => (false, s),
                                };
                                if is_plus {
                                    let mut quote_individually = false;

                                    let (stripped, delim) = stripped.rsplit_once(':').unwrap_or_else(|| {
                                        quote_individually = quote_args;
                                        (stripped, " ")
                                    });
                                    if let Some(range) = FieldRange::from_str(stripped) {
                                        let expanded = selected
                                            .clone()
                                            .map(|i| {
                                                escape_arg(
                                                    get_string_by_field(delimiter, &strip_ansi(&i.output()).0, &range)
                                                        .unwrap_or_default(),
                                                    quote_individually,
                                                )
                                            })
                                            .reduce(|a: String, b| a.clone() + delim + b.as_str())
                                            .unwrap_or_default();

                                        if quote_args && !quote_individually {
                                            let _ = write!(replaced, "'{expanded}'");
                                        } else {
                                            replaced.push_str(&expanded);
                                        }
                                    } else {
                                        log::warn!("Failed to build multi-item field range from {content}");
                                        let _ = write!(replaced, "{{{s}}}");
                                    }
                                } else if let Some(range) = FieldRange::from_str(stripped) {
                                    let replacement =
                                        get_string_by_field(delimiter, &item_text, &range).unwrap_or_default();
                                    replaced.push_str(&escape_arg(replacement, true));
                                } else {
                                    log::warn!("Failed to build field range from {content}");
                                    let _ = write!(replaced, "{{{s}}}");
                                }
                            }
                        }

                        content.clear();
                        inside = false;
                    } else {
                        content.push(c);
                    }
                } else if c == '{' {
                    inside = true;
                } else {
                    replaced.push(c);
                }
            }
            // }
        }
        replaced_parts.push(replaced);
    }

    // Join back the replstr parts into the res
    replaced_parts
        .into_iter()
        .reduce(|a: String, b| a + &escaped_item + &b)
        .unwrap_or_default()
}

#[cfg(test)]
#[path = "util_tests.rs"]
mod test;
