/// Parses the contents of a collection file into a Collection struct.
use std::{
    collections::HashMap,
    path::{Path, PathBuf},
};

use http::Method;

use pest::Parser;
use pest_derive::Parser;

pub mod context;
mod preprocessor;

use crate::{
    collection::Collection,
    request::{
        expand_templates, Assertion, FragmentInclude, RequestTemplate, ResponseCapture,
        TemplateError, VariableStore,
    },
};

#[derive(Parser)]
#[grammar = "src/parser/grammar.pest"]
struct CollectionParser;

#[derive(Debug, Clone)]
pub struct SyntaxRequestSummary {
    pub index: usize,
    pub description: String,
    pub method: String,
    pub url: String,
}

#[derive(Debug, Clone)]
pub struct SyntaxSummary {
    pub name: String,
    pub description: String,
    pub requests: Vec<SyntaxRequestSummary>,
}

pub fn inspect_collection_syntax(
    input: &str,
    working_dir: PathBuf,
) -> Result<SyntaxSummary, pest::error::Error<Rule>> {
    let preprocessed = preprocessor::preprocess(input, &working_dir).map_err(|err| {
        pest::error::Error::new_from_span(
            pest::error::ErrorVariant::CustomError {
                message: err.to_string(),
            },
            pest::Span::new(input, 0, input.len()).unwrap(),
        )
    })?;

    let mut pairs = CollectionParser::parse(Rule::request_collection, preprocessed.as_str())?;
    let collection = pairs.next().unwrap();

    let mut name = String::new();
    let mut description = String::new();
    let mut requests = Vec::new();
    let mut global_store = VariableStore::new();

    for pair in collection.into_inner() {
        match pair.as_rule() {
            Rule::collection_name => {
                name = pair.as_str().trim().to_string();
            }
            Rule::collection_description => {
                description.push_str(pair.as_str().trim());
            }
            Rule::variable => {
                let span = pair.as_span();
                let mut inner_pairs = pair.into_inner();
                let key = inner_pairs.next().unwrap().as_str().trim().to_string();
                let value = inner_pairs.next().unwrap().as_str().to_string();

                assign_global_variable_for_validation(&mut global_store, key, value.as_str())
                    .map_err(|err| template_error_to_span(err, span.clone()))?;
            }
            Rule::requests => {
                for (index, request_pair) in pair.into_inner().enumerate() {
                    requests.push(inspect_request_syntax(index, request_pair, &global_store)?);
                }
            }
            _ => {}
        }
    }

    Ok(SyntaxSummary {
        name,
        description,
        requests,
    })
}

pub fn parse_collection(
    input: &str,
    working_dir: PathBuf,
) -> Result<Collection, pest::error::Error<Rule>> {
    let preprocessed = preprocessor::preprocess(input, &working_dir).map_err(|e| {
        pest::error::Error::new_from_span(
            pest::error::ErrorVariant::CustomError {
                message: e.to_string(),
            },
            pest::Span::new(input, 0, input.len()).unwrap(),
        )
    })?;

    log::debug!("PREPROCESSED COMPLETE:\n{}", preprocessed);

    let mut pairs = CollectionParser::parse(Rule::request_collection, preprocessed.as_str())?;

    let collection = pairs.next().unwrap();

    let mut name = String::new();
    let mut description = String::new();
    let mut request_templates: Vec<RequestTemplate> = Vec::new();
    let mut global_store = VariableStore::new();
    let mut global_headers: HashMap<String, String> = HashMap::new();
    let mut global_queries: HashMap<String, String> = HashMap::new();
    let mut global_callbacks: Vec<String> = vec![];

    for pair in collection.into_inner() {
        match pair.as_rule() {
            Rule::collection_name => {
                name = pair.as_str().trim().to_string();
            }

            Rule::collection_description => {
                description.push_str(pair.as_str().trim());
            }

            Rule::variable => {
                let span = pair.as_span();
                let mut inner_pairs = pair.into_inner();
                let key = inner_pairs.next().unwrap().as_str().trim().to_string();
                let value = inner_pairs.next().unwrap().as_str().to_string();

                assign_global_variable(&mut global_store, key, value.as_str(), &working_dir)
                    .map_err(|err| template_error_to_span(err, span.clone()))?;
            }

            Rule::header => {
                let span = pair.as_span();
                let mut inner_pairs = pair.into_inner();
                let key = inner_pairs.next().unwrap().as_str().trim().to_string();
                let value = inner_pairs.next().unwrap().as_str().trim().to_string();

                let value = context::try_inject_from_prompt(&value)
                    .map_err(|err| prompt_error_to_span(err, span.clone()))?;
                global_headers.insert(key, value);
            }

            Rule::query => {
                let span = pair.as_span();
                let mut inner_pairs = pair.into_inner();
                let key = inner_pairs.next().unwrap().as_str().trim().to_string();
                let value = inner_pairs.next().unwrap().as_str().trim().to_string();

                let value = context::try_inject_from_prompt(&value)
                    .map_err(|err| prompt_error_to_span(err, span.clone()))?;
                global_queries.insert(key, value);
            }

            Rule::callback => {
                // drop the leading "!" character
                global_callbacks.push(pair.as_str().strip_prefix('!').unwrap().to_string());
            }

            Rule::requests => {
                for request_pair in pair.into_inner() {
                    let template = parse_request(
                        request_pair,
                        &global_store,
                        &global_headers,
                        &global_queries,
                        &global_callbacks,
                        &working_dir,
                    )?;
                    request_templates.push(template);
                }
            }

            _ => {
                unreachable!("unexpected rule: {:?}", pair.as_rule());
            }
        }
    }

    let requests = expand_templates(request_templates)
        .map_err(|err| template_error_to_collection_error(err, preprocessed.as_str()))?;

    Ok(Collection {
        name,
        description,
        requests,
    })
}

fn parse_request(
    pair: pest::iterators::Pair<Rule>,
    collection_store: &VariableStore,
    global_headers: &HashMap<String, String>,
    global_queries: &HashMap<String, String>,
    global_callbacks: &[String],
    working_dir: &PathBuf,
) -> Result<RequestTemplate, pest::error::Error<Rule>> {
    let request_span = pair.as_span();
    let mut store = collection_store.clone();

    let mut description_raw = String::new();
    let mut method: Option<Method> = None;
    let mut url_raw: Option<String> = None;
    let mut header_raw: HashMap<String, String> = HashMap::new();
    let mut query_raw: HashMap<String, String> = HashMap::new();
    let mut form_text_raw: HashMap<String, String> = HashMap::new();
    let mut form_file_raw: HashMap<String, String> = HashMap::new();
    let mut body_raw: Option<String> = None;
    let mut body_content_type_raw: Option<String> = None;
    let mut callback_src_raw: Vec<String> = global_callbacks.to_vec();
    let mut dependencies: Vec<String> = Vec::new();
    let mut response_capture_raw: Vec<(String, pest::Span<'_>)> = Vec::new();
    let mut assertion_raw: Vec<(String, pest::Span<'_>)> = Vec::new();
    let mut fragment_includes_raw: Vec<(String, Option<String>, usize)> = Vec::new();

    for inner in pair.into_inner() {
        match inner.as_rule() {
            Rule::description => {
                description_raw.push_str(inner.as_str().trim());
            }
            Rule::variable => {
                let span = inner.as_span();
                let mut inner_pairs = inner.into_inner();
                let key = inner_pairs.next().unwrap().as_str().trim().to_string();
                let raw_value = inner_pairs.next().unwrap().as_str().to_string();

                assign_request_variable(&mut store, key, raw_value.as_str(), working_dir)
                    .map_err(|err| template_error_to_span(err, span.clone()))?;
            }
            Rule::http_method => {
                method = Some(inner.as_str().parse().unwrap());
            }
            Rule::url => {
                url_raw = Some(inner.as_str().trim().to_string());
            }
            Rule::header => {
                let mut header_pairs = inner.into_inner();
                let key = header_pairs.next().unwrap().as_str().trim().to_string();
                let value = header_pairs.next().unwrap().as_str().trim().to_string();
                header_raw.insert(key, value);
            }
            Rule::query => {
                let mut query_pairs = inner.into_inner();
                let key = query_pairs.next().unwrap().as_str().trim().to_string();
                let value = query_pairs.next().unwrap().as_str().trim().to_string();
                query_raw.insert(key, value);
            }
            Rule::form => {
                let mut form_pairs = inner.into_inner();
                let key = form_pairs.next().unwrap().as_str().trim().to_string();
                let value = form_pairs.next().unwrap();
                match value.as_rule() {
                    Rule::file => {
                        let trimmed = value.as_str().trim_start_matches('@').trim().to_string();
                        form_file_raw.insert(key, trimmed);
                    }
                    Rule::text => {
                        form_text_raw.insert(key, value.as_str().to_string());
                    }
                    _ => unreachable!("unexpected rule: {:?}", value.as_rule()),
                }
            }
            Rule::body => {
                body_raw = Some(inner.as_str().to_string());
            }
            Rule::body_content_type => {
                body_content_type_raw = Some(inner.as_str().trim().to_string());
            }
            Rule::dependency => {
                dependencies.push(parse_dependency(inner.as_str()));
            }
            Rule::callback => {
                callback_src_raw.push(inner.as_str().strip_prefix('!').unwrap().to_string());
            }
            Rule::response_capture => {
                response_capture_raw.push((inner.as_str().to_string(), inner.as_span()));
            }
            Rule::assertion => {
                assertion_raw.push((inner.as_str().to_string(), inner.as_span()));
            }
            Rule::fragment_include => {
                let raw = inner.as_str().to_string();
                let (guard, path) = parse_fragment_include(raw.as_str());
                if path.is_empty() {
                    return Err(pest::error::Error::<Rule>::new_from_span(
                        pest::error::ErrorVariant::CustomError {
                            message: "fragment include is missing a target".to_string(),
                        },
                        inner.as_span(),
                    ));
                }
                fragment_includes_raw.push((path, guard, assertion_raw.len()));
            }
            _ => unreachable!("unexpected rule: {:?}", inner.as_rule()),
        }
    }

    let method = method.ok_or_else(|| {
        pest::error::Error::<Rule>::new_from_span(
            pest::error::ErrorVariant::CustomError {
                message: "request is missing an HTTP method".to_string(),
            },
            request_span.clone(),
        )
    })?;

    let url_raw = url_raw.ok_or_else(|| {
        pest::error::Error::<Rule>::new_from_span(
            pest::error::ErrorVariant::CustomError {
                message: "request is missing a URL".to_string(),
            },
            request_span.clone(),
        )
    })?;

    let scalar_map = store.clone_scalars();

    let description_template = if description_raw.trim().is_empty() {
        "[No Description]".to_string()
    } else {
        description_raw.trim().to_string()
    };

    let group_key = if description_raw.trim().is_empty() {
        "[No Description]".to_string()
    } else {
        context::resolve_with_context(description_raw.trim(), &scalar_map)
    };

    let mut headers = HashMap::new();
    for (key, value) in global_headers {
        headers.insert(
            key.clone(),
            context::inject_from_variable(value.as_str(), &scalar_map),
        );
    }

    for (key, value) in header_raw {
        headers.insert(
            key,
            context::inject_from_variable(value.as_str(), &scalar_map),
        );
    }

    let mut query_params = HashMap::new();
    for (key, value) in global_queries {
        query_params.insert(
            key.clone(),
            context::inject_from_variable(value.as_str(), &scalar_map),
        );
    }

    for (key, value) in query_raw {
        query_params.insert(
            key,
            context::inject_from_variable(value.as_str(), &scalar_map),
        );
    }

    let mut form_text = HashMap::new();
    for (key, value) in form_text_raw {
        form_text.insert(
            key,
            context::inject_from_variable(value.as_str(), &scalar_map),
        );
    }

    let mut form_files = HashMap::new();
    for (key, value) in form_file_raw {
        form_files.insert(
            key,
            context::inject_from_variable(value.as_str(), &scalar_map),
        );
    }

    let body = body_raw.map(|raw| context::inject_from_variable(raw.as_str(), &scalar_map));
    let body_content_type =
        body_content_type_raw.map(|raw| context::inject_from_variable(raw.as_str(), &scalar_map));

    let callback_src = callback_src_raw
        .into_iter()
        .map(|raw| context::inject_from_variable(raw.as_str(), &scalar_map))
        .collect();

    let mut response_captures = Vec::new();
    for (raw, span) in response_capture_raw {
        let capture = ResponseCapture::parse(raw.as_str(), &scalar_map).map_err(|e| {
            pest::error::Error::<Rule>::new_from_span(
                pest::error::ErrorVariant::CustomError { message: e.0 },
                span,
            )
        })?;
        response_captures.push(capture);
    }

    let mut assertions = Vec::new();
    for (raw, span) in assertion_raw {
        let assertion = Assertion::parse(raw.as_str(), &scalar_map).map_err(|e| {
            pest::error::Error::<Rule>::new_from_span(
                pest::error::ErrorVariant::CustomError { message: e.0 },
                span,
            )
        })?;
        assertions.push(assertion);
    }

    let fragment_includes = fragment_includes_raw
        .into_iter()
        .map(|(path, guard, index)| FragmentInclude {
            path,
            guard,
            assertion_index: index,
        })
        .collect();

    Ok(RequestTemplate {
        group_key,
        description_template,
        method,
        url: url_raw,
        headers,
        query_params,
        form_text,
        form_files,
        body,
        body_content_type,
        callback_src,
        response_captures,
        assertions,
        fragment_includes,
        declared_dependencies: dependencies,
        variable_store: store,
        working_dir: working_dir.clone(),
    })
}

fn inspect_request_syntax(
    index: usize,
    pair: pest::iterators::Pair<Rule>,
    collection_store: &VariableStore,
) -> Result<SyntaxRequestSummary, pest::error::Error<Rule>> {
    let request_span = pair.as_span();
    let mut store = collection_store.clone();
    let mut description = String::new();
    let mut method: Option<String> = None;
    let mut url: Option<String> = None;
    let mut response_capture_raw: Vec<(String, pest::Span<'_>)> = Vec::new();
    let mut assertion_raw: Vec<(String, pest::Span<'_>)> = Vec::new();
    let mut fragment_guard_raw: Vec<(String, pest::Span<'_>)> = Vec::new();

    for inner in pair.into_inner() {
        match inner.as_rule() {
            Rule::description => description.push_str(inner.as_str().trim()),
            Rule::variable => {
                let span = inner.as_span();
                let mut inner_pairs = inner.into_inner();
                let key = inner_pairs.next().unwrap().as_str().trim().to_string();
                let raw_value = inner_pairs.next().unwrap().as_str().to_string();

                assign_request_variable_for_validation(&mut store, key, raw_value.as_str())
                    .map_err(|err| template_error_to_span(err, span.clone()))?;
            }
            Rule::http_method => method = Some(inner.as_str().to_string()),
            Rule::url => url = Some(inner.as_str().trim().to_string()),
            Rule::response_capture => {
                response_capture_raw.push((inner.as_str().to_string(), inner.as_span()));
            }
            Rule::assertion => {
                assertion_raw.push((inner.as_str().to_string(), inner.as_span()));
            }
            Rule::fragment_include => {
                let raw = inner.as_str().to_string();
                let (guard, path) = parse_fragment_include(raw.as_str());
                if path.is_empty() {
                    return Err(pest::error::Error::<Rule>::new_from_span(
                        pest::error::ErrorVariant::CustomError {
                            message: "fragment include is missing a target".to_string(),
                        },
                        inner.as_span(),
                    ));
                }
                if let Some(guard) = guard {
                    fragment_guard_raw.push((guard, inner.as_span()));
                }
            }
            _ => {}
        }
    }

    let scalar_map = store.clone_scalars();

    for (raw, span) in response_capture_raw {
        ResponseCapture::parse(raw.as_str(), &scalar_map).map_err(|err| {
            pest::error::Error::<Rule>::new_from_span(
                pest::error::ErrorVariant::CustomError { message: err.0 },
                span,
            )
        })?;
    }

    for (raw, span) in assertion_raw {
        Assertion::parse(raw.as_str(), &scalar_map).map_err(|err| {
            pest::error::Error::<Rule>::new_from_span(
                pest::error::ErrorVariant::CustomError { message: err.0 },
                span,
            )
        })?;
    }

    for (guard, span) in fragment_guard_raw {
        let wrapped = format!("^ {}", guard);
        Assertion::parse(wrapped.as_str(), &scalar_map).map_err(|err| {
            pest::error::Error::<Rule>::new_from_span(
                pest::error::ErrorVariant::CustomError { message: err.0 },
                span,
            )
        })?;
    }

    let method = method.ok_or_else(|| {
        pest::error::Error::<Rule>::new_from_span(
            pest::error::ErrorVariant::CustomError {
                message: "request is missing an HTTP method".to_string(),
            },
            request_span.clone(),
        )
    })?;

    let url = url.ok_or_else(|| {
        pest::error::Error::<Rule>::new_from_span(
            pest::error::ErrorVariant::CustomError {
                message: "request is missing a URL".to_string(),
            },
            request_span,
        )
    })?;

    let description = if description.trim().is_empty() {
        "[No Description]".to_string()
    } else {
        description.trim().to_string()
    };

    Ok(SyntaxRequestSummary {
        index,
        description,
        method,
        url,
    })
}

fn parse_fragment_include(raw: &str) -> (Option<String>, String) {
    let trimmed = raw.trim();
    if !trimmed.starts_with("<<") && !trimmed.starts_with('[') {
        // no guard, direct include
        let path = trimmed.trim_start_matches("<<").trim().to_string();
        return (None, path);
    }

    if let Some(remainder) = trimmed.strip_prefix("<<") {
        return (None, remainder.trim().to_string());
    }

    let mut chars = trimmed.chars().peekable();
    if chars.next() != Some('[') {
        return (None, trimmed.trim().to_string());
    }

    let mut guard = String::new();
    let mut in_single = false;
    let mut in_double = false;

    while let Some(ch) = chars.next() {
        match ch {
            '\\' => {
                if let Some(next) = chars.next() {
                    guard.push(next);
                }
            }
            '\'' if !in_double => {
                in_single = !in_single;
                guard.push(ch);
            }
            '"' if !in_single => {
                in_double = !in_double;
                guard.push(ch);
            }
            ']' if !in_single && !in_double => {
                let remainder: String = chars.collect();
                let remainder = remainder.trim();
                let path = remainder.trim_start_matches("<<").trim().to_string();
                return (Some(guard.trim().to_string()), path);
            }
            _ => guard.push(ch),
        }
    }

    (None, trimmed.trim().to_string())
}

fn parse_dependency(raw: &str) -> String {
    let remainder = raw
        .strip_prefix('>')
        .map(|s| s.trim())
        .unwrap_or(raw.trim());

    let remainder = remainder
        .strip_prefix("requires")
        .map(|s| s.trim_start())
        .unwrap_or(remainder);

    let remainder = remainder
        .strip_prefix(':')
        .map(|s| s.trim_start())
        .unwrap_or(remainder);

    let name = remainder.trim();

    if name.starts_with('"') && name.ends_with('"') && name.len() >= 2 {
        name[1..name.len() - 1].trim().to_string()
    } else {
        name.to_string()
    }
}

pub fn eval_shell_script(
    script: &str,
    working_dir: &PathBuf,
    env: Option<HashMap<String, String>>,
) -> String {
    let env = env.unwrap_or_default();
    log::debug!("evaluating shell script: {}", script);
    log::debug!("using directory {:?}", working_dir);
    let output = std::process::Command::new("sh")
        .current_dir(working_dir)
        .arg("-c")
        .envs(env)
        .arg(script)
        .output()
        .expect("failed to execute process");

    String::from_utf8(output.stdout).unwrap()
}

pub fn preprocess_only(input: &str, working_dir: &Path) -> Result<String, String> {
    preprocessor::preprocess(input, working_dir).map_err(|err| err.to_string())
}

fn assign_global_variable_for_validation(
    store: &mut VariableStore,
    key: String,
    raw_value: &str,
) -> Result<(), TemplateError> {
    let label = format!("${}", key);
    let trimmed = raw_value.trim();
    let empty_context = HashMap::new();

    if trimmed.starts_with("$(") {
        store.set_scalar(key.clone(), syntax_placeholder_value(key.as_str()));
        return Ok(());
    }

    if is_array_literal(trimmed) {
        let values = parse_array_literal(trimmed, &label, &key, |value| {
            Ok(context::inject_from_variable(value, &empty_context))
        })?;
        store.set_array(key, values);
        return Ok(());
    }

    let value = context::inject_from_variable(trimmed, &empty_context);
    store.set_scalar(key, value);
    Ok(())
}

fn assign_request_variable_for_validation(
    store: &mut VariableStore,
    key: String,
    raw_value: &str,
) -> Result<(), TemplateError> {
    let label = format!("request variable ${}", key);
    let trimmed = raw_value.trim();

    if trimmed.starts_with("$(") {
        store.set_scalar(key.clone(), syntax_placeholder_value(key.as_str()));
        return Ok(());
    }

    let current_scalars = store.clone_scalars();

    if is_array_literal(trimmed) {
        let values = parse_array_literal(trimmed, &label, &key, |value| {
            Ok(context::inject_from_variable(value, &current_scalars))
        })?;
        store.set_array(key, values);
        return Ok(());
    }

    let value = context::inject_from_variable(trimmed, &current_scalars);
    store.set_scalar(key, value);
    Ok(())
}

fn syntax_placeholder_value(key: &str) -> String {
    format!("__hen_syntax_{}__", key)
}

fn assign_global_variable(
    store: &mut VariableStore,
    key: String,
    raw_value: &str,
    working_dir: &PathBuf,
) -> Result<(), TemplateError> {
    let label = format!("${}", key);
    let trimmed = raw_value.trim();

    if trimmed.starts_with("$(") {
        let script = trimmed.trim_start_matches("$(").trim_end_matches(")");
        let value = eval_shell_script(script, working_dir, None)
            .trim()
            .to_string();
        store.set_scalar(key, value);
        return Ok(());
    }

    if is_array_literal(trimmed) {
        let values = parse_array_literal(trimmed, &label, &key, |value| {
            context::try_inject_from_prompt(value)
                .map_err(|err| prompt_error_to_template_error(&label, err))
        })?;
        store.set_array(key, values);
        return Ok(());
    }

    let value = context::try_inject_from_prompt(trimmed)
        .map_err(|err| prompt_error_to_template_error(&label, err))?;
    store.set_scalar(key, value);
    Ok(())
}

fn assign_request_variable(
    store: &mut VariableStore,
    key: String,
    raw_value: &str,
    working_dir: &PathBuf,
) -> Result<(), TemplateError> {
    let label = format!("request variable ${}", key);
    let trimmed = raw_value.trim();

    if trimmed.starts_with("$(") {
        let script = trimmed.trim_start_matches("$(").trim_end_matches(")");
        let value = eval_shell_script(script, working_dir, None)
            .trim()
            .to_string();
        store.set_scalar(key, value);
        return Ok(());
    }

    let current_scalars = store.clone_scalars();

    if is_array_literal(trimmed) {
        let values = parse_array_literal(trimmed, &label, &key, |value| {
            Ok(context::inject_from_variable(value, &current_scalars))
        })?;
        store.set_array(key, values);
        return Ok(());
    }

    let value = context::inject_from_variable(trimmed, &current_scalars);
    store.set_scalar(key, value);
    Ok(())
}

fn is_array_literal(raw: &str) -> bool {
    let trimmed = raw.trim();
    trimmed.starts_with('[') && trimmed.ends_with(']') && trimmed.len() >= 2
}

fn parse_array_literal<F>(
    raw: &str,
    label: &str,
    variable: &str,
    mut transform: F,
) -> Result<Vec<String>, TemplateError>
where
    F: FnMut(&str) -> Result<String, TemplateError>,
{
    if !is_array_literal(raw) {
        return Err(TemplateError::InvalidArrayValue {
            request: label.to_string(),
            variable: variable.to_string(),
            value: raw.to_string(),
        });
    }

    let inner = &raw[1..raw.len() - 1];
    if inner.trim().is_empty() {
        return Err(TemplateError::EmptyArrayValues {
            request: label.to_string(),
            variable: variable.to_string(),
        });
    }

    let mut segments: Vec<String> = Vec::new();
    let mut current = String::new();
    let mut in_single = false;
    let mut in_double = false;

    for ch in inner.chars() {
        match ch {
            '\'' => {
                if !in_double {
                    in_single = !in_single;
                }
                current.push(ch);
            }
            '"' => {
                if !in_single {
                    in_double = !in_double;
                }
                current.push(ch);
            }
            ',' if !in_single && !in_double => {
                if current.trim().is_empty() {
                    return Err(TemplateError::InvalidArrayValue {
                        request: label.to_string(),
                        variable: variable.to_string(),
                        value: raw.to_string(),
                    });
                }
                segments.push(current.trim().to_string());
                current.clear();
            }
            _ => current.push(ch),
        }
    }

    if in_single || in_double {
        return Err(TemplateError::InvalidArrayValue {
            request: label.to_string(),
            variable: variable.to_string(),
            value: raw.to_string(),
        });
    }

    if current.trim().is_empty() {
        return Err(TemplateError::InvalidArrayValue {
            request: label.to_string(),
            variable: variable.to_string(),
            value: raw.to_string(),
        });
    }

    segments.push(current.trim().to_string());

    let mut values = Vec::with_capacity(segments.len());
    for segment in segments {
        let normalized = normalize_array_element(segment.as_str());
        if normalized.trim().is_empty() {
            return Err(TemplateError::InvalidArrayValue {
                request: label.to_string(),
                variable: variable.to_string(),
                value: raw.to_string(),
            });
        }
        values.push(transform(normalized.trim())?);
    }

    Ok(values)
}

fn normalize_array_element(value: &str) -> String {
    let trimmed = value.trim();
    if (trimmed.starts_with('"') && trimmed.ends_with('"') && trimmed.len() >= 2)
        || (trimmed.starts_with('\'') && trimmed.ends_with('\'') && trimmed.len() >= 2)
    {
        trimmed[1..trimmed.len() - 1].to_string()
    } else {
        trimmed.to_string()
    }
}

fn template_error_to_span(error: TemplateError, span: pest::Span<'_>) -> pest::error::Error<Rule> {
    pest::error::Error::new_from_span(
        pest::error::ErrorVariant::CustomError {
            message: error.to_string(),
        },
        span,
    )
}

fn template_error_to_collection_error(
    error: TemplateError,
    source: &str,
) -> pest::error::Error<Rule> {
    pest::error::Error::new_from_span(
        pest::error::ErrorVariant::CustomError {
            message: error.to_string(),
        },
        pest::Span::new(source, 0, source.len()).unwrap(),
    )
}

fn prompt_error_to_span(
    error: context::PromptInputError,
    span: pest::Span<'_>,
) -> pest::error::Error<Rule> {
    pest::error::Error::new_from_span(
        pest::error::ErrorVariant::CustomError {
            message: error.to_string(),
        },
        span,
    )
}

fn prompt_error_to_template_error(
    request: &str,
    error: context::PromptInputError,
) -> TemplateError {
    TemplateError::PromptInput {
        request: request.to_string(),
        prompt: error.prompt().to_string(),
        default: error.default().map(str::to_string),
    }
}
